123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- //! CDK Mint Lightning
- use std::convert::Infallible;
- use std::pin::Pin;
- use async_trait::async_trait;
- use cashu::util::hex;
- use cashu::{Bolt11Invoice, MeltOptions};
- use futures::Stream;
- use lightning::offers::offer::Offer;
- use lightning_invoice::ParseOrSemanticError;
- use serde::{Deserialize, Serialize};
- use serde_json::Value;
- use thiserror::Error;
- use crate::mint::MeltPaymentRequest;
- use crate::nuts::{CurrencyUnit, MeltQuoteState};
- use crate::Amount;
- /// CDK Lightning Error
- #[derive(Debug, Error)]
- pub enum Error {
- /// Invoice already paid
- #[error("Invoice already paid")]
- InvoiceAlreadyPaid,
- /// Invoice pay pending
- #[error("Invoice pay is pending")]
- InvoicePaymentPending,
- /// Unsupported unit
- #[error("Unsupported unit")]
- UnsupportedUnit,
- /// Unsupported payment option
- #[error("Unsupported payment option")]
- UnsupportedPaymentOption,
- /// Payment state is unknown
- #[error("Payment state is unknown")]
- UnknownPaymentState,
- /// Amount mismatch
- #[error("Amount is not what is expected")]
- AmountMismatch,
- /// Lightning Error
- #[error(transparent)]
- Lightning(Box<dyn std::error::Error + Send + Sync>),
- /// Serde Error
- #[error(transparent)]
- Serde(#[from] serde_json::Error),
- /// AnyHow Error
- #[error(transparent)]
- Anyhow(#[from] anyhow::Error),
- /// Parse Error
- #[error(transparent)]
- Parse(#[from] ParseOrSemanticError),
- /// Amount Error
- #[error(transparent)]
- Amount(#[from] crate::amount::Error),
- /// NUT04 Error
- #[error(transparent)]
- NUT04(#[from] crate::nuts::nut04::Error),
- /// NUT05 Error
- #[error(transparent)]
- NUT05(#[from] crate::nuts::nut05::Error),
- /// NUT23 Error
- #[error(transparent)]
- NUT23(#[from] crate::nuts::nut23::Error),
- /// Hex error
- #[error("Hex error")]
- Hex(#[from] hex::Error),
- /// Invalid hash
- #[error("Invalid hash")]
- InvalidHash,
- /// Custom
- #[error("`{0}`")]
- Custom(String),
- }
- impl From<Infallible> for Error {
- fn from(_: Infallible) -> Self {
- unreachable!("Infallible cannot be constructed")
- }
- }
- /// Payment identifier types
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
- #[serde(tag = "type", content = "value")]
- pub enum PaymentIdentifier {
- /// Label identifier
- Label(String),
- /// Offer ID identifier
- OfferId(String),
- /// Payment hash identifier
- PaymentHash([u8; 32]),
- /// Bolt12 payment hash
- Bolt12PaymentHash([u8; 32]),
- /// Custom Payment ID
- CustomId(String),
- }
- impl PaymentIdentifier {
- /// Create new [`PaymentIdentifier`]
- pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
- match kind.to_lowercase().as_str() {
- "label" => Ok(Self::Label(identifier.to_string())),
- "offer_id" => Ok(Self::OfferId(identifier.to_string())),
- "payment_hash" => Ok(Self::PaymentHash(
- hex::decode(identifier)?
- .try_into()
- .map_err(|_| Error::InvalidHash)?,
- )),
- "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
- hex::decode(identifier)?
- .try_into()
- .map_err(|_| Error::InvalidHash)?,
- )),
- "custom" => Ok(Self::CustomId(identifier.to_string())),
- _ => Err(Error::UnsupportedPaymentOption),
- }
- }
- /// Payment id kind
- pub fn kind(&self) -> String {
- match self {
- Self::Label(_) => "label".to_string(),
- Self::OfferId(_) => "offer_id".to_string(),
- Self::PaymentHash(_) => "payment_hash".to_string(),
- Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
- Self::CustomId(_) => "custom".to_string(),
- }
- }
- }
- impl std::fmt::Display for PaymentIdentifier {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Label(l) => write!(f, "{l}"),
- Self::OfferId(o) => write!(f, "{o}"),
- Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
- Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
- Self::CustomId(c) => write!(f, "{c}"),
- }
- }
- }
- /// Options for creating a BOLT11 incoming payment request
- #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
- pub struct Bolt11IncomingPaymentOptions {
- /// Optional description for the payment request
- pub description: Option<String>,
- /// Amount for the payment request in sats
- pub amount: Amount,
- /// Optional expiry time as Unix timestamp in seconds
- pub unix_expiry: Option<u64>,
- }
- /// Options for creating a BOLT12 incoming payment request
- #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
- pub struct Bolt12IncomingPaymentOptions {
- /// Optional description for the payment request
- pub description: Option<String>,
- /// Optional amount for the payment request in sats
- pub amount: Option<Amount>,
- /// Optional expiry time as Unix timestamp in seconds
- pub unix_expiry: Option<u64>,
- }
- /// Options for creating an incoming payment request
- #[derive(Debug, Clone, PartialEq, Eq, Hash)]
- pub enum IncomingPaymentOptions {
- /// BOLT11 payment request options
- Bolt11(Bolt11IncomingPaymentOptions),
- /// BOLT12 payment request options
- Bolt12(Box<Bolt12IncomingPaymentOptions>),
- }
- /// Options for BOLT11 outgoing payments
- #[derive(Debug, Clone, PartialEq, Eq, Hash)]
- pub struct Bolt11OutgoingPaymentOptions {
- /// Bolt11
- pub bolt11: Bolt11Invoice,
- /// Maximum fee amount allowed for the payment
- pub max_fee_amount: Option<Amount>,
- /// Optional timeout in seconds
- pub timeout_secs: Option<u64>,
- /// Melt options
- pub melt_options: Option<MeltOptions>,
- }
- /// Options for BOLT12 outgoing payments
- #[derive(Debug, Clone, PartialEq, Eq, Hash)]
- pub struct Bolt12OutgoingPaymentOptions {
- /// Offer
- pub offer: Offer,
- /// Maximum fee amount allowed for the payment
- pub max_fee_amount: Option<Amount>,
- /// Optional timeout in seconds
- pub timeout_secs: Option<u64>,
- /// Bolt12 Invoice
- pub invoice: Option<Vec<u8>>,
- /// Melt options
- pub melt_options: Option<MeltOptions>,
- }
- /// Options for creating an outgoing payment
- #[derive(Debug, Clone, PartialEq, Eq, Hash)]
- pub enum OutgoingPaymentOptions {
- /// BOLT11 payment options
- Bolt11(Box<Bolt11OutgoingPaymentOptions>),
- /// BOLT12 payment options
- Bolt12(Box<Bolt12OutgoingPaymentOptions>),
- }
- impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
- type Error = Error;
- fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
- match melt_quote.request {
- MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
- Bolt11OutgoingPaymentOptions {
- max_fee_amount: Some(melt_quote.fee_reserve),
- timeout_secs: None,
- bolt11,
- melt_options: melt_quote.options,
- },
- ))),
- MeltPaymentRequest::Bolt12 { offer, invoice } => {
- let melt_options = match melt_quote.options {
- None => None,
- Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
- Some(options) => Some(options),
- };
- Ok(OutgoingPaymentOptions::Bolt12(Box::new(
- Bolt12OutgoingPaymentOptions {
- max_fee_amount: Some(melt_quote.fee_reserve),
- timeout_secs: None,
- offer: *offer,
- invoice,
- melt_options,
- },
- )))
- }
- }
- }
- }
- /// Mint payment trait
- #[async_trait]
- pub trait MintPayment {
- /// Mint Lightning Error
- type Err: Into<Error> + From<Error>;
- /// Base Settings
- async fn get_settings(&self) -> Result<serde_json::Value, Self::Err>;
- /// Create a new invoice
- async fn create_incoming_payment_request(
- &self,
- unit: &CurrencyUnit,
- options: IncomingPaymentOptions,
- ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
- /// Get payment quote
- /// Used to get fee and amount required for a payment request
- async fn get_payment_quote(
- &self,
- unit: &CurrencyUnit,
- options: OutgoingPaymentOptions,
- ) -> Result<PaymentQuoteResponse, Self::Err>;
- /// Pay request
- async fn make_payment(
- &self,
- unit: &CurrencyUnit,
- options: OutgoingPaymentOptions,
- ) -> Result<MakePaymentResponse, Self::Err>;
- /// Listen for invoices to be paid to the mint
- /// Returns a stream of request_lookup_id once invoices are paid
- async fn wait_any_incoming_payment(
- &self,
- ) -> Result<Pin<Box<dyn Stream<Item = WaitPaymentResponse> + Send>>, Self::Err>;
- /// Is wait invoice active
- fn is_wait_invoice_active(&self) -> bool;
- /// Cancel wait invoice
- fn cancel_wait_invoice(&self);
- /// Check the status of an incoming payment
- async fn check_incoming_payment_status(
- &self,
- payment_identifier: &PaymentIdentifier,
- ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
- /// Check the status of an outgoing payment
- async fn check_outgoing_payment(
- &self,
- payment_identifier: &PaymentIdentifier,
- ) -> Result<MakePaymentResponse, Self::Err>;
- }
- /// Wait any invoice response
- #[derive(Debug, Clone, Hash, Serialize, Deserialize)]
- pub struct WaitPaymentResponse {
- /// Request look up id
- /// Id that relates the quote and payment request
- pub payment_identifier: PaymentIdentifier,
- /// Payment amount
- pub payment_amount: Amount,
- /// Unit
- pub unit: CurrencyUnit,
- /// Unique id of payment
- // Payment hash
- pub payment_id: String,
- }
- /// Create incoming payment response
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
- pub struct CreateIncomingPaymentResponse {
- /// Id that is used to look up the payment from the ln backend
- pub request_lookup_id: PaymentIdentifier,
- /// Payment request
- pub request: String,
- /// Unix Expiry of Invoice
- pub expiry: Option<u64>,
- }
- /// Payment response
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
- pub struct MakePaymentResponse {
- /// Payment hash
- pub payment_lookup_id: PaymentIdentifier,
- /// Payment proof
- pub payment_proof: Option<String>,
- /// Status
- pub status: MeltQuoteState,
- /// Total Amount Spent
- pub total_spent: Amount,
- /// Unit of total spent
- pub unit: CurrencyUnit,
- }
- /// Payment quote response
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
- pub struct PaymentQuoteResponse {
- /// Request look up id
- pub request_lookup_id: PaymentIdentifier,
- /// Amount
- pub amount: Amount,
- /// Fee required for melt
- pub fee: Amount,
- /// Currency unit of `amount` and `fee`
- pub unit: CurrencyUnit,
- /// Status
- pub state: MeltQuoteState,
- /// Payment Quote Options
- pub options: Option<PaymentQuoteOptions>,
- }
- /// Payment quote options
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
- pub enum PaymentQuoteOptions {
- /// Bolt12 payment options
- Bolt12 {
- /// Bolt12 invoice
- invoice: Option<Vec<u8>>,
- },
- }
- /// Ln backend settings
- #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
- pub struct Bolt11Settings {
- /// MPP supported
- pub mpp: bool,
- /// Base unit of backend
- pub unit: CurrencyUnit,
- /// Invoice Description supported
- pub invoice_description: bool,
- /// Paying amountless invoices supported
- pub amountless: bool,
- /// Bolt12 supported
- pub bolt12: bool,
- }
- impl TryFrom<Bolt11Settings> for Value {
- type Error = crate::error::Error;
- fn try_from(value: Bolt11Settings) -> Result<Self, Self::Error> {
- serde_json::to_value(value).map_err(|err| err.into())
- }
- }
- impl TryFrom<Value> for Bolt11Settings {
- type Error = crate::error::Error;
- fn try_from(value: Value) -> Result<Self, Self::Error> {
- serde_json::from_value(value).map_err(|err| err.into())
- }
- }
|