Эх сурвалжийг харах

Wallet custom followup (#1527)

* feat: custum mint and melt

* feat: combine mint and melt in connector
tsk 1 долоо хоног өмнө
parent
commit
92eed0fbc6

+ 7 - 10
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -109,7 +109,11 @@ impl MintConnector for DirectMintConnection {
             .map(Into::into)
     }
 
-    async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error> {
+    async fn post_mint(
+        &self,
+        _method: &PaymentMethod,
+        request: MintRequest<String>,
+    ) -> Result<MintResponse, Error> {
         let request_id: MintRequest<QuoteId> = request.try_into().unwrap();
         self.mint.process_mint_request(request_id).await
     }
@@ -136,6 +140,7 @@ impl MintConnector for DirectMintConnection {
 
     async fn post_melt(
         &self,
+        _method: &PaymentMethod,
         request: MeltRequest<String>,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         let request_uuid = request.try_into().unwrap();
@@ -215,19 +220,11 @@ impl MintConnector for DirectMintConnection {
             .await
             .map(Into::into)
     }
-    /// Melt [NUT-23]
-    async fn post_melt_bolt12(
-        &self,
-        _request: MeltRequest<String>,
-    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        // Implementation to be added later
-        Err(Error::UnsupportedPaymentMethod)
-    }
 
     /// Mint Quote for Custom Payment Method
     async fn post_mint_custom_quote(
         &self,
-        _method: &str,
+        _method: &PaymentMethod,
         _request: MintQuoteCustomRequest,
     ) -> Result<MintQuoteCustomResponse<String>, Error> {
         // Custom payment methods not implemented in test mock

+ 10 - 2
crates/cdk-integration-tests/tests/bolt12.rs

@@ -6,8 +6,11 @@ use std::sync::Arc;
 use anyhow::{bail, Result};
 use bip39::Mnemonic;
 use cashu::amount::SplitTarget;
+use cashu::nut00::KnownMethod;
 use cashu::nut23::Amountless;
-use cashu::{Amount, CurrencyUnit, MintRequest, MintUrl, PreMintSecrets, ProofsMethods};
+use cashu::{
+    Amount, CurrencyUnit, MintRequest, MintUrl, PaymentMethod, PreMintSecrets, ProofsMethods,
+};
 use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletBuilder};
 use cdk_integration_tests::get_mint_url_from_env;
 use cdk_integration_tests::init_regtest::{get_cln_dir, get_temp_dir};
@@ -384,7 +387,12 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
 
     let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
 
-    let response = http_client.post_mint(mint_request.clone()).await;
+    let response = http_client
+        .post_mint(
+            &PaymentMethod::Known(KnownMethod::Bolt11),
+            mint_request.clone(),
+        )
+        .await;
 
     match response {
         Err(err) => match err {

+ 9 - 5
crates/cdk-integration-tests/tests/fake_auth.rs

@@ -6,11 +6,11 @@ use bip39::Mnemonic;
 use cashu::{MintAuthRequest, MintInfo};
 use cdk::amount::{Amount, SplitTarget};
 use cdk::mint_url::MintUrl;
-use cdk::nuts::nut00::ProofsMethods;
+use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
 use cdk::nuts::{
     AuthProof, AuthToken, BlindAuthToken, CheckStateRequest, CurrencyUnit, MeltQuoteBolt11Request,
-    MeltQuoteState, MeltRequest, MintQuoteBolt11Request, MintRequest, RestoreRequest, State,
-    SwapRequest,
+    MeltQuoteState, MeltRequest, MintQuoteBolt11Request, MintRequest, PaymentMethod,
+    RestoreRequest, State, SwapRequest,
 };
 use cdk::wallet::{AuthHttpClient, AuthMintConnector, HttpClient, MintConnector, WalletBuilder};
 use cdk::{Error, OidcClient};
@@ -115,7 +115,9 @@ async fn test_mint_without_auth() {
             signature: None,
         };
 
-        let mint_res = client.post_mint(request).await;
+        let mint_res = client
+            .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
+            .await;
 
         assert!(
             matches!(mint_res, Err(Error::BlindAuthRequired)),
@@ -213,7 +215,9 @@ async fn test_melt_without_auth() {
             None,
         );
 
-        let melt_res = client.post_melt(request).await;
+        let melt_res = client
+            .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), request)
+            .await;
 
         assert!(
             matches!(melt_res, Err(Error::BlindAuthRequired)),

+ 49 - 12
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -20,10 +20,10 @@ use std::time::Duration;
 use bip39::Mnemonic;
 use cashu::Amount;
 use cdk::amount::SplitTarget;
-use cdk::nuts::nut00::ProofsMethods;
+use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
 use cdk::nuts::{
-    CurrencyUnit, MeltQuoteState, MeltRequest, MintRequest, PreMintSecrets, Proofs, SecretKey,
-    State, SwapRequest,
+    CurrencyUnit, MeltQuoteState, MeltRequest, MintRequest, PaymentMethod, PreMintSecrets, Proofs,
+    SecretKey, State, SwapRequest,
 };
 use cdk::wallet::types::TransactionDirection;
 use cdk::wallet::{HttpClient, MintConnector, Wallet};
@@ -433,7 +433,10 @@ async fn test_fake_melt_change_in_quote() {
         Some(premint_secrets.blinded_messages()),
     );
 
-    let melt_response = client.post_melt(melt_request).await.unwrap();
+    let melt_response = client
+        .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), melt_request)
+        .await
+        .unwrap();
 
     assert!(melt_response.change.is_some());
 
@@ -514,7 +517,9 @@ async fn test_fake_mint_without_witness() {
         signature: None,
     };
 
-    let response = http_client.post_mint(request.clone()).await;
+    let response = http_client
+        .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
+        .await;
 
     match response {
         Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
@@ -570,7 +575,9 @@ async fn test_fake_mint_with_wrong_witness() {
         .sign(secret_key)
         .expect("failed to sign the mint request");
 
-    let response = http_client.post_mint(request.clone()).await;
+    let response = http_client
+        .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
+        .await;
 
     match response {
         Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
@@ -632,7 +639,12 @@ async fn test_fake_mint_inflated() {
     }
     let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
 
-    let response = http_client.post_mint(mint_request.clone()).await;
+    let response = http_client
+        .post_mint(
+            &PaymentMethod::Known(KnownMethod::Bolt11),
+            mint_request.clone(),
+        )
+        .await;
 
     match response {
         Err(err) => match err {
@@ -725,7 +737,12 @@ async fn test_fake_mint_multiple_units() {
     }
     let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
 
-    let response = http_client.post_mint(mint_request.clone()).await;
+    let response = http_client
+        .post_mint(
+            &PaymentMethod::Known(KnownMethod::Bolt11),
+            mint_request.clone(),
+        )
+        .await;
 
     match response {
         Err(err) => match err {
@@ -928,7 +945,12 @@ async fn test_fake_mint_multiple_unit_melt() {
         let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
 
         let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
-        let response = http_client.post_melt(melt_request.clone()).await;
+        let response = http_client
+            .post_melt(
+                &PaymentMethod::Known(KnownMethod::Bolt11),
+                melt_request.clone(),
+            )
+            .await;
 
         match response {
             Err(err) => match err {
@@ -978,7 +1000,12 @@ async fn test_fake_mint_multiple_unit_melt() {
 
         let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
 
-        let response = http_client.post_melt(melt_request.clone()).await;
+        let response = http_client
+            .post_melt(
+                &PaymentMethod::Known(KnownMethod::Bolt11),
+                melt_request.clone(),
+            )
+            .await;
 
         match response {
             Err(err) => match err {
@@ -1258,7 +1285,12 @@ async fn test_fake_mint_melt_spend_after_fail() {
     let melt_request = MeltRequest::new(melt_quote.id, proofs, None);
 
     let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
-    let response = http_client.post_melt(melt_request.clone()).await;
+    let response = http_client
+        .post_melt(
+            &PaymentMethod::Known(KnownMethod::Bolt11),
+            melt_request.clone(),
+        )
+        .await;
 
     match response {
         Err(err) => match err {
@@ -1385,7 +1417,12 @@ async fn test_fake_mint_duplicate_proofs_melt() {
     let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
 
     let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
-    let response = http_client.post_melt(melt_request.clone()).await;
+    let response = http_client
+        .post_melt(
+            &PaymentMethod::Known(KnownMethod::Bolt11),
+            melt_request.clone(),
+        )
+        .await;
 
     match response {
         Err(err) => match err {

+ 6 - 3
crates/cdk-integration-tests/tests/happy_path_mint_wallet.rs

@@ -21,8 +21,8 @@ use bip39::Mnemonic;
 use cashu::{MeltRequest, PreMintSecrets};
 use cdk::amount::{Amount, SplitTarget};
 use cdk::mint_url::MintUrl;
-use cdk::nuts::nut00::ProofsMethods;
-use cdk::nuts::{CurrencyUnit, MeltQuoteState, NotificationPayload, State};
+use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
+use cdk::nuts::{CurrencyUnit, MeltQuoteState, NotificationPayload, PaymentMethod, State};
 use cdk::wallet::{HttpClient, MintConnector, MultiMintWallet, Wallet};
 use cdk_integration_tests::{create_invoice_for_env, get_mint_url_from_env, pay_if_regtest};
 use cdk_sqlite::wallet::memory;
@@ -818,7 +818,10 @@ async fn test_fake_melt_change_in_quote() {
         Some(premint_secrets.blinded_messages()),
     );
 
-    let melt_response = client.post_melt(melt_request).await.unwrap();
+    let melt_response = client
+        .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), melt_request)
+        .await
+        .unwrap();
 
     assert!(melt_response.change.is_some());
 

+ 10 - 3
crates/cdk-integration-tests/tests/regtest.rs

@@ -19,9 +19,10 @@ use std::time::Duration;
 use bip39::Mnemonic;
 use cashu::ProofsMethods;
 use cdk::amount::{Amount, SplitTarget};
+use cdk::nuts::nut00::KnownMethod;
 use cdk::nuts::{
     CurrencyUnit, MeltOptions, MeltQuoteState, MintQuoteState, MintRequest, Mpp,
-    NotificationPayload, PreMintSecrets,
+    NotificationPayload, PaymentMethod, PreMintSecrets,
 };
 use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
 use cdk_integration_tests::{get_mint_url_from_env, get_second_mint_url_from_env, get_test_client};
@@ -342,8 +343,14 @@ async fn test_cached_mint() {
         .sign(secret_key.expect("Secret key on quote"))
         .unwrap();
 
-    let response = http_client.post_mint(request.clone()).await.unwrap();
-    let response1 = http_client.post_mint(request).await.unwrap();
+    let response = http_client
+        .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
+        .await
+        .unwrap();
+    let response1 = http_client
+        .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
+        .await
+        .unwrap();
 
     assert!(response == response1);
 }

+ 4 - 1
crates/cdk/src/wallet/issue/bolt11.rs

@@ -306,7 +306,10 @@ impl Wallet {
             request.sign(secret_key.clone())?;
         }
 
-        let mint_res = self.client.post_mint(request).await?;
+        let mint_res = self
+            .client
+            .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
+            .await?;
 
         let keys = self.load_keyset_keys(active_keyset_id).await?;
 

+ 4 - 1
crates/cdk/src/wallet/issue/bolt12.rs

@@ -182,7 +182,10 @@ impl Wallet {
             return Err(Error::SignatureMissingOrInvalid);
         }
 
-        let mint_res = self.client.post_mint(request).await?;
+        let mint_res = self
+            .client
+            .post_mint(&PaymentMethod::Known(KnownMethod::Bolt12), request)
+            .await?;
 
         let keys = self.load_keyset_keys(active_keyset_id).await?;
 

+ 9 - 2
crates/cdk/src/wallet/issue/custom.rs

@@ -22,7 +22,7 @@ impl Wallet {
     pub(super) async fn mint_quote_custom(
         &self,
         amount: Option<Amount>,
-        method: &str,
+        method: &PaymentMethod,
         description: Option<String>,
         extra: Option<String>,
     ) -> Result<MintQuote, Error> {
@@ -171,7 +171,14 @@ impl Wallet {
             request.sign(secret_key.clone())?;
         }
 
-        let mint_res = self.client.post_mint(request).await?;
+        if !quote_info.payment_method.is_custom() {
+            return Err(Error::UnsupportedPaymentMethod);
+        }
+
+        let mint_res = self
+            .client
+            .post_mint(&quote_info.payment_method, request)
+            .await?;
 
         let keys = self.load_keyset_keys(active_keyset_id).await?;
 

+ 2 - 2
crates/cdk/src/wallet/issue/mod.rs

@@ -30,8 +30,8 @@ impl Wallet {
                 // For bolt12, request is the offer string
                 self.mint_bolt12_quote(amount, description).await
             }
-            PaymentMethod::Custom(custom_method) => {
-                self.mint_quote_custom(amount, &custom_method, description, extra)
+            PaymentMethod::Custom(ref _custom_method) => {
+                self.mint_quote_custom(amount, &method, description, extra)
                     .await
             }
         }

+ 6 - 25
crates/cdk/src/wallet/melt/bolt11.rs

@@ -205,31 +205,12 @@ impl Wallet {
             Some(premint_secrets.blinded_messages()),
         );
 
-        let melt_response = match quote_info.payment_method {
-            cdk_common::PaymentMethod::Known(cdk_common::nut00::KnownMethod::Bolt11) => {
-                self.try_proof_operation_or_reclaim(
-                    request.inputs().clone(),
-                    self.client.post_melt(request),
-                )
-                .await?
-            }
-            cdk_common::PaymentMethod::Known(cdk_common::nut00::KnownMethod::Bolt12) => {
-                self.try_proof_operation_or_reclaim(
-                    request.inputs().clone(),
-                    self.client.post_melt_bolt12(request),
-                )
-                .await?
-            }
-            cdk_common::PaymentMethod::Custom(ref _method) => {
-                // For now, custom methods will use the same post_melt endpoint
-                // This will be enhanced when custom HTTP client methods are added
-                self.try_proof_operation_or_reclaim(
-                    request.inputs().clone(),
-                    self.client.post_melt(request),
-                )
-                .await?
-            }
-        };
+        let melt_response = self
+            .try_proof_operation_or_reclaim(
+                request.inputs().clone(),
+                self.client.post_melt(&quote_info.payment_method, request),
+            )
+            .await?;
 
         let active_keys = self.load_keyset_keys(active_keyset_id).await?;
 

+ 38 - 51
crates/cdk/src/wallet/mint_connector/http_client.rs

@@ -17,7 +17,6 @@ use web_time::{Duration, Instant};
 use super::transport::Transport;
 use super::{Error, MintConnector};
 use crate::mint_url::MintUrl;
-#[cfg(feature = "auth")]
 use crate::nuts::nut00::{KnownMethod, PaymentMethod};
 #[cfg(feature = "auth")]
 use crate::nuts::nut22::MintAuthRequest;
@@ -326,24 +325,31 @@ where
 
     /// Mint Tokens [NUT-04]
     #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
-    async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error> {
+    async fn post_mint(
+        &self,
+        method: &PaymentMethod,
+        request: MintRequest<String>,
+    ) -> Result<MintResponse, Error> {
         #[cfg(feature = "auth")]
         let auth_token = self
-            .get_auth_token(
-                Method::Post,
-                RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
-            )
+            .get_auth_token(Method::Post, RoutePath::Mint(method.to_string()))
             .await?;
 
         #[cfg(not(feature = "auth"))]
         let auth_token = None;
-        self.retriable_http_request(
-            nut19::Method::Post,
-            nut19::Path::Custom("/v1/mint/bolt11".to_string()),
-            auth_token,
-            &request,
-        )
-        .await
+
+        let path = match method {
+            PaymentMethod::Known(KnownMethod::Bolt11) => {
+                nut19::Path::Custom("/v1/mint/bolt11".to_string())
+            }
+            PaymentMethod::Known(KnownMethod::Bolt12) => {
+                nut19::Path::Custom("/v1/mint/bolt12".to_string())
+            }
+            PaymentMethod::Custom(m) => nut19::Path::custom_mint(m),
+        };
+
+        self.retriable_http_request(nut19::Method::Post, path, auth_token, &request)
+            .await
     }
 
     /// Melt Quote [NUT-05]
@@ -396,26 +402,29 @@ where
     #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
     async fn post_melt(
         &self,
+        method: &PaymentMethod,
         request: MeltRequest<String>,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
         #[cfg(feature = "auth")]
         let auth_token = self
-            .get_auth_token(
-                Method::Post,
-                RoutePath::Melt(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
-            )
+            .get_auth_token(Method::Post, RoutePath::Melt(method.to_string()))
             .await?;
 
         #[cfg(not(feature = "auth"))]
         let auth_token = None;
 
-        self.retriable_http_request(
-            nut19::Method::Post,
-            nut19::Path::Custom("/v1/melt/bolt11".to_string()),
-            auth_token,
-            &request,
-        )
-        .await
+        let path = match method {
+            PaymentMethod::Known(KnownMethod::Bolt11) => {
+                nut19::Path::Custom("/v1/melt/bolt11".to_string())
+            }
+            PaymentMethod::Known(KnownMethod::Bolt12) => {
+                nut19::Path::Custom("/v1/melt/bolt12".to_string())
+            }
+            PaymentMethod::Custom(m) => nut19::Path::custom_melt(m),
+        };
+
+        self.retriable_http_request(nut19::Method::Post, path, auth_token, &request)
+            .await
     }
 
     /// Swap Token [NUT-03]
@@ -591,39 +600,16 @@ where
         self.transport.http_get(url, auth_token).await
     }
 
-    /// Melt Bolt12 [NUT-23]
-    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
-    async fn post_melt_bolt12(
-        &self,
-        request: MeltRequest<String>,
-    ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        #[cfg(feature = "auth")]
-        let auth_token = self
-            .get_auth_token(
-                Method::Post,
-                RoutePath::Melt(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
-            )
-            .await?;
-
-        #[cfg(not(feature = "auth"))]
-        let auth_token = None;
-        self.retriable_http_request(
-            nut19::Method::Post,
-            nut19::Path::Custom("/v1/melt/bolt12".to_string()),
-            auth_token,
-            &request,
-        )
-        .await
-    }
-
     /// Mint Quote for Custom Payment Method
     #[instrument(skip(self), fields(mint_url = %self.mint_url))]
     async fn post_mint_custom_quote(
         &self,
-        method: &str,
+        method: &PaymentMethod,
         request: MintQuoteCustomRequest,
     ) -> Result<MintQuoteCustomResponse<String>, Error> {
-        let url = self.mint_url.join_paths(&["v1", "mint", "quote", method])?;
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "mint", "quote", &method.to_string()])?;
 
         #[cfg(feature = "auth")]
         let auth_token = self
@@ -659,6 +645,7 @@ where
 }
 
 /// Http Client
+
 #[derive(Debug, Clone)]
 #[cfg(feature = "auth")]
 pub struct AuthHttpClient<T>

+ 12 - 8
crates/cdk/src/wallet/mint_connector/mod.rs

@@ -12,7 +12,7 @@ use crate::nuts::{
     CheckStateRequest, CheckStateResponse, Id, KeySet, KeysetResponse, MeltQuoteBolt11Request,
     MeltQuoteBolt11Response, MeltQuoteCustomRequest, MeltRequest, MintInfo, MintQuoteBolt11Request,
     MintQuoteBolt11Response, MintQuoteCustomRequest, MintQuoteCustomResponse, MintRequest,
-    MintResponse, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
+    MintResponse, PaymentMethod, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
 };
 #[cfg(feature = "auth")]
 use crate::wallet::AuthWallet;
@@ -66,23 +66,32 @@ pub trait MintConnector: Debug {
         quote_id: &str,
     ) -> Result<MintQuoteBolt11Response<String>, Error>;
     /// Mint Tokens [NUT-04]
-    async fn post_mint(&self, request: MintRequest<String>) -> Result<MintResponse, Error>;
+    async fn post_mint(
+        &self,
+        method: &PaymentMethod,
+        request: MintRequest<String>,
+    ) -> Result<MintResponse, Error>;
+
     /// Melt Quote [NUT-05]
     async fn post_melt_quote(
         &self,
         request: MeltQuoteBolt11Request,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
+
     /// Melt Quote Status
     async fn get_melt_quote_status(
         &self,
         quote_id: &str,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
+
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
     async fn post_melt(
         &self,
+        method: &PaymentMethod,
         request: MeltRequest<String>,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
+
     /// Split Token [NUT-06]
     async fn post_swap(&self, request: SwapRequest) -> Result<SwapResponse, Error>;
     /// Get Mint Info [NUT-06]
@@ -122,16 +131,11 @@ pub trait MintConnector: Debug {
         &self,
         quote_id: &str,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
-    /// Melt [NUT-23]
-    async fn post_melt_bolt12(
-        &self,
-        request: MeltRequest<String>,
-    ) -> Result<MeltQuoteBolt11Response<String>, Error>;
 
     /// Mint Quote for Custom Payment Method
     async fn post_mint_custom_quote(
         &self,
-        method: &str,
+        method: &PaymentMethod,
         request: MintQuoteCustomRequest,
     ) -> Result<MintQuoteCustomResponse<String>, Error>;