payment.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. //! CDK Mint Lightning
  2. use std::convert::Infallible;
  3. use std::pin::Pin;
  4. use async_trait::async_trait;
  5. use cashu::util::hex;
  6. use cashu::{Bolt11Invoice, MeltOptions};
  7. #[cfg(feature = "prometheus")]
  8. use cdk_prometheus::METRICS;
  9. use futures::Stream;
  10. use lightning::offers::offer::Offer;
  11. use lightning_invoice::ParseOrSemanticError;
  12. use serde::{Deserialize, Serialize};
  13. use serde_json::Value;
  14. use thiserror::Error;
  15. use crate::mint::MeltPaymentRequest;
  16. use crate::nuts::{CurrencyUnit, MeltQuoteState};
  17. use crate::Amount;
  18. /// CDK Lightning Error
  19. #[derive(Debug, Error)]
  20. pub enum Error {
  21. /// Invoice already paid
  22. #[error("Invoice already paid")]
  23. InvoiceAlreadyPaid,
  24. /// Invoice pay pending
  25. #[error("Invoice pay is pending")]
  26. InvoicePaymentPending,
  27. /// Unsupported unit
  28. #[error("Unsupported unit")]
  29. UnsupportedUnit,
  30. /// Unsupported payment option
  31. #[error("Unsupported payment option")]
  32. UnsupportedPaymentOption,
  33. /// Payment state is unknown
  34. #[error("Payment state is unknown")]
  35. UnknownPaymentState,
  36. /// Amount mismatch
  37. #[error("Amount is not what is expected")]
  38. AmountMismatch,
  39. /// Lightning Error
  40. #[error(transparent)]
  41. Lightning(Box<dyn std::error::Error + Send + Sync>),
  42. /// Serde Error
  43. #[error(transparent)]
  44. Serde(#[from] serde_json::Error),
  45. /// AnyHow Error
  46. #[error(transparent)]
  47. Anyhow(#[from] anyhow::Error),
  48. /// Parse Error
  49. #[error(transparent)]
  50. Parse(#[from] ParseOrSemanticError),
  51. /// Amount Error
  52. #[error(transparent)]
  53. Amount(#[from] crate::amount::Error),
  54. /// NUT04 Error
  55. #[error(transparent)]
  56. NUT04(#[from] crate::nuts::nut04::Error),
  57. /// NUT05 Error
  58. #[error(transparent)]
  59. NUT05(#[from] crate::nuts::nut05::Error),
  60. /// NUT23 Error
  61. #[error(transparent)]
  62. NUT23(#[from] crate::nuts::nut23::Error),
  63. /// Hex error
  64. #[error("Hex error")]
  65. Hex(#[from] hex::Error),
  66. /// Invalid hash
  67. #[error("Invalid hash")]
  68. InvalidHash,
  69. /// Custom
  70. #[error("`{0}`")]
  71. Custom(String),
  72. }
  73. impl From<Infallible> for Error {
  74. fn from(_: Infallible) -> Self {
  75. unreachable!("Infallible cannot be constructed")
  76. }
  77. }
  78. /// Payment identifier types
  79. #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
  80. #[serde(tag = "type", content = "value")]
  81. pub enum PaymentIdentifier {
  82. /// Label identifier
  83. Label(String),
  84. /// Offer ID identifier
  85. OfferId(String),
  86. /// Payment hash identifier
  87. PaymentHash([u8; 32]),
  88. /// Bolt12 payment hash
  89. Bolt12PaymentHash([u8; 32]),
  90. /// Payment id
  91. PaymentId([u8; 32]),
  92. /// Custom Payment ID
  93. CustomId(String),
  94. }
  95. impl PaymentIdentifier {
  96. /// Create new [`PaymentIdentifier`]
  97. pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
  98. match kind.to_lowercase().as_str() {
  99. "label" => Ok(Self::Label(identifier.to_string())),
  100. "offer_id" => Ok(Self::OfferId(identifier.to_string())),
  101. "payment_hash" => Ok(Self::PaymentHash(
  102. hex::decode(identifier)?
  103. .try_into()
  104. .map_err(|_| Error::InvalidHash)?,
  105. )),
  106. "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
  107. hex::decode(identifier)?
  108. .try_into()
  109. .map_err(|_| Error::InvalidHash)?,
  110. )),
  111. "custom" => Ok(Self::CustomId(identifier.to_string())),
  112. "payment_id" => Ok(Self::PaymentId(
  113. hex::decode(identifier)?
  114. .try_into()
  115. .map_err(|_| Error::InvalidHash)?,
  116. )),
  117. _ => Err(Error::UnsupportedPaymentOption),
  118. }
  119. }
  120. /// Payment id kind
  121. pub fn kind(&self) -> String {
  122. match self {
  123. Self::Label(_) => "label".to_string(),
  124. Self::OfferId(_) => "offer_id".to_string(),
  125. Self::PaymentHash(_) => "payment_hash".to_string(),
  126. Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
  127. Self::PaymentId(_) => "payment_id".to_string(),
  128. Self::CustomId(_) => "custom".to_string(),
  129. }
  130. }
  131. }
  132. impl std::fmt::Display for PaymentIdentifier {
  133. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  134. match self {
  135. Self::Label(l) => write!(f, "{l}"),
  136. Self::OfferId(o) => write!(f, "{o}"),
  137. Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
  138. Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
  139. Self::PaymentId(h) => write!(f, "{}", hex::encode(h)),
  140. Self::CustomId(c) => write!(f, "{c}"),
  141. }
  142. }
  143. }
  144. /// Options for creating a BOLT11 incoming payment request
  145. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
  146. pub struct Bolt11IncomingPaymentOptions {
  147. /// Optional description for the payment request
  148. pub description: Option<String>,
  149. /// Amount for the payment request in sats
  150. pub amount: Amount,
  151. /// Optional expiry time as Unix timestamp in seconds
  152. pub unix_expiry: Option<u64>,
  153. }
  154. /// Options for creating a BOLT12 incoming payment request
  155. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
  156. pub struct Bolt12IncomingPaymentOptions {
  157. /// Optional description for the payment request
  158. pub description: Option<String>,
  159. /// Optional amount for the payment request in sats
  160. pub amount: Option<Amount>,
  161. /// Optional expiry time as Unix timestamp in seconds
  162. pub unix_expiry: Option<u64>,
  163. }
  164. /// Options for creating an incoming payment request
  165. #[derive(Debug, Clone, PartialEq, Eq, Hash)]
  166. pub enum IncomingPaymentOptions {
  167. /// BOLT11 payment request options
  168. Bolt11(Bolt11IncomingPaymentOptions),
  169. /// BOLT12 payment request options
  170. Bolt12(Box<Bolt12IncomingPaymentOptions>),
  171. }
  172. /// Options for BOLT11 outgoing payments
  173. #[derive(Debug, Clone, PartialEq, Eq, Hash)]
  174. pub struct Bolt11OutgoingPaymentOptions {
  175. /// Bolt11
  176. pub bolt11: Bolt11Invoice,
  177. /// Maximum fee amount allowed for the payment
  178. pub max_fee_amount: Option<Amount>,
  179. /// Optional timeout in seconds
  180. pub timeout_secs: Option<u64>,
  181. /// Melt options
  182. pub melt_options: Option<MeltOptions>,
  183. }
  184. /// Options for BOLT12 outgoing payments
  185. #[derive(Debug, Clone, PartialEq, Eq, Hash)]
  186. pub struct Bolt12OutgoingPaymentOptions {
  187. /// Offer
  188. pub offer: Offer,
  189. /// Maximum fee amount allowed for the payment
  190. pub max_fee_amount: Option<Amount>,
  191. /// Optional timeout in seconds
  192. pub timeout_secs: Option<u64>,
  193. /// Melt options
  194. pub melt_options: Option<MeltOptions>,
  195. }
  196. /// Options for creating an outgoing payment
  197. #[derive(Debug, Clone, PartialEq, Eq, Hash)]
  198. pub enum OutgoingPaymentOptions {
  199. /// BOLT11 payment options
  200. Bolt11(Box<Bolt11OutgoingPaymentOptions>),
  201. /// BOLT12 payment options
  202. Bolt12(Box<Bolt12OutgoingPaymentOptions>),
  203. }
  204. impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
  205. type Error = Error;
  206. fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
  207. match melt_quote.request {
  208. MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
  209. Bolt11OutgoingPaymentOptions {
  210. max_fee_amount: Some(melt_quote.fee_reserve),
  211. timeout_secs: None,
  212. bolt11,
  213. melt_options: melt_quote.options,
  214. },
  215. ))),
  216. MeltPaymentRequest::Bolt12 { offer } => {
  217. let melt_options = match melt_quote.options {
  218. None => None,
  219. Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
  220. Some(options) => Some(options),
  221. };
  222. Ok(OutgoingPaymentOptions::Bolt12(Box::new(
  223. Bolt12OutgoingPaymentOptions {
  224. max_fee_amount: Some(melt_quote.fee_reserve),
  225. timeout_secs: None,
  226. offer: *offer,
  227. melt_options,
  228. },
  229. )))
  230. }
  231. }
  232. }
  233. }
  234. /// Mint payment trait
  235. #[async_trait]
  236. pub trait MintPayment {
  237. /// Mint Lightning Error
  238. type Err: Into<Error> + From<Error>;
  239. /// Start the payment processor
  240. /// Called when the mint starts up to initialize the payment processor
  241. async fn start(&self) -> Result<(), Self::Err> {
  242. // Default implementation - do nothing
  243. Ok(())
  244. }
  245. /// Stop the payment processor
  246. /// Called when the mint shuts down to gracefully stop the payment processor
  247. async fn stop(&self) -> Result<(), Self::Err> {
  248. // Default implementation - do nothing
  249. Ok(())
  250. }
  251. /// Base Settings
  252. async fn get_settings(&self) -> Result<serde_json::Value, Self::Err>;
  253. /// Create a new invoice
  254. async fn create_incoming_payment_request(
  255. &self,
  256. unit: &CurrencyUnit,
  257. options: IncomingPaymentOptions,
  258. ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
  259. /// Get payment quote
  260. /// Used to get fee and amount required for a payment request
  261. async fn get_payment_quote(
  262. &self,
  263. unit: &CurrencyUnit,
  264. options: OutgoingPaymentOptions,
  265. ) -> Result<PaymentQuoteResponse, Self::Err>;
  266. /// Pay request
  267. async fn make_payment(
  268. &self,
  269. unit: &CurrencyUnit,
  270. options: OutgoingPaymentOptions,
  271. ) -> Result<MakePaymentResponse, Self::Err>;
  272. /// Listen for invoices to be paid to the mint
  273. /// Returns a stream of request_lookup_id once invoices are paid
  274. async fn wait_payment_event(
  275. &self,
  276. ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
  277. /// Is wait invoice active
  278. fn is_wait_invoice_active(&self) -> bool;
  279. /// Cancel wait invoice
  280. fn cancel_wait_invoice(&self);
  281. /// Check the status of an incoming payment
  282. async fn check_incoming_payment_status(
  283. &self,
  284. payment_identifier: &PaymentIdentifier,
  285. ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
  286. /// Check the status of an outgoing payment
  287. async fn check_outgoing_payment(
  288. &self,
  289. payment_identifier: &PaymentIdentifier,
  290. ) -> Result<MakePaymentResponse, Self::Err>;
  291. }
  292. /// An event emitted which should be handled by the mint
  293. #[derive(Debug, Clone, Hash)]
  294. pub enum Event {
  295. /// A payment has been received.
  296. PaymentReceived(WaitPaymentResponse),
  297. }
  298. impl Default for Event {
  299. fn default() -> Self {
  300. // We use this as a sentinel value for no-op events
  301. // The actual processing will filter these out
  302. Event::PaymentReceived(WaitPaymentResponse {
  303. payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
  304. payment_amount: Amount::from(0),
  305. unit: CurrencyUnit::Msat,
  306. payment_id: "default".to_string(),
  307. })
  308. }
  309. }
  310. /// Wait any invoice response
  311. #[derive(Debug, Clone, Hash, Serialize, Deserialize)]
  312. pub struct WaitPaymentResponse {
  313. /// Request look up id
  314. /// Id that relates the quote and payment request
  315. pub payment_identifier: PaymentIdentifier,
  316. /// Payment amount
  317. pub payment_amount: Amount,
  318. /// Unit
  319. pub unit: CurrencyUnit,
  320. /// Unique id of payment
  321. // Payment hash
  322. pub payment_id: String,
  323. }
  324. /// Create incoming payment response
  325. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  326. pub struct CreateIncomingPaymentResponse {
  327. /// Id that is used to look up the payment from the ln backend
  328. pub request_lookup_id: PaymentIdentifier,
  329. /// Payment request
  330. pub request: String,
  331. /// Unix Expiry of Invoice
  332. pub expiry: Option<u64>,
  333. }
  334. /// Payment response
  335. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  336. pub struct MakePaymentResponse {
  337. /// Payment hash
  338. pub payment_lookup_id: PaymentIdentifier,
  339. /// Payment proof
  340. pub payment_proof: Option<String>,
  341. /// Status
  342. pub status: MeltQuoteState,
  343. /// Total Amount Spent
  344. pub total_spent: Amount,
  345. /// Unit of total spent
  346. pub unit: CurrencyUnit,
  347. }
  348. /// Payment quote response
  349. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  350. pub struct PaymentQuoteResponse {
  351. /// Request look up id
  352. pub request_lookup_id: Option<PaymentIdentifier>,
  353. /// Amount
  354. pub amount: Amount,
  355. /// Fee required for melt
  356. pub fee: Amount,
  357. /// Currency unit of `amount` and `fee`
  358. pub unit: CurrencyUnit,
  359. /// Status
  360. pub state: MeltQuoteState,
  361. }
  362. /// Ln backend settings
  363. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  364. pub struct Bolt11Settings {
  365. /// MPP supported
  366. pub mpp: bool,
  367. /// Base unit of backend
  368. pub unit: CurrencyUnit,
  369. /// Invoice Description supported
  370. pub invoice_description: bool,
  371. /// Paying amountless invoices supported
  372. pub amountless: bool,
  373. /// Bolt12 supported
  374. pub bolt12: bool,
  375. }
  376. impl TryFrom<Bolt11Settings> for Value {
  377. type Error = crate::error::Error;
  378. fn try_from(value: Bolt11Settings) -> Result<Self, Self::Error> {
  379. serde_json::to_value(value).map_err(|err| err.into())
  380. }
  381. }
  382. impl TryFrom<Value> for Bolt11Settings {
  383. type Error = crate::error::Error;
  384. fn try_from(value: Value) -> Result<Self, Self::Error> {
  385. serde_json::from_value(value).map_err(|err| err.into())
  386. }
  387. }
  388. /// Metrics wrapper for MintPayment implementations
  389. ///
  390. /// This wrapper implements the Decorator pattern to collect metrics on all
  391. /// MintPayment trait methods. It wraps any existing MintPayment implementation
  392. /// and automatically records timing and operation metrics.
  393. #[derive(Clone)]
  394. #[cfg(feature = "prometheus")]
  395. pub struct MetricsMintPayment<T> {
  396. inner: T,
  397. }
  398. #[cfg(feature = "prometheus")]
  399. impl<T> MetricsMintPayment<T>
  400. where
  401. T: MintPayment,
  402. {
  403. /// Create a new metrics wrapper around a MintPayment implementation
  404. pub fn new(inner: T) -> Self {
  405. Self { inner }
  406. }
  407. /// Get reference to the underlying implementation
  408. pub fn inner(&self) -> &T {
  409. &self.inner
  410. }
  411. /// Consume the wrapper and return the inner implementation
  412. pub fn into_inner(self) -> T {
  413. self.inner
  414. }
  415. }
  416. #[async_trait]
  417. #[cfg(feature = "prometheus")]
  418. impl<T> MintPayment for MetricsMintPayment<T>
  419. where
  420. T: MintPayment + Send + Sync,
  421. {
  422. type Err = T::Err;
  423. async fn get_settings(&self) -> Result<serde_json::Value, Self::Err> {
  424. let start = std::time::Instant::now();
  425. METRICS.inc_in_flight_requests("get_settings");
  426. let result = self.inner.get_settings().await;
  427. let duration = start.elapsed().as_secs_f64();
  428. METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
  429. METRICS.dec_in_flight_requests("get_settings");
  430. result
  431. }
  432. async fn create_incoming_payment_request(
  433. &self,
  434. unit: &CurrencyUnit,
  435. options: IncomingPaymentOptions,
  436. ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
  437. let start = std::time::Instant::now();
  438. METRICS.inc_in_flight_requests("create_incoming_payment_request");
  439. let result = self
  440. .inner
  441. .create_incoming_payment_request(unit, options)
  442. .await;
  443. let duration = start.elapsed().as_secs_f64();
  444. METRICS.record_mint_operation_histogram(
  445. "create_incoming_payment_request",
  446. result.is_ok(),
  447. duration,
  448. );
  449. METRICS.dec_in_flight_requests("create_incoming_payment_request");
  450. result
  451. }
  452. async fn get_payment_quote(
  453. &self,
  454. unit: &CurrencyUnit,
  455. options: OutgoingPaymentOptions,
  456. ) -> Result<PaymentQuoteResponse, Self::Err> {
  457. let start = std::time::Instant::now();
  458. METRICS.inc_in_flight_requests("get_payment_quote");
  459. let result = self.inner.get_payment_quote(unit, options).await;
  460. let duration = start.elapsed().as_secs_f64();
  461. let success = result.is_ok();
  462. if let Ok(ref quote) = result {
  463. let amount: f64 = u64::from(quote.amount) as f64;
  464. let fee: f64 = u64::from(quote.fee) as f64;
  465. METRICS.record_lightning_payment(amount, fee);
  466. }
  467. METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
  468. METRICS.dec_in_flight_requests("get_payment_quote");
  469. result
  470. }
  471. async fn wait_payment_event(
  472. &self,
  473. ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
  474. let start = std::time::Instant::now();
  475. METRICS.inc_in_flight_requests("wait_payment_event");
  476. let result = self.inner.wait_payment_event().await;
  477. let duration = start.elapsed().as_secs_f64();
  478. let success = result.is_ok();
  479. METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
  480. METRICS.dec_in_flight_requests("wait_payment_event");
  481. result
  482. }
  483. async fn make_payment(
  484. &self,
  485. unit: &CurrencyUnit,
  486. options: OutgoingPaymentOptions,
  487. ) -> Result<MakePaymentResponse, Self::Err> {
  488. let start = std::time::Instant::now();
  489. METRICS.inc_in_flight_requests("make_payment");
  490. let result = self.inner.make_payment(unit, options).await;
  491. let duration = start.elapsed().as_secs_f64();
  492. let success = result.is_ok();
  493. METRICS.record_mint_operation_histogram("make_payment", success, duration);
  494. METRICS.dec_in_flight_requests("make_payment");
  495. result
  496. }
  497. fn is_wait_invoice_active(&self) -> bool {
  498. self.inner.is_wait_invoice_active()
  499. }
  500. fn cancel_wait_invoice(&self) {
  501. self.inner.cancel_wait_invoice()
  502. }
  503. async fn check_incoming_payment_status(
  504. &self,
  505. payment_identifier: &PaymentIdentifier,
  506. ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
  507. let start = std::time::Instant::now();
  508. METRICS.inc_in_flight_requests("check_incoming_payment_status");
  509. let result = self
  510. .inner
  511. .check_incoming_payment_status(payment_identifier)
  512. .await;
  513. let duration = start.elapsed().as_secs_f64();
  514. METRICS.record_mint_operation_histogram(
  515. "check_incoming_payment_status",
  516. result.is_ok(),
  517. duration,
  518. );
  519. METRICS.dec_in_flight_requests("check_incoming_payment_status");
  520. result
  521. }
  522. async fn check_outgoing_payment(
  523. &self,
  524. payment_identifier: &PaymentIdentifier,
  525. ) -> Result<MakePaymentResponse, Self::Err> {
  526. let start = std::time::Instant::now();
  527. METRICS.inc_in_flight_requests("check_outgoing_payment");
  528. let result = self.inner.check_outgoing_payment(payment_identifier).await;
  529. let duration = start.elapsed().as_secs_f64();
  530. let success = result.is_ok();
  531. METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
  532. METRICS.dec_in_flight_requests("check_outgoing_payment");
  533. result
  534. }
  535. }
  536. /// Type alias for Mint Payment trait
  537. pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;