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(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
154impl std::fmt::Debug for PaymentIdentifier {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 PaymentIdentifier::PaymentHash(h) => write!(f, "PaymentHash({})", hex::encode(h)),
158 PaymentIdentifier::Bolt12PaymentHash(h) => {
159 write!(f, "Bolt12PaymentHash({})", hex::encode(h))
160 }
161 PaymentIdentifier::PaymentId(h) => write!(f, "PaymentId({})", hex::encode(h)),
162 PaymentIdentifier::Label(s) => write!(f, "Label({})", s),
163 PaymentIdentifier::OfferId(s) => write!(f, "OfferId({})", s),
164 PaymentIdentifier::CustomId(s) => write!(f, "CustomId({})", s),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
171pub struct Bolt11IncomingPaymentOptions {
172 pub description: Option<String>,
174 pub amount: Amount,
176 pub unix_expiry: Option<u64>,
178}
179
180#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
182pub struct Bolt12IncomingPaymentOptions {
183 pub description: Option<String>,
185 pub amount: Option<Amount>,
187 pub unix_expiry: Option<u64>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Hash)]
193pub struct CustomIncomingPaymentOptions {
194 pub method: String,
196 pub description: Option<String>,
198 pub amount: Amount,
200 pub unix_expiry: Option<u64>,
202 pub extra_json: Option<String>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Hash)]
211pub enum IncomingPaymentOptions {
212 Bolt11(Bolt11IncomingPaymentOptions),
214 Bolt12(Box<Bolt12IncomingPaymentOptions>),
216 Custom(Box<CustomIncomingPaymentOptions>),
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222pub struct Bolt11OutgoingPaymentOptions {
223 pub bolt11: Bolt11Invoice,
225 pub max_fee_amount: Option<Amount>,
227 pub timeout_secs: Option<u64>,
229 pub melt_options: Option<MeltOptions>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
235pub struct Bolt12OutgoingPaymentOptions {
236 pub offer: Offer,
238 pub max_fee_amount: Option<Amount>,
240 pub timeout_secs: Option<u64>,
242 pub melt_options: Option<MeltOptions>,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
248pub struct CustomOutgoingPaymentOptions {
249 pub method: String,
251 pub request: String,
253 pub max_fee_amount: Option<Amount>,
255 pub timeout_secs: Option<u64>,
257 pub melt_options: Option<MeltOptions>,
259 pub extra_json: Option<String>,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Hash)]
268pub enum OutgoingPaymentOptions {
269 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
271 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
273 Custom(Box<CustomOutgoingPaymentOptions>),
275}
276
277impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
278 type Error = Error;
279
280 fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
281 let fee_reserve = melt_quote.fee_reserve();
282 match &melt_quote.request {
283 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
284 Bolt11OutgoingPaymentOptions {
285 max_fee_amount: Some(fee_reserve.to_owned().into()),
286 timeout_secs: None,
287 bolt11: bolt11.clone(),
288 melt_options: melt_quote.options,
289 },
290 ))),
291 MeltPaymentRequest::Bolt12 { offer } => {
292 let melt_options = match melt_quote.options {
293 None => None,
294 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
295 Some(options) => Some(options),
296 };
297
298 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
299 Bolt12OutgoingPaymentOptions {
300 max_fee_amount: Some(fee_reserve.clone().into()),
301 timeout_secs: None,
302 offer: *offer.clone(),
303 melt_options,
304 },
305 )))
306 }
307 MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
308 Box::new(CustomOutgoingPaymentOptions {
309 method: method.to_string(),
310 request: request.to_string(),
311 max_fee_amount: Some(melt_quote.fee_reserve().into()),
312 timeout_secs: None,
313 melt_options: melt_quote.options,
314 extra_json: None,
315 }),
316 )),
317 }
318 }
319}
320
321#[async_trait]
323pub trait MintPayment {
324 type Err: Into<Error> + From<Error>;
326
327 async fn start(&self) -> Result<(), Self::Err> {
330 Ok(())
332 }
333
334 async fn stop(&self) -> Result<(), Self::Err> {
337 Ok(())
339 }
340
341 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
343
344 async fn create_incoming_payment_request(
346 &self,
347 unit: &CurrencyUnit,
348 options: IncomingPaymentOptions,
349 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
350
351 async fn get_payment_quote(
354 &self,
355 unit: &CurrencyUnit,
356 options: OutgoingPaymentOptions,
357 ) -> Result<PaymentQuoteResponse, Self::Err>;
358
359 async fn make_payment(
361 &self,
362 unit: &CurrencyUnit,
363 options: OutgoingPaymentOptions,
364 ) -> Result<MakePaymentResponse, Self::Err>;
365
366 async fn wait_payment_event(
369 &self,
370 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
371
372 fn is_wait_invoice_active(&self) -> bool;
374
375 fn cancel_wait_invoice(&self);
377
378 async fn check_incoming_payment_status(
380 &self,
381 payment_identifier: &PaymentIdentifier,
382 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
383
384 async fn check_outgoing_payment(
386 &self,
387 payment_identifier: &PaymentIdentifier,
388 ) -> Result<MakePaymentResponse, Self::Err>;
389}
390
391#[derive(Debug, Clone, Hash)]
393pub enum Event {
394 PaymentReceived(WaitPaymentResponse),
396}
397
398impl Default for Event {
399 fn default() -> Self {
400 Event::PaymentReceived(WaitPaymentResponse {
403 payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
404 payment_amount: Amount::new(0, CurrencyUnit::Msat),
405 payment_id: "default".to_string(),
406 })
407 }
408}
409
410#[derive(Debug, Clone, Hash)]
412pub struct WaitPaymentResponse {
413 pub payment_identifier: PaymentIdentifier,
416 pub payment_amount: Amount<CurrencyUnit>,
418 pub payment_id: String,
421}
422
423impl WaitPaymentResponse {
424 pub fn unit(&self) -> &CurrencyUnit {
426 self.payment_amount.unit()
427 }
428}
429
430#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
432pub struct CreateIncomingPaymentResponse {
433 pub request_lookup_id: PaymentIdentifier,
435 pub request: String,
437 pub expiry: Option<u64>,
439 #[serde(flatten, default)]
444 pub extra_json: Option<serde_json::Value>,
445}
446
447#[derive(Debug, Clone, Hash, PartialEq, Eq)]
449pub struct MakePaymentResponse {
450 pub payment_lookup_id: PaymentIdentifier,
452 pub payment_proof: Option<String>,
454 pub status: MeltQuoteState,
456 pub total_spent: Amount<CurrencyUnit>,
458}
459
460impl MakePaymentResponse {
461 pub fn unit(&self) -> &CurrencyUnit {
463 self.total_spent.unit()
464 }
465}
466
467#[derive(Debug, Clone, Hash, PartialEq, Eq)]
469pub struct PaymentQuoteResponse {
470 pub request_lookup_id: Option<PaymentIdentifier>,
472 pub amount: Amount<CurrencyUnit>,
474 pub fee: Amount<CurrencyUnit>,
476 pub state: MeltQuoteState,
478}
479
480impl PaymentQuoteResponse {
481 pub fn unit(&self) -> &CurrencyUnit {
483 self.amount.unit()
484 }
485}
486
487#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
489pub struct Bolt11Settings {
490 pub mpp: bool,
492 pub amountless: bool,
494 pub invoice_description: bool,
496}
497
498#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
500pub struct Bolt12Settings {
501 pub amountless: bool,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
508pub struct SettingsResponse {
509 pub unit: String,
511 pub bolt11: Option<Bolt11Settings>,
513 pub bolt12: Option<Bolt12Settings>,
515 #[serde(default)]
517 pub custom: std::collections::HashMap<String, String>,
518}
519
520impl From<SettingsResponse> for Value {
521 fn from(value: SettingsResponse) -> Self {
522 serde_json::to_value(value).unwrap_or(Value::Null)
523 }
524}
525
526impl TryFrom<Value> for SettingsResponse {
527 type Error = crate::error::Error;
528
529 fn try_from(value: Value) -> Result<Self, Self::Error> {
530 serde_json::from_value(value).map_err(|err| err.into())
531 }
532}
533
534#[derive(Debug, Clone)]
540#[cfg(feature = "prometheus")]
541pub struct MetricsMintPayment<T> {
542 inner: T,
543}
544#[cfg(feature = "prometheus")]
545impl<T> MetricsMintPayment<T>
546where
547 T: MintPayment,
548{
549 pub fn new(inner: T) -> Self {
551 Self { inner }
552 }
553
554 pub fn inner(&self) -> &T {
556 &self.inner
557 }
558
559 pub fn into_inner(self) -> T {
561 self.inner
562 }
563}
564
565#[async_trait]
566#[cfg(feature = "prometheus")]
567impl<T> MintPayment for MetricsMintPayment<T>
568where
569 T: MintPayment + Send + Sync,
570{
571 type Err = T::Err;
572
573 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
574 let start = std::time::Instant::now();
575 METRICS.inc_in_flight_requests("get_settings");
576
577 let result = self.inner.get_settings().await;
578
579 let duration = start.elapsed().as_secs_f64();
580 METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
581 METRICS.dec_in_flight_requests("get_settings");
582
583 result
584 }
585
586 async fn create_incoming_payment_request(
587 &self,
588 unit: &CurrencyUnit,
589 options: IncomingPaymentOptions,
590 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
591 let start = std::time::Instant::now();
592 METRICS.inc_in_flight_requests("create_incoming_payment_request");
593
594 let result = self
595 .inner
596 .create_incoming_payment_request(unit, options)
597 .await;
598
599 let duration = start.elapsed().as_secs_f64();
600 METRICS.record_mint_operation_histogram(
601 "create_incoming_payment_request",
602 result.is_ok(),
603 duration,
604 );
605 METRICS.dec_in_flight_requests("create_incoming_payment_request");
606
607 result
608 }
609
610 async fn get_payment_quote(
611 &self,
612 unit: &CurrencyUnit,
613 options: OutgoingPaymentOptions,
614 ) -> Result<PaymentQuoteResponse, Self::Err> {
615 let start = std::time::Instant::now();
616 METRICS.inc_in_flight_requests("get_payment_quote");
617
618 let result = self.inner.get_payment_quote(unit, options).await;
619
620 let duration = start.elapsed().as_secs_f64();
621 let success = result.is_ok();
622
623 if let Ok(ref quote) = result {
624 let amount: f64 = quote.amount.value() as f64;
625 let fee: f64 = quote.fee.value() as f64;
626 METRICS.record_lightning_payment(amount, fee);
627 }
628
629 METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
630 METRICS.dec_in_flight_requests("get_payment_quote");
631
632 result
633 }
634 async fn wait_payment_event(
635 &self,
636 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
637 let start = std::time::Instant::now();
638 METRICS.inc_in_flight_requests("wait_payment_event");
639
640 let result = self.inner.wait_payment_event().await;
641
642 let duration = start.elapsed().as_secs_f64();
643 let success = result.is_ok();
644
645 METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
646 METRICS.dec_in_flight_requests("wait_payment_event");
647
648 result
649 }
650
651 async fn make_payment(
652 &self,
653 unit: &CurrencyUnit,
654 options: OutgoingPaymentOptions,
655 ) -> Result<MakePaymentResponse, Self::Err> {
656 let start = std::time::Instant::now();
657 METRICS.inc_in_flight_requests("make_payment");
658
659 let result = self.inner.make_payment(unit, options).await;
660
661 let duration = start.elapsed().as_secs_f64();
662 let success = result.is_ok();
663
664 METRICS.record_mint_operation_histogram("make_payment", success, duration);
665 METRICS.dec_in_flight_requests("make_payment");
666
667 result
668 }
669
670 fn is_wait_invoice_active(&self) -> bool {
671 self.inner.is_wait_invoice_active()
672 }
673
674 fn cancel_wait_invoice(&self) {
675 self.inner.cancel_wait_invoice()
676 }
677
678 async fn check_incoming_payment_status(
679 &self,
680 payment_identifier: &PaymentIdentifier,
681 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
682 let start = std::time::Instant::now();
683 METRICS.inc_in_flight_requests("check_incoming_payment_status");
684
685 let result = self
686 .inner
687 .check_incoming_payment_status(payment_identifier)
688 .await;
689
690 let duration = start.elapsed().as_secs_f64();
691 METRICS.record_mint_operation_histogram(
692 "check_incoming_payment_status",
693 result.is_ok(),
694 duration,
695 );
696 METRICS.dec_in_flight_requests("check_incoming_payment_status");
697
698 result
699 }
700
701 async fn check_outgoing_payment(
702 &self,
703 payment_identifier: &PaymentIdentifier,
704 ) -> Result<MakePaymentResponse, Self::Err> {
705 let start = std::time::Instant::now();
706 METRICS.inc_in_flight_requests("check_outgoing_payment");
707
708 let result = self.inner.check_outgoing_payment(payment_identifier).await;
709
710 let duration = start.elapsed().as_secs_f64();
711 let success = result.is_ok();
712
713 METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
714 METRICS.dec_in_flight_requests("check_outgoing_payment");
715
716 result
717 }
718}
719
720pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;