1use std::convert::Infallible;
4use std::pin::Pin;
5
6use async_trait::async_trait;
7use cashu::util::hex;
8use cashu::{Bolt11Invoice, MeltOptions};
9#[cfg(feature = "prometheus")]
10use cdk_prometheus::METRICS;
11use futures::Stream;
12use lightning::offers::offer::Offer;
13use lightning_invoice::ParseOrSemanticError;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use thiserror::Error;
17
18use crate::mint::MeltPaymentRequest;
19use crate::nuts::{CurrencyUnit, MeltQuoteState};
20use crate::Amount;
21
22#[derive(Debug, Error)]
24pub enum Error {
25 #[error("Invoice already paid")]
27 InvoiceAlreadyPaid,
28 #[error("Invoice pay is pending")]
30 InvoicePaymentPending,
31 #[error("Unsupported unit")]
33 UnsupportedUnit,
34 #[error("Unsupported payment option")]
36 UnsupportedPaymentOption,
37 #[error("Payment state is unknown")]
39 UnknownPaymentState,
40 #[error("Amount is not what is expected")]
42 AmountMismatch,
43 #[error(transparent)]
45 Lightning(Box<dyn std::error::Error + Send + Sync>),
46 #[error(transparent)]
48 Serde(#[from] serde_json::Error),
49 #[error(transparent)]
51 Anyhow(#[from] anyhow::Error),
52 #[error(transparent)]
54 Parse(#[from] ParseOrSemanticError),
55 #[error(transparent)]
57 Amount(#[from] crate::amount::Error),
58 #[error(transparent)]
60 NUT04(#[from] crate::nuts::nut04::Error),
61 #[error(transparent)]
63 NUT05(#[from] crate::nuts::nut05::Error),
64 #[error(transparent)]
66 NUT23(#[from] crate::nuts::nut23::Error),
67 #[error("Hex error")]
69 Hex(#[from] hex::Error),
70 #[error("Invalid hash")]
72 InvalidHash,
73 #[error("`{0}`")]
75 Custom(String),
76}
77
78impl From<Infallible> for Error {
79 fn from(_: Infallible) -> Self {
80 unreachable!("Infallible cannot be constructed")
81 }
82}
83
84#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
86#[serde(tag = "type", content = "value")]
87pub enum PaymentIdentifier {
88 Label(String),
90 OfferId(String),
92 PaymentHash([u8; 32]),
94 Bolt12PaymentHash([u8; 32]),
96 PaymentId([u8; 32]),
98 CustomId(String),
100}
101
102impl PaymentIdentifier {
103 pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
105 match kind.to_lowercase().as_str() {
106 "label" => Ok(Self::Label(identifier.to_string())),
107 "offer_id" => Ok(Self::OfferId(identifier.to_string())),
108 "payment_hash" => Ok(Self::PaymentHash(
109 hex::decode(identifier)?
110 .try_into()
111 .map_err(|_| Error::InvalidHash)?,
112 )),
113 "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
114 hex::decode(identifier)?
115 .try_into()
116 .map_err(|_| Error::InvalidHash)?,
117 )),
118 "custom" => Ok(Self::CustomId(identifier.to_string())),
119 "payment_id" => Ok(Self::PaymentId(
120 hex::decode(identifier)?
121 .try_into()
122 .map_err(|_| Error::InvalidHash)?,
123 )),
124 _ => Err(Error::UnsupportedPaymentOption),
125 }
126 }
127
128 pub fn kind(&self) -> String {
130 match self {
131 Self::Label(_) => "label".to_string(),
132 Self::OfferId(_) => "offer_id".to_string(),
133 Self::PaymentHash(_) => "payment_hash".to_string(),
134 Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
135 Self::PaymentId(_) => "payment_id".to_string(),
136 Self::CustomId(_) => "custom".to_string(),
137 }
138 }
139}
140
141impl std::fmt::Display for PaymentIdentifier {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::Label(l) => write!(f, "{l}"),
145 Self::OfferId(o) => write!(f, "{o}"),
146 Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
147 Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
148 Self::PaymentId(h) => write!(f, "{}", hex::encode(h)),
149 Self::CustomId(c) => write!(f, "{c}"),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
156pub struct Bolt11IncomingPaymentOptions {
157 pub description: Option<String>,
159 pub amount: Amount,
161 pub unix_expiry: Option<u64>,
163}
164
165#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
167pub struct Bolt12IncomingPaymentOptions {
168 pub description: Option<String>,
170 pub amount: Option<Amount>,
172 pub unix_expiry: Option<u64>,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
178pub enum IncomingPaymentOptions {
179 Bolt11(Bolt11IncomingPaymentOptions),
181 Bolt12(Box<Bolt12IncomingPaymentOptions>),
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
187pub struct Bolt11OutgoingPaymentOptions {
188 pub bolt11: Bolt11Invoice,
190 pub max_fee_amount: Option<Amount>,
192 pub timeout_secs: Option<u64>,
194 pub melt_options: Option<MeltOptions>,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Hash)]
200pub struct Bolt12OutgoingPaymentOptions {
201 pub offer: Offer,
203 pub max_fee_amount: Option<Amount>,
205 pub timeout_secs: Option<u64>,
207 pub melt_options: Option<MeltOptions>,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Hash)]
213pub enum OutgoingPaymentOptions {
214 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
216 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
218}
219
220impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
221 type Error = Error;
222
223 fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
224 match melt_quote.request {
225 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
226 Bolt11OutgoingPaymentOptions {
227 max_fee_amount: Some(melt_quote.fee_reserve),
228 timeout_secs: None,
229 bolt11,
230 melt_options: melt_quote.options,
231 },
232 ))),
233 MeltPaymentRequest::Bolt12 { offer } => {
234 let melt_options = match melt_quote.options {
235 None => None,
236 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
237 Some(options) => Some(options),
238 };
239
240 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
241 Bolt12OutgoingPaymentOptions {
242 max_fee_amount: Some(melt_quote.fee_reserve),
243 timeout_secs: None,
244 offer: *offer,
245 melt_options,
246 },
247 )))
248 }
249 }
250 }
251}
252
253#[async_trait]
255pub trait MintPayment {
256 type Err: Into<Error> + From<Error>;
258
259 async fn start(&self) -> Result<(), Self::Err> {
262 Ok(())
264 }
265
266 async fn stop(&self) -> Result<(), Self::Err> {
269 Ok(())
271 }
272
273 async fn get_settings(&self) -> Result<serde_json::Value, Self::Err>;
275
276 async fn create_incoming_payment_request(
278 &self,
279 unit: &CurrencyUnit,
280 options: IncomingPaymentOptions,
281 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
282
283 async fn get_payment_quote(
286 &self,
287 unit: &CurrencyUnit,
288 options: OutgoingPaymentOptions,
289 ) -> Result<PaymentQuoteResponse, Self::Err>;
290
291 async fn make_payment(
293 &self,
294 unit: &CurrencyUnit,
295 options: OutgoingPaymentOptions,
296 ) -> Result<MakePaymentResponse, Self::Err>;
297
298 async fn wait_payment_event(
301 &self,
302 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
303
304 fn is_wait_invoice_active(&self) -> bool;
306
307 fn cancel_wait_invoice(&self);
309
310 async fn check_incoming_payment_status(
312 &self,
313 payment_identifier: &PaymentIdentifier,
314 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
315
316 async fn check_outgoing_payment(
318 &self,
319 payment_identifier: &PaymentIdentifier,
320 ) -> Result<MakePaymentResponse, Self::Err>;
321}
322
323#[derive(Debug, Clone, Hash)]
325pub enum Event {
326 PaymentReceived(WaitPaymentResponse),
328}
329
330impl Default for Event {
331 fn default() -> Self {
332 Event::PaymentReceived(WaitPaymentResponse {
335 payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
336 payment_amount: Amount::from(0),
337 unit: CurrencyUnit::Msat,
338 payment_id: "default".to_string(),
339 })
340 }
341}
342
343#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
345pub struct WaitPaymentResponse {
346 pub payment_identifier: PaymentIdentifier,
349 pub payment_amount: Amount,
351 pub unit: CurrencyUnit,
353 pub payment_id: String,
356}
357
358#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
360pub struct CreateIncomingPaymentResponse {
361 pub request_lookup_id: PaymentIdentifier,
363 pub request: String,
365 pub expiry: Option<u64>,
367}
368
369#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
371pub struct MakePaymentResponse {
372 pub payment_lookup_id: PaymentIdentifier,
374 pub payment_proof: Option<String>,
376 pub status: MeltQuoteState,
378 pub total_spent: Amount,
380 pub unit: CurrencyUnit,
382}
383
384#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
386pub struct PaymentQuoteResponse {
387 pub request_lookup_id: Option<PaymentIdentifier>,
389 pub amount: Amount,
391 pub fee: Amount,
393 pub unit: CurrencyUnit,
395 pub state: MeltQuoteState,
397}
398
399#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
401pub struct Bolt11Settings {
402 pub mpp: bool,
404 pub unit: CurrencyUnit,
406 pub invoice_description: bool,
408 pub amountless: bool,
410 pub bolt12: bool,
412}
413
414impl TryFrom<Bolt11Settings> for Value {
415 type Error = crate::error::Error;
416
417 fn try_from(value: Bolt11Settings) -> Result<Self, Self::Error> {
418 serde_json::to_value(value).map_err(|err| err.into())
419 }
420}
421
422impl TryFrom<Value> for Bolt11Settings {
423 type Error = crate::error::Error;
424
425 fn try_from(value: Value) -> Result<Self, Self::Error> {
426 serde_json::from_value(value).map_err(|err| err.into())
427 }
428}
429
430#[derive(Clone)]
436#[cfg(feature = "prometheus")]
437pub struct MetricsMintPayment<T> {
438 inner: T,
439}
440#[cfg(feature = "prometheus")]
441impl<T> MetricsMintPayment<T>
442where
443 T: MintPayment,
444{
445 pub fn new(inner: T) -> Self {
447 Self { inner }
448 }
449
450 pub fn inner(&self) -> &T {
452 &self.inner
453 }
454
455 pub fn into_inner(self) -> T {
457 self.inner
458 }
459}
460
461#[async_trait]
462#[cfg(feature = "prometheus")]
463impl<T> MintPayment for MetricsMintPayment<T>
464where
465 T: MintPayment + Send + Sync,
466{
467 type Err = T::Err;
468
469 async fn get_settings(&self) -> Result<serde_json::Value, Self::Err> {
470 let start = std::time::Instant::now();
471 METRICS.inc_in_flight_requests("get_settings");
472
473 let result = self.inner.get_settings().await;
474
475 let duration = start.elapsed().as_secs_f64();
476 METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
477 METRICS.dec_in_flight_requests("get_settings");
478
479 result
480 }
481
482 async fn create_incoming_payment_request(
483 &self,
484 unit: &CurrencyUnit,
485 options: IncomingPaymentOptions,
486 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
487 let start = std::time::Instant::now();
488 METRICS.inc_in_flight_requests("create_incoming_payment_request");
489
490 let result = self
491 .inner
492 .create_incoming_payment_request(unit, options)
493 .await;
494
495 let duration = start.elapsed().as_secs_f64();
496 METRICS.record_mint_operation_histogram(
497 "create_incoming_payment_request",
498 result.is_ok(),
499 duration,
500 );
501 METRICS.dec_in_flight_requests("create_incoming_payment_request");
502
503 result
504 }
505
506 async fn get_payment_quote(
507 &self,
508 unit: &CurrencyUnit,
509 options: OutgoingPaymentOptions,
510 ) -> Result<PaymentQuoteResponse, Self::Err> {
511 let start = std::time::Instant::now();
512 METRICS.inc_in_flight_requests("get_payment_quote");
513
514 let result = self.inner.get_payment_quote(unit, options).await;
515
516 let duration = start.elapsed().as_secs_f64();
517 let success = result.is_ok();
518
519 if let Ok(ref quote) = result {
520 let amount: f64 = u64::from(quote.amount) as f64;
521 let fee: f64 = u64::from(quote.fee) as f64;
522 METRICS.record_lightning_payment(amount, fee);
523 }
524
525 METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
526 METRICS.dec_in_flight_requests("get_payment_quote");
527
528 result
529 }
530 async fn wait_payment_event(
531 &self,
532 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
533 let start = std::time::Instant::now();
534 METRICS.inc_in_flight_requests("wait_payment_event");
535
536 let result = self.inner.wait_payment_event().await;
537
538 let duration = start.elapsed().as_secs_f64();
539 let success = result.is_ok();
540
541 METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
542 METRICS.dec_in_flight_requests("wait_payment_event");
543
544 result
545 }
546
547 async fn make_payment(
548 &self,
549 unit: &CurrencyUnit,
550 options: OutgoingPaymentOptions,
551 ) -> Result<MakePaymentResponse, Self::Err> {
552 let start = std::time::Instant::now();
553 METRICS.inc_in_flight_requests("make_payment");
554
555 let result = self.inner.make_payment(unit, options).await;
556
557 let duration = start.elapsed().as_secs_f64();
558 let success = result.is_ok();
559
560 METRICS.record_mint_operation_histogram("make_payment", success, duration);
561 METRICS.dec_in_flight_requests("make_payment");
562
563 result
564 }
565
566 fn is_wait_invoice_active(&self) -> bool {
567 self.inner.is_wait_invoice_active()
568 }
569
570 fn cancel_wait_invoice(&self) {
571 self.inner.cancel_wait_invoice()
572 }
573
574 async fn check_incoming_payment_status(
575 &self,
576 payment_identifier: &PaymentIdentifier,
577 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
578 let start = std::time::Instant::now();
579 METRICS.inc_in_flight_requests("check_incoming_payment_status");
580
581 let result = self
582 .inner
583 .check_incoming_payment_status(payment_identifier)
584 .await;
585
586 let duration = start.elapsed().as_secs_f64();
587 METRICS.record_mint_operation_histogram(
588 "check_incoming_payment_status",
589 result.is_ok(),
590 duration,
591 );
592 METRICS.dec_in_flight_requests("check_incoming_payment_status");
593
594 result
595 }
596
597 async fn check_outgoing_payment(
598 &self,
599 payment_identifier: &PaymentIdentifier,
600 ) -> Result<MakePaymentResponse, Self::Err> {
601 let start = std::time::Instant::now();
602 METRICS.inc_in_flight_requests("check_outgoing_payment");
603
604 let result = self.inner.check_outgoing_payment(payment_identifier).await;
605
606 let duration = start.elapsed().as_secs_f64();
607 let success = result.is_ok();
608
609 METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
610 METRICS.dec_in_flight_requests("check_outgoing_payment");
611
612 result
613 }
614}
615
616pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;