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, MeltQuote};
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, PartialEq, Eq, Hash)]
171pub struct Bolt11IncomingPaymentOptions {
172 pub description: Option<String>,
174 pub amount: Amount<CurrencyUnit>,
176 pub unix_expiry: Option<u64>,
178}
179
180impl Default for Bolt11IncomingPaymentOptions {
181 fn default() -> Self {
182 Self {
183 description: None,
184 amount: Amount::new(0, CurrencyUnit::Sat),
185 unix_expiry: None,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
192pub struct Bolt12IncomingPaymentOptions {
193 pub description: Option<String>,
195 pub amount: Option<Amount<CurrencyUnit>>,
197 pub unix_expiry: Option<u64>,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Hash)]
203pub struct CustomIncomingPaymentOptions {
204 pub method: String,
206 pub description: Option<String>,
208 pub amount: Amount<CurrencyUnit>,
210 pub unix_expiry: Option<u64>,
212 pub extra_json: Option<String>,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221pub enum IncomingPaymentOptions {
222 Bolt11(Bolt11IncomingPaymentOptions),
224 Bolt12(Box<Bolt12IncomingPaymentOptions>),
226 Custom(Box<CustomIncomingPaymentOptions>),
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232pub struct Bolt11OutgoingPaymentOptions {
233 pub bolt11: Bolt11Invoice,
235 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
237 pub timeout_secs: Option<u64>,
239 pub melt_options: Option<MeltOptions>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Hash)]
245pub struct Bolt12OutgoingPaymentOptions {
246 pub offer: Offer,
248 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
250 pub timeout_secs: Option<u64>,
252 pub melt_options: Option<MeltOptions>,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Hash)]
258pub struct CustomOutgoingPaymentOptions {
259 pub method: String,
261 pub request: String,
263 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
265 pub timeout_secs: Option<u64>,
267 pub melt_options: Option<MeltOptions>,
269 pub extra_json: Option<String>,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash)]
278pub enum OutgoingPaymentOptions {
279 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
281 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
283 Custom(Box<CustomOutgoingPaymentOptions>),
285}
286
287impl OutgoingPaymentOptions {
288 pub fn from_melt_quote_with_fee(
290 melt_quote: MeltQuote,
291 ) -> Result<OutgoingPaymentOptions, Error> {
292 let fee_reserve = melt_quote.fee_reserve();
293 match &melt_quote.request {
294 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
295 Bolt11OutgoingPaymentOptions {
296 max_fee_amount: Some(fee_reserve),
297 timeout_secs: None,
298 bolt11: bolt11.clone(),
299 melt_options: melt_quote.options,
300 },
301 ))),
302 MeltPaymentRequest::Bolt12 { offer } => {
303 let melt_options = match melt_quote.options {
304 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
305 Some(options) => Some(options),
306 _ => None,
307 };
308
309 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
310 Bolt12OutgoingPaymentOptions {
311 max_fee_amount: Some(fee_reserve),
312 timeout_secs: None,
313 offer: *offer.clone(),
314 melt_options,
315 },
316 )))
317 }
318 MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
319 Box::new(CustomOutgoingPaymentOptions {
320 method: method.to_string(),
321 request: request.to_string(),
322 max_fee_amount: Some(fee_reserve),
323 timeout_secs: None,
324 melt_options: melt_quote.options,
325 extra_json: None,
326 }),
327 )),
328 }
329 }
330}
331
332#[async_trait]
334pub trait MintPayment {
335 type Err: Into<Error> + From<Error>;
337
338 async fn start(&self) -> Result<(), Self::Err> {
341 Ok(())
343 }
344
345 async fn stop(&self) -> Result<(), Self::Err> {
348 Ok(())
350 }
351
352 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
354
355 async fn create_incoming_payment_request(
357 &self,
358 options: IncomingPaymentOptions,
359 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
360
361 async fn get_payment_quote(
364 &self,
365 unit: &CurrencyUnit,
366 options: OutgoingPaymentOptions,
367 ) -> Result<PaymentQuoteResponse, Self::Err>;
368
369 async fn make_payment(
371 &self,
372 unit: &CurrencyUnit,
373 options: OutgoingPaymentOptions,
374 ) -> Result<MakePaymentResponse, Self::Err>;
375
376 async fn wait_payment_event(
379 &self,
380 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
381
382 fn is_wait_invoice_active(&self) -> bool;
384
385 fn cancel_wait_invoice(&self);
387
388 async fn check_incoming_payment_status(
390 &self,
391 payment_identifier: &PaymentIdentifier,
392 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
393
394 async fn check_outgoing_payment(
396 &self,
397 payment_identifier: &PaymentIdentifier,
398 ) -> Result<MakePaymentResponse, Self::Err>;
399}
400
401#[derive(Debug, Clone, Hash)]
403pub enum Event {
404 PaymentReceived(WaitPaymentResponse),
406}
407
408impl Default for Event {
409 fn default() -> Self {
410 Event::PaymentReceived(WaitPaymentResponse {
413 payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
414 payment_amount: Amount::new(0, CurrencyUnit::Msat),
415 payment_id: "default".to_string(),
416 })
417 }
418}
419
420#[derive(Debug, Clone, Hash)]
422pub struct WaitPaymentResponse {
423 pub payment_identifier: PaymentIdentifier,
426 pub payment_amount: Amount<CurrencyUnit>,
428 pub payment_id: String,
431}
432
433impl WaitPaymentResponse {
434 pub fn unit(&self) -> &CurrencyUnit {
436 self.payment_amount.unit()
437 }
438}
439
440#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
442pub struct CreateIncomingPaymentResponse {
443 pub request_lookup_id: PaymentIdentifier,
445 pub request: String,
447 pub expiry: Option<u64>,
449 #[serde(flatten, default)]
454 pub extra_json: Option<serde_json::Value>,
455}
456
457#[derive(Debug, Clone, Hash, PartialEq, Eq)]
459pub struct MakePaymentResponse {
460 pub payment_lookup_id: PaymentIdentifier,
462 pub payment_proof: Option<String>,
464 pub status: MeltQuoteState,
466 pub total_spent: Amount<CurrencyUnit>,
468}
469
470impl MakePaymentResponse {
471 pub fn unit(&self) -> &CurrencyUnit {
473 self.total_spent.unit()
474 }
475}
476
477#[derive(Debug, Clone, Hash, PartialEq, Eq)]
479pub struct PaymentQuoteResponse {
480 pub request_lookup_id: Option<PaymentIdentifier>,
482 pub amount: Amount<CurrencyUnit>,
484 pub fee: Amount<CurrencyUnit>,
486 pub state: MeltQuoteState,
488}
489
490impl PaymentQuoteResponse {
491 pub fn unit(&self) -> &CurrencyUnit {
493 self.amount.unit()
494 }
495}
496
497#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
499pub struct Bolt11Settings {
500 pub mpp: bool,
502 pub amountless: bool,
504 pub invoice_description: bool,
506}
507
508#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
510pub struct Bolt12Settings {
511 pub amountless: bool,
513}
514
515#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
518pub struct SettingsResponse {
519 pub unit: String,
521 pub bolt11: Option<Bolt11Settings>,
523 pub bolt12: Option<Bolt12Settings>,
525 #[serde(default)]
527 pub custom: std::collections::HashMap<String, String>,
528}
529
530impl From<SettingsResponse> for Value {
531 fn from(value: SettingsResponse) -> Self {
532 serde_json::to_value(value).unwrap_or(Value::Null)
533 }
534}
535
536impl TryFrom<Value> for SettingsResponse {
537 type Error = crate::error::Error;
538
539 fn try_from(value: Value) -> Result<Self, Self::Error> {
540 serde_json::from_value(value).map_err(|err| err.into())
541 }
542}
543
544#[derive(Debug, Clone)]
550#[cfg(feature = "prometheus")]
551pub struct MetricsMintPayment<T> {
552 inner: T,
553}
554#[cfg(feature = "prometheus")]
555impl<T> MetricsMintPayment<T>
556where
557 T: MintPayment,
558{
559 pub fn new(inner: T) -> Self {
561 Self { inner }
562 }
563
564 pub fn inner(&self) -> &T {
566 &self.inner
567 }
568
569 pub fn into_inner(self) -> T {
571 self.inner
572 }
573}
574
575#[async_trait]
576#[cfg(feature = "prometheus")]
577impl<T> MintPayment for MetricsMintPayment<T>
578where
579 T: MintPayment + Send + Sync,
580{
581 type Err = T::Err;
582
583 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
584 let start = std::time::Instant::now();
585 METRICS.inc_in_flight_requests("get_settings");
586
587 let result = self.inner.get_settings().await;
588
589 let duration = start.elapsed().as_secs_f64();
590 METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
591 METRICS.dec_in_flight_requests("get_settings");
592
593 result
594 }
595
596 async fn create_incoming_payment_request(
597 &self,
598 options: IncomingPaymentOptions,
599 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
600 let start = std::time::Instant::now();
601 METRICS.inc_in_flight_requests("create_incoming_payment_request");
602
603 let result = self.inner.create_incoming_payment_request(options).await;
604
605 let duration = start.elapsed().as_secs_f64();
606 METRICS.record_mint_operation_histogram(
607 "create_incoming_payment_request",
608 result.is_ok(),
609 duration,
610 );
611 METRICS.dec_in_flight_requests("create_incoming_payment_request");
612
613 result
614 }
615
616 async fn get_payment_quote(
617 &self,
618 unit: &CurrencyUnit,
619 options: OutgoingPaymentOptions,
620 ) -> Result<PaymentQuoteResponse, Self::Err> {
621 let start = std::time::Instant::now();
622 METRICS.inc_in_flight_requests("get_payment_quote");
623
624 let result = self.inner.get_payment_quote(unit, options).await;
625
626 let duration = start.elapsed().as_secs_f64();
627 let success = result.is_ok();
628
629 if let Ok(ref quote) = result {
630 let amount: f64 = quote.amount.value() as f64;
631 let fee: f64 = quote.fee.value() as f64;
632 METRICS.record_lightning_payment(amount, fee);
633 }
634
635 METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
636 METRICS.dec_in_flight_requests("get_payment_quote");
637
638 result
639 }
640 async fn wait_payment_event(
641 &self,
642 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
643 let start = std::time::Instant::now();
644 METRICS.inc_in_flight_requests("wait_payment_event");
645
646 let result = self.inner.wait_payment_event().await;
647
648 let duration = start.elapsed().as_secs_f64();
649 let success = result.is_ok();
650
651 METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
652 METRICS.dec_in_flight_requests("wait_payment_event");
653
654 result
655 }
656
657 async fn make_payment(
658 &self,
659 unit: &CurrencyUnit,
660 options: OutgoingPaymentOptions,
661 ) -> Result<MakePaymentResponse, Self::Err> {
662 let start = std::time::Instant::now();
663 METRICS.inc_in_flight_requests("make_payment");
664
665 let result = self.inner.make_payment(unit, options).await;
666
667 let duration = start.elapsed().as_secs_f64();
668 let success = result.is_ok();
669
670 METRICS.record_mint_operation_histogram("make_payment", success, duration);
671 METRICS.dec_in_flight_requests("make_payment");
672
673 result
674 }
675
676 fn is_wait_invoice_active(&self) -> bool {
677 self.inner.is_wait_invoice_active()
678 }
679
680 fn cancel_wait_invoice(&self) {
681 self.inner.cancel_wait_invoice()
682 }
683
684 async fn check_incoming_payment_status(
685 &self,
686 payment_identifier: &PaymentIdentifier,
687 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
688 let start = std::time::Instant::now();
689 METRICS.inc_in_flight_requests("check_incoming_payment_status");
690
691 let result = self
692 .inner
693 .check_incoming_payment_status(payment_identifier)
694 .await;
695
696 let duration = start.elapsed().as_secs_f64();
697 METRICS.record_mint_operation_histogram(
698 "check_incoming_payment_status",
699 result.is_ok(),
700 duration,
701 );
702 METRICS.dec_in_flight_requests("check_incoming_payment_status");
703
704 result
705 }
706
707 async fn check_outgoing_payment(
708 &self,
709 payment_identifier: &PaymentIdentifier,
710 ) -> Result<MakePaymentResponse, Self::Err> {
711 let start = std::time::Instant::now();
712 METRICS.inc_in_flight_requests("check_outgoing_payment");
713
714 let result = self.inner.check_outgoing_payment(payment_identifier).await;
715
716 let duration = start.elapsed().as_secs_f64();
717 let success = result.is_ok();
718
719 METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
720 METRICS.dec_in_flight_requests("check_outgoing_payment");
721
722 result
723 }
724}
725
726pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;