1use bitcoin::bip32::DerivationPath;
4use cashu::util::unix_time;
5use cashu::{
6 Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
7 MintQuoteBolt12Response, PaymentMethod,
8};
9use lightning::offers::offer::Offer;
10use serde::{Deserialize, Serialize};
11use tracing::instrument;
12use uuid::Uuid;
13
14use crate::nuts::{MeltQuoteState, MintQuoteState};
15use crate::payment::PaymentIdentifier;
16use crate::{Amount, CurrencyUnit, Id, KeySetInfo, PublicKey};
17
18#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
20pub struct MintQuote {
21 pub id: Uuid,
23 pub amount: Option<Amount>,
25 pub unit: CurrencyUnit,
27 pub request: String,
29 pub expiry: u64,
31 pub request_lookup_id: PaymentIdentifier,
33 pub pubkey: Option<PublicKey>,
35 #[serde(default)]
37 pub created_time: u64,
38 #[serde(default)]
40 amount_paid: Amount,
41 #[serde(default)]
43 amount_issued: Amount,
44 #[serde(default)]
46 pub payments: Vec<IncomingPayment>,
47 #[serde(default)]
49 pub payment_method: PaymentMethod,
50 #[serde(default)]
52 pub issuance: Vec<Issuance>,
53}
54
55impl MintQuote {
56 #[allow(clippy::too_many_arguments)]
58 pub fn new(
59 id: Option<Uuid>,
60 request: String,
61 unit: CurrencyUnit,
62 amount: Option<Amount>,
63 expiry: u64,
64 request_lookup_id: PaymentIdentifier,
65 pubkey: Option<PublicKey>,
66 amount_paid: Amount,
67 amount_issued: Amount,
68 payment_method: PaymentMethod,
69 created_time: u64,
70 payments: Vec<IncomingPayment>,
71 issuance: Vec<Issuance>,
72 ) -> Self {
73 let id = id.unwrap_or(Uuid::new_v4());
74
75 Self {
76 id,
77 amount,
78 unit,
79 request,
80 expiry,
81 request_lookup_id,
82 pubkey,
83 created_time,
84 amount_paid,
85 amount_issued,
86 payment_method,
87 payments,
88 issuance,
89 }
90 }
91
92 #[instrument(skip(self))]
94 pub fn increment_amount_paid(
95 &mut self,
96 additional_amount: Amount,
97 ) -> Result<Amount, crate::Error> {
98 self.amount_paid = self
99 .amount_paid
100 .checked_add(additional_amount)
101 .ok_or(crate::Error::AmountOverflow)?;
102 Ok(self.amount_paid)
103 }
104
105 #[instrument(skip(self))]
107 pub fn amount_paid(&self) -> Amount {
108 self.amount_paid
109 }
110
111 #[instrument(skip(self))]
113 pub fn increment_amount_issued(
114 &mut self,
115 additional_amount: Amount,
116 ) -> Result<Amount, crate::Error> {
117 self.amount_issued = self
118 .amount_issued
119 .checked_add(additional_amount)
120 .ok_or(crate::Error::AmountOverflow)?;
121 Ok(self.amount_issued)
122 }
123
124 #[instrument(skip(self))]
126 pub fn amount_issued(&self) -> Amount {
127 self.amount_issued
128 }
129
130 #[instrument(skip(self))]
132 pub fn state(&self) -> MintQuoteState {
133 self.compute_quote_state()
134 }
135
136 pub fn payment_ids(&self) -> Vec<&String> {
138 self.payments.iter().map(|a| &a.payment_id).collect()
139 }
140
141 #[instrument(skip(self))]
145 pub fn add_payment(
146 &mut self,
147 amount: Amount,
148 payment_id: String,
149 time: u64,
150 ) -> Result<(), crate::Error> {
151 let payment_ids = self.payment_ids();
152 if payment_ids.contains(&&payment_id) {
153 return Err(crate::Error::DuplicatePaymentId);
154 }
155
156 let payment = IncomingPayment::new(amount, payment_id, time);
157
158 self.payments.push(payment);
159 Ok(())
160 }
161
162 #[instrument(skip(self))]
164 fn compute_quote_state(&self) -> MintQuoteState {
165 if self.amount_paid == Amount::ZERO && self.amount_issued == Amount::ZERO {
166 return MintQuoteState::Unpaid;
167 }
168
169 match self.amount_paid.cmp(&self.amount_issued) {
170 std::cmp::Ordering::Less => {
171 tracing::error!("We should not have issued more then has been paid");
174 MintQuoteState::Issued
175 }
176 std::cmp::Ordering::Equal => {
177 MintQuoteState::Issued
181 }
182 std::cmp::Ordering::Greater => {
183 MintQuoteState::Paid
186 }
187 }
188 }
189}
190
191#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
193pub struct IncomingPayment {
194 pub amount: Amount,
196 pub time: u64,
198 pub payment_id: String,
200}
201
202impl IncomingPayment {
203 pub fn new(amount: Amount, payment_id: String, time: u64) -> Self {
205 Self {
206 payment_id,
207 time,
208 amount,
209 }
210 }
211}
212
213#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
215pub struct Issuance {
216 pub amount: Amount,
218 pub time: u64,
220}
221
222impl Issuance {
223 pub fn new(amount: Amount, time: u64) -> Self {
225 Self { amount, time }
226 }
227}
228
229#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
231pub struct MeltQuote {
232 pub id: Uuid,
234 pub unit: CurrencyUnit,
236 pub amount: Amount,
238 pub request: MeltPaymentRequest,
240 pub fee_reserve: Amount,
242 pub state: MeltQuoteState,
244 pub expiry: u64,
246 pub payment_preimage: Option<String>,
248 pub request_lookup_id: Option<PaymentIdentifier>,
250 pub options: Option<MeltOptions>,
254 #[serde(default)]
256 pub created_time: u64,
257 pub paid_time: Option<u64>,
259 #[serde(default)]
261 pub payment_method: PaymentMethod,
262}
263
264impl MeltQuote {
265 #[allow(clippy::too_many_arguments)]
267 pub fn new(
268 request: MeltPaymentRequest,
269 unit: CurrencyUnit,
270 amount: Amount,
271 fee_reserve: Amount,
272 expiry: u64,
273 request_lookup_id: Option<PaymentIdentifier>,
274 options: Option<MeltOptions>,
275 payment_method: PaymentMethod,
276 ) -> Self {
277 let id = Uuid::new_v4();
278
279 Self {
280 id,
281 amount,
282 unit,
283 request,
284 fee_reserve,
285 state: MeltQuoteState::Unpaid,
286 expiry,
287 payment_preimage: None,
288 request_lookup_id,
289 options,
290 created_time: unix_time(),
291 paid_time: None,
292 payment_method,
293 }
294 }
295}
296
297#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
299pub struct MintKeySetInfo {
300 pub id: Id,
302 pub unit: CurrencyUnit,
304 pub active: bool,
307 pub valid_from: u64,
309 pub derivation_path: DerivationPath,
311 pub derivation_path_index: Option<u32>,
313 pub max_order: u8,
315 #[serde(default = "default_fee")]
317 pub input_fee_ppk: u64,
318 pub final_expiry: Option<u64>,
320}
321
322pub fn default_fee() -> u64 {
324 0
325}
326
327impl From<MintKeySetInfo> for KeySetInfo {
328 fn from(keyset_info: MintKeySetInfo) -> Self {
329 Self {
330 id: keyset_info.id,
331 unit: keyset_info.unit,
332 active: keyset_info.active,
333 input_fee_ppk: keyset_info.input_fee_ppk,
334 final_expiry: keyset_info.final_expiry,
335 }
336 }
337}
338
339impl From<MintQuote> for MintQuoteBolt11Response<Uuid> {
340 fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
341 MintQuoteBolt11Response {
342 quote: mint_quote.id,
343 state: mint_quote.state(),
344 request: mint_quote.request,
345 expiry: Some(mint_quote.expiry),
346 pubkey: mint_quote.pubkey,
347 amount: mint_quote.amount,
348 unit: Some(mint_quote.unit.clone()),
349 }
350 }
351}
352
353impl From<MintQuote> for MintQuoteBolt11Response<String> {
354 fn from(quote: MintQuote) -> Self {
355 let quote: MintQuoteBolt11Response<Uuid> = quote.into();
356
357 quote.into()
358 }
359}
360
361impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<Uuid> {
362 type Error = crate::Error;
363
364 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
365 Ok(MintQuoteBolt12Response {
366 quote: mint_quote.id,
367 request: mint_quote.request,
368 expiry: Some(mint_quote.expiry),
369 amount_paid: mint_quote.amount_paid,
370 amount_issued: mint_quote.amount_issued,
371 pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
372 amount: mint_quote.amount,
373 unit: mint_quote.unit,
374 })
375 }
376}
377
378impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
379 type Error = crate::Error;
380
381 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
382 let quote: MintQuoteBolt12Response<Uuid> = quote.try_into()?;
383
384 Ok(quote.into())
385 }
386}
387
388impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
389 fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
390 MeltQuoteBolt11Response {
391 quote: melt_quote.id,
392 payment_preimage: None,
393 change: None,
394 state: melt_quote.state,
395 paid: Some(melt_quote.state == MeltQuoteState::Paid),
396 expiry: melt_quote.expiry,
397 amount: melt_quote.amount,
398 fee_reserve: melt_quote.fee_reserve,
399 request: None,
400 unit: Some(melt_quote.unit.clone()),
401 }
402 }
403}
404
405impl From<MeltQuote> for MeltQuoteBolt11Response<Uuid> {
406 fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
407 let paid = melt_quote.state == MeltQuoteState::Paid;
408 MeltQuoteBolt11Response {
409 quote: melt_quote.id,
410 amount: melt_quote.amount,
411 fee_reserve: melt_quote.fee_reserve,
412 paid: Some(paid),
413 state: melt_quote.state,
414 expiry: melt_quote.expiry,
415 payment_preimage: melt_quote.payment_preimage,
416 change: None,
417 request: Some(melt_quote.request.to_string()),
418 unit: Some(melt_quote.unit.clone()),
419 }
420 }
421}
422
423#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
425pub enum MeltPaymentRequest {
426 Bolt11 {
428 bolt11: Bolt11Invoice,
430 },
431 Bolt12 {
433 #[serde(with = "offer_serde")]
435 offer: Box<Offer>,
436 },
437}
438
439impl std::fmt::Display for MeltPaymentRequest {
440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441 match self {
442 MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
443 MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
444 }
445 }
446}
447
448mod offer_serde {
449 use std::str::FromStr;
450
451 use serde::{self, Deserialize, Deserializer, Serializer};
452
453 use super::Offer;
454
455 pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
456 where
457 S: Serializer,
458 {
459 let s = offer.to_string();
460 serializer.serialize_str(&s)
461 }
462
463 pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
464 where
465 D: Deserializer<'de>,
466 {
467 let s = String::deserialize(deserializer)?;
468 Ok(Box::new(Offer::from_str(&s).map_err(|_| {
469 serde::de::Error::custom("Invalid Bolt12 Offer")
470 })?))
471 }
472}