Преглед изворни кода

feat: mmw pay payment request (#1434)

tsk пре 3 недеља
родитељ
комит
acf525746a

+ 38 - 0
crates/cdk-ffi/src/multi_mint_wallet.rs

@@ -684,6 +684,44 @@ impl MultiMintWallet {
 /// Payment request methods for MultiMintWallet
 #[uniffi::export(async_runtime = "tokio")]
 impl MultiMintWallet {
+    /// Pay a NUT-18 PaymentRequest
+    ///
+    /// This method handles paying a payment request by selecting an appropriate mint:
+    /// - If `mint_url` is provided, it verifies the payment request accepts that mint
+    ///   and uses it to pay.
+    /// - If `mint_url` is None, it automatically selects the mint that:
+    ///   1. Is accepted by the payment request (matches one of the request's mints, or request accepts any mint)
+    ///   2. Has the highest balance among matching mints
+    ///
+    /// # Arguments
+    ///
+    /// * `payment_request` - The NUT-18 payment request to pay
+    /// * `mint_url` - Optional specific mint to use. If None, automatically selects the best matching mint.
+    /// * `custom_amount` - Custom amount to pay (required if payment request has no amount)
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if:
+    /// - The payment request has no amount and no custom amount is provided
+    /// - The specified mint is not accepted by the payment request
+    /// - No matching mint has sufficient balance
+    /// - No transport is available in the payment request
+    pub async fn pay_request(
+        &self,
+        payment_request: Arc<PaymentRequest>,
+        mint_url: Option<MintUrl>,
+        custom_amount: Option<Amount>,
+    ) -> Result<(), FfiError> {
+        let cdk_mint_url = mint_url.map(|url| url.try_into()).transpose()?;
+        let cdk_amount = custom_amount.map(Into::into);
+
+        self.inner
+            .pay_request(payment_request.inner().clone(), cdk_mint_url, cdk_amount)
+            .await?;
+
+        Ok(())
+    }
+
     /// Create a NUT-18 payment request
     ///
     /// Creates a payment request that can be shared to receive Cashu tokens.

+ 3 - 0
crates/cdk/src/wallet/mod.rs

@@ -72,6 +72,9 @@ pub use mint_connector::transport::Transport as HttpTransport;
 pub use mint_connector::AuthHttpClient;
 pub use mint_connector::{HttpClient, LnurlPayInvoiceResponse, LnurlPayResponse, MintConnector};
 pub use multi_mint_wallet::{MultiMintReceiveOptions, MultiMintSendOptions, MultiMintWallet};
+pub use payment_request::CreateRequestParams;
+#[cfg(feature = "nostr")]
+pub use payment_request::NostrWaitInfo;
 pub use receive::ReceiveOptions;
 pub use send::{PreparedSend, SendMemo, SendOptions};
 pub use types::{MeltQuote, MintQuote, SendKind};

+ 93 - 0
crates/cdk/src/wallet/payment_request.rs

@@ -18,6 +18,7 @@ use nostr_sdk::{Client as NostrClient, EventBuilder, FromBech32, Keys, ToBech32}
 use reqwest::Client;
 
 use crate::error::Error;
+use crate::mint_url::MintUrl;
 use crate::nuts::nut11::{Conditions, SigFlag, SpendingConditions};
 use crate::nuts::nut18::Nut10SecretRequest;
 use crate::nuts::{CurrencyUnit, Transport};
@@ -222,6 +223,98 @@ pub struct NostrWaitInfo {
 }
 
 impl MultiMintWallet {
+    /// Pay a NUT-18 PaymentRequest using the MultiMintWallet.
+    ///
+    /// This method handles paying a payment request by selecting an appropriate mint:
+    /// - If `mint_url` is provided, it verifies the payment request accepts that mint
+    ///   and uses it to pay.
+    /// - If `mint_url` is None, it automatically selects the mint that:
+    ///   1. Is accepted by the payment request (matches one of the request's mints, or request accepts any mint)
+    ///   2. Has the highest balance among matching mints
+    ///
+    /// # Arguments
+    ///
+    /// * `payment_request` - The NUT-18 payment request to pay
+    /// * `mint_url` - Optional specific mint to use. If None, automatically selects the best matching mint.
+    /// * `custom_amount` - Custom amount to pay (required if payment request has no amount)
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if:
+    /// - The payment request has no amount and no custom amount is provided
+    /// - The specified mint is not accepted by the payment request
+    /// - No matching mint has sufficient balance
+    /// - No transport is available in the payment request
+    pub async fn pay_request(
+        &self,
+        payment_request: PaymentRequest,
+        mint_url: Option<MintUrl>,
+        custom_amount: Option<Amount>,
+    ) -> Result<(), Error> {
+        let amount = match payment_request.amount {
+            Some(amount) => amount,
+            None => match custom_amount {
+                Some(a) => a,
+                None => return Err(Error::AmountUndefined),
+            },
+        };
+
+        // Get the list of mints accepted by the payment request (None means any mint is accepted)
+        let accepted_mints = payment_request.mints.as_ref();
+
+        // Select the wallet to use for payment
+        let selected_wallet = if let Some(specified_mint) = &mint_url {
+            // User specified a mint - verify it's accepted by the payment request
+            if let Some(accepted) = accepted_mints {
+                if !accepted.contains(specified_mint) {
+                    return Err(Error::Custom(format!(
+                        "Mint {} is not accepted by this payment request. Accepted mints: {:?}",
+                        specified_mint, accepted
+                    )));
+                }
+            }
+
+            // Get the wallet for the specified mint
+            self.get_wallet(specified_mint)
+                .await
+                .ok_or_else(|| Error::UnknownMint {
+                    mint_url: specified_mint.to_string(),
+                })?
+        } else {
+            // No mint specified - find the best matching mint with highest balance
+            let balances = self.get_balances().await?;
+            let mut best_wallet: Option<Wallet> = None;
+            let mut best_balance = Amount::ZERO;
+
+            for (mint_url, balance) in balances.iter() {
+                // Check if this mint is accepted by the payment request
+                let is_accepted = match accepted_mints {
+                    Some(accepted) => accepted.contains(mint_url),
+                    None => true, // No mints specified means any mint is accepted
+                };
+
+                if !is_accepted {
+                    continue;
+                }
+
+                // Check balance meets requirements and is best so far
+                if *balance >= amount && *balance > best_balance {
+                    if let Some(wallet) = self.get_wallet(mint_url).await {
+                        best_balance = *balance;
+                        best_wallet = Some(wallet);
+                    }
+                }
+            }
+
+            best_wallet.ok_or(Error::InsufficientFunds)?
+        };
+
+        // Use the selected wallet to pay the request
+        selected_wallet
+            .pay_request(payment_request, custom_amount)
+            .await
+    }
+
     /// Derive enforceable NUT-10 spending conditions from high-level request params.
     ///
     /// Why: