Ver Fonte

Add support for pure integration tests (#458)

* Embed mint_url as a field of HttpClient

* Create pure integration tests

* DirectMintConnection: convert between String and Uuid
ok300 há 3 meses atrás
pai
commit
86c4c2dfeb

+ 1 - 0
Cargo.lock

@@ -780,6 +780,7 @@ name = "cdk-integration-tests"
 version = "0.5.0"
 dependencies = [
  "anyhow",
+ "async-trait",
  "axum",
  "bip39",
  "cdk",

+ 4 - 3
crates/cdk-cli/src/main.rs

@@ -144,16 +144,17 @@ async fn main() -> Result<()> {
 
     let mints = localstore.get_mints().await?;
 
-    for (mint, _) in mints {
+    for (mint_url, _) in mints {
         let mut wallet = Wallet::new(
-            &mint.to_string(),
+            &mint_url.to_string(),
             cdk::nuts::CurrencyUnit::Sat,
             localstore.clone(),
             &mnemonic.to_seed_normalized(""),
             None,
         )?;
         if let Some(proxy_url) = args.proxy.as_ref() {
-            wallet.set_client(HttpClient::with_proxy(proxy_url.clone(), None, true)?);
+            let http_client = HttpClient::with_proxy(mint_url, proxy_url.clone(), None, true)?;
+            wallet.set_client(Arc::from(http_client));
         }
 
         wallets.push(wallet);

+ 5 - 6
crates/cdk-cli/src/sub_commands/mint_info.rs

@@ -1,6 +1,6 @@
 use anyhow::Result;
 use cdk::mint_url::MintUrl;
-use cdk::wallet::client::HttpClientMethods;
+use cdk::wallet::client::MintConnector;
 use cdk::HttpClient;
 use clap::Args;
 use url::Url;
@@ -11,14 +11,13 @@ pub struct MintInfoSubcommand {
 }
 
 pub async fn mint_info(proxy: Option<Url>, sub_command_args: &MintInfoSubcommand) -> Result<()> {
+    let mint_url = sub_command_args.mint_url.clone();
     let client = match proxy {
-        Some(proxy) => HttpClient::with_proxy(proxy, None, true)?,
-        None => HttpClient::new(),
+        Some(proxy) => HttpClient::with_proxy(mint_url, proxy, None, true)?,
+        None => HttpClient::new(mint_url),
     };
 
-    let info = client
-        .get_mint_info(sub_command_args.mint_url.clone())
-        .await?;
+    let info = client.get_mint_info().await?;
 
     println!("{:#?}", info);
 

+ 1 - 0
crates/cdk-integration-tests/Cargo.toml

@@ -57,6 +57,7 @@ getrandom = { version = "0.2", features = ["js"] }
 instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] }
 
 [dev-dependencies]
+async-trait = "0.1"
 rand = "0.8.5"
 bip39 = { version = "2.0", features = ["rand"] }
 anyhow = "1"

+ 7 - 7
crates/cdk-integration-tests/src/lib.rs

@@ -1,4 +1,5 @@
 use std::collections::{HashMap, HashSet};
+use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;
 
@@ -10,12 +11,13 @@ use cdk::cdk_database::mint_memory::MintMemoryDatabase;
 use cdk::cdk_lightning::MintLightning;
 use cdk::dhke::construct_proofs;
 use cdk::mint::FeeReserve;
+use cdk::mint_url::MintUrl;
 use cdk::nuts::{
     CurrencyUnit, Id, KeySet, MintBolt11Request, MintInfo, MintQuoteBolt11Request, MintQuoteState,
     Nuts, PaymentMethod, PreMintSecrets, Proofs, State,
 };
 use cdk::types::{LnKey, QuoteTTL};
-use cdk::wallet::client::{HttpClient, HttpClientMethods};
+use cdk::wallet::client::{HttpClient, MintConnector};
 use cdk::{Mint, Wallet};
 use cdk_fake_wallet::FakeWallet;
 use init_regtest::{get_mint_addr, get_mint_port, get_mint_url};
@@ -155,7 +157,7 @@ pub async fn mint_proofs(
     println!("Minting for ecash");
     println!();
 
-    let wallet_client = HttpClient::new();
+    let wallet_client = HttpClient::new(MintUrl::from_str(mint_url)?);
 
     let request = MintQuoteBolt11Request {
         amount,
@@ -163,15 +165,13 @@ pub async fn mint_proofs(
         description,
     };
 
-    let mint_quote = wallet_client
-        .post_mint_quote(mint_url.parse()?, request)
-        .await?;
+    let mint_quote = wallet_client.post_mint_quote(request).await?;
 
     println!("Please pay: {}", mint_quote.request);
 
     loop {
         let status = wallet_client
-            .get_mint_quote_status(mint_url.parse()?, &mint_quote.quote)
+            .get_mint_quote_status(&mint_quote.quote)
             .await?;
 
         if status.state == MintQuoteState::Paid {
@@ -189,7 +189,7 @@ pub async fn mint_proofs(
         outputs: premint_secrets.blinded_messages(),
     };
 
-    let mint_response = wallet_client.post_mint(mint_url.parse()?, request).await?;
+    let mint_response = wallet_client.post_mint(request).await?;
 
     let pre_swap_proofs = construct_proofs(
         mint_response.signatures,

+ 3 - 3
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -8,7 +8,7 @@ use cdk::cdk_database::WalletMemoryDatabase;
 use cdk::nuts::{
     CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintQuoteState, PreMintSecrets, State,
 };
-use cdk::wallet::client::{HttpClient, HttpClientMethods};
+use cdk::wallet::client::{HttpClient, MintConnector};
 use cdk::wallet::Wallet;
 use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
 use cdk_integration_tests::attempt_to_swap_pending;
@@ -354,7 +354,7 @@ async fn test_fake_melt_change_in_quote() -> Result<()> {
 
     let premint_secrets = PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default())?;
 
-    let client = HttpClient::new();
+    let client = HttpClient::new(MINT_URL.parse()?);
 
     let melt_request = MeltBolt11Request {
         quote: melt_quote.id.clone(),
@@ -362,7 +362,7 @@ async fn test_fake_melt_change_in_quote() -> Result<()> {
         outputs: Some(premint_secrets.blinded_messages()),
     };
 
-    let melt_response = client.post_melt(MINT_URL.parse()?, melt_request).await?;
+    let melt_response = client.post_melt(melt_request).await?;
 
     assert!(melt_response.change.is_some());
 

+ 266 - 0
crates/cdk-integration-tests/tests/integration_tests_pure.rs

@@ -0,0 +1,266 @@
+#[cfg(test)]
+mod integration_tests_pure {
+    use std::assert_eq;
+    use std::collections::HashMap;
+    use std::fmt::{Debug, Formatter};
+    use std::str::FromStr;
+    use std::sync::Arc;
+
+    use async_trait::async_trait;
+    use cdk::amount::SplitTarget;
+    use cdk::cdk_database::mint_memory::MintMemoryDatabase;
+    use cdk::cdk_database::WalletMemoryDatabase;
+    use cdk::nuts::{
+        CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse,
+        MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
+        MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response,
+        MintQuoteState, Nuts, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
+    };
+    use cdk::types::QuoteTTL;
+    use cdk::util::unix_time;
+    use cdk::wallet::client::MintConnector;
+    use cdk::{Amount, Error, Mint, Wallet};
+    use cdk_integration_tests::create_backends_fake_wallet;
+    use rand::random;
+    use tokio::sync::Notify;
+    use uuid::Uuid;
+
+    struct DirectMintConnection {
+        mint: Arc<Mint>,
+    }
+
+    impl Debug for DirectMintConnection {
+        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+            write!(
+                f,
+                "DirectMintConnection {{ mint_info: {:?} }}",
+                self.mint.mint_info
+            )
+        }
+    }
+
+    /// Implements the generic [MintConnector] (i.e. use the interface that expects to communicate
+    /// to a generic mint, where we don't know that quote ID's are [Uuid]s) for [DirectMintConnection],
+    /// where we know we're dealing with a mint that uses [Uuid]s for quotes.
+    /// Convert the requests and responses between the [String] and [Uuid] variants as necessary.
+    #[async_trait]
+    impl MintConnector for DirectMintConnection {
+        async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
+            self.mint.pubkeys().await.map(|pks| pks.keysets)
+        }
+
+        async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
+            self.mint
+                .keyset(&keyset_id)
+                .await
+                .and_then(|res| res.ok_or(Error::UnknownKeySet))
+        }
+
+        async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
+            self.mint.keysets().await
+        }
+
+        async fn post_mint_quote(
+            &self,
+            request: MintQuoteBolt11Request,
+        ) -> Result<MintQuoteBolt11Response<String>, Error> {
+            self.mint
+                .get_mint_bolt11_quote(request)
+                .await
+                .map(Into::into)
+        }
+
+        async fn get_mint_quote_status(
+            &self,
+            quote_id: &str,
+        ) -> Result<MintQuoteBolt11Response<String>, Error> {
+            let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
+            self.mint
+                .check_mint_quote(&quote_id_uuid)
+                .await
+                .map(Into::into)
+        }
+
+        async fn post_mint(
+            &self,
+            request: MintBolt11Request<String>,
+        ) -> Result<MintBolt11Response, Error> {
+            let request_uuid = request.try_into().unwrap();
+            self.mint.process_mint_request(request_uuid).await
+        }
+
+        async fn post_melt_quote(
+            &self,
+            request: MeltQuoteBolt11Request,
+        ) -> Result<MeltQuoteBolt11Response<String>, Error> {
+            self.mint
+                .get_melt_bolt11_quote(&request)
+                .await
+                .map(Into::into)
+        }
+
+        async fn get_melt_quote_status(
+            &self,
+            quote_id: &str,
+        ) -> Result<MeltQuoteBolt11Response<String>, Error> {
+            let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
+            self.mint
+                .check_melt_quote(&quote_id_uuid)
+                .await
+                .map(Into::into)
+        }
+
+        async fn post_melt(
+            &self,
+            request: MeltBolt11Request<String>,
+        ) -> Result<MeltQuoteBolt11Response<String>, Error> {
+            let request_uuid = request.try_into().unwrap();
+            self.mint.melt_bolt11(&request_uuid).await.map(Into::into)
+        }
+
+        async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
+            self.mint.process_swap_request(swap_request).await
+        }
+
+        async fn get_mint_info(&self) -> Result<MintInfo, Error> {
+            Ok(self.mint.mint_info().clone().time(unix_time()))
+        }
+
+        async fn post_check_state(
+            &self,
+            request: CheckStateRequest,
+        ) -> Result<CheckStateResponse, Error> {
+            self.mint.check_state(&request).await
+        }
+
+        async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
+            self.mint.restore(request).await
+        }
+    }
+
+    fn get_mint_connector(mint: Arc<Mint>) -> DirectMintConnection {
+        DirectMintConnection { mint }
+    }
+
+    async fn create_and_start_test_mint() -> anyhow::Result<Arc<Mint>> {
+        let fee: u64 = 0;
+        let mut supported_units = HashMap::new();
+        supported_units.insert(CurrencyUnit::Sat, (fee, 32));
+
+        let nuts = Nuts::new()
+            .nut07(true)
+            .nut08(true)
+            .nut09(true)
+            .nut10(true)
+            .nut11(true)
+            .nut12(true)
+            .nut14(true);
+
+        let mint_info = MintInfo::new().nuts(nuts);
+
+        let quote_ttl = QuoteTTL::new(10000, 10000);
+
+        let mint_url = "http://aaa";
+
+        let seed = random::<[u8; 32]>();
+        let mint: Mint = Mint::new(
+            mint_url,
+            &seed,
+            mint_info,
+            quote_ttl,
+            Arc::new(MintMemoryDatabase::default()),
+            create_backends_fake_wallet(),
+            supported_units,
+            HashMap::new(),
+        )
+        .await?;
+
+        let mint_arc = Arc::new(mint);
+
+        let mint_arc_clone = Arc::clone(&mint_arc);
+        let shutdown = Arc::new(Notify::new());
+        tokio::spawn({
+            let shutdown = Arc::clone(&shutdown);
+            async move { mint_arc_clone.wait_for_paid_invoices(shutdown).await }
+        });
+
+        Ok(mint_arc)
+    }
+
+    fn create_test_wallet_for_mint(mint: Arc<Mint>) -> anyhow::Result<Arc<Wallet>> {
+        let connector = get_mint_connector(mint);
+
+        let seed = random::<[u8; 32]>();
+        let mint_url = connector.mint.mint_url.to_string();
+        let unit = CurrencyUnit::Sat;
+
+        let localstore = WalletMemoryDatabase::default();
+        let mut wallet = Wallet::new(&mint_url, unit, Arc::new(localstore), &seed, None)?;
+
+        wallet.set_client(Arc::from(connector));
+
+        Ok(Arc::new(wallet))
+    }
+
+    /// Creates a mint quote for the given amount and checks its state in a loop. Returns when
+    /// amount is minted.
+    async fn receive(wallet: Arc<Wallet>, amount: u64) -> anyhow::Result<Amount> {
+        let desired_amount = Amount::from(amount);
+        let quote = wallet.mint_quote(desired_amount, None).await?;
+
+        loop {
+            let status = wallet.mint_quote_state(&quote.id).await?;
+            if status.state == MintQuoteState::Paid {
+                break;
+            }
+        }
+
+        wallet
+            .mint(&quote.id, SplitTarget::default(), None)
+            .await
+            .map_err(Into::into)
+    }
+
+    mod nut03 {
+        use cdk::nuts::nut00::ProofsMethods;
+        use cdk::wallet::SendKind;
+
+        use crate::integration_tests_pure::*;
+
+        #[tokio::test]
+        async fn test_swap_to_send() -> anyhow::Result<()> {
+            let mint_bob = create_and_start_test_mint().await?;
+            let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())?;
+
+            // Alice gets 64 sats
+            receive(wallet_alice.clone(), 64).await?;
+            let balance_alice = wallet_alice.total_balance().await?;
+            assert_eq!(Amount::from(64), balance_alice);
+
+            // Alice wants to send 40 sats, which internally swaps
+            let token = wallet_alice
+                .send(
+                    Amount::from(40),
+                    None,
+                    None,
+                    &SplitTarget::None,
+                    &SendKind::OnlineExact,
+                    false,
+                )
+                .await?;
+            assert_eq!(Amount::from(40), token.proofs().total_amount()?);
+            assert_eq!(Amount::from(24), wallet_alice.total_balance().await?);
+
+            // Alice sends cashu, Carol receives
+            let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())?;
+            let received_amount = wallet_carol
+                .receive_proofs(token.proofs(), SplitTarget::None, &[], &[])
+                .await?;
+
+            assert_eq!(Amount::from(40), received_amount);
+            assert_eq!(Amount::from(40), wallet_carol.total_balance().await?);
+
+            Ok(())
+        }
+    }
+}

+ 4 - 8
crates/cdk-integration-tests/tests/regtest.rs

@@ -11,7 +11,7 @@ use cdk::nuts::{
     CurrencyUnit, MeltQuoteState, MintBolt11Request, MintQuoteState, NotificationPayload,
     PreMintSecrets, State,
 };
-use cdk::wallet::client::{HttpClient, HttpClientMethods};
+use cdk::wallet::client::{HttpClient, MintConnector};
 use cdk::wallet::Wallet;
 use cdk_integration_tests::init_regtest::{
     get_mint_url, get_mint_ws_url, init_cln_client, init_lnd_client,
@@ -374,7 +374,7 @@ async fn test_cached_mint() -> Result<()> {
     }
 
     let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
-    let http_client = HttpClient::new();
+    let http_client = HttpClient::new(get_mint_url().as_str().parse()?);
     let premint_secrets =
         PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap();
 
@@ -383,12 +383,8 @@ async fn test_cached_mint() -> Result<()> {
         outputs: premint_secrets.blinded_messages(),
     };
 
-    let response = http_client
-        .post_mint(get_mint_url().as_str().parse()?, request.clone())
-        .await?;
-    let response1 = http_client
-        .post_mint(get_mint_url().as_str().parse()?, request)
-        .await?;
+    let response = http_client.post_mint(request.clone()).await?;
+    let response1 = http_client.post_mint(request).await?;
 
     assert!(response == response1);
     Ok(())

+ 24 - 0
crates/cdk/src/nuts/nut04.rs

@@ -97,6 +97,18 @@ pub struct MintQuoteBolt11Response<Q> {
 }
 
 #[cfg(feature = "mint")]
+impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
+    fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
+        Self {
+            quote: value.quote.to_string(),
+            request: value.request,
+            state: value.state,
+            expiry: value.expiry,
+        }
+    }
+}
+
+#[cfg(feature = "mint")]
 impl From<crate::mint::MintQuote> for MintQuoteBolt11Response<Uuid> {
     fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
         MintQuoteBolt11Response {
@@ -121,6 +133,18 @@ pub struct MintBolt11Request<Q> {
     pub outputs: Vec<BlindedMessage>,
 }
 
+#[cfg(feature = "mint")]
+impl TryFrom<MintBolt11Request<String>> for MintBolt11Request<Uuid> {
+    type Error = uuid::Error;
+
+    fn try_from(value: MintBolt11Request<String>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            quote: Uuid::from_str(&value.quote)?,
+            outputs: value.outputs,
+        })
+    }
+}
+
 impl<Q> MintBolt11Request<Q> {
     /// Total [`Amount`] of outputs
     pub fn total_amount(&self) -> Result<Amount, Error> {

+ 29 - 0
crates/cdk/src/nuts/nut05.rs

@@ -116,6 +116,22 @@ pub struct MeltQuoteBolt11Response<Q> {
 }
 
 #[cfg(feature = "mint")]
+impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
+    fn from(value: MeltQuoteBolt11Response<Uuid>) -> Self {
+        Self {
+            quote: value.quote.to_string(),
+            amount: value.amount,
+            fee_reserve: value.fee_reserve,
+            paid: value.paid,
+            state: value.state,
+            expiry: value.expiry,
+            payment_preimage: value.payment_preimage,
+            change: value.change,
+        }
+    }
+}
+
+#[cfg(feature = "mint")]
 impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
     fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
         MeltQuoteBolt11Response {
@@ -247,6 +263,19 @@ pub struct MeltBolt11Request<Q> {
     pub outputs: Option<Vec<BlindedMessage>>,
 }
 
+#[cfg(feature = "mint")]
+impl TryFrom<MeltBolt11Request<String>> for MeltBolt11Request<Uuid> {
+    type Error = uuid::Error;
+
+    fn try_from(value: MeltBolt11Request<String>) -> Result<Self, Self::Error> {
+        Ok(Self {
+            quote: Uuid::from_str(&value.quote)?,
+            inputs: value.inputs,
+            outputs: value.outputs,
+        })
+    }
+}
+
 impl<Q: Serialize + DeserializeOwned> MeltBolt11Request<Q> {
     /// Total [`Amount`] of [`Proofs`]
     pub fn proofs_amount(&self) -> Result<Amount, Error> {

+ 59 - 91
crates/cdk/src/wallet/client.rs

@@ -33,19 +33,15 @@ macro_rules! convert_http_response {
 #[derive(Debug, Clone)]
 pub struct HttpClient {
     inner: Client,
-}
-
-impl Default for HttpClient {
-    fn default() -> Self {
-        Self::new()
-    }
+    mint_url: MintUrl,
 }
 
 impl HttpClient {
     /// Create new [`HttpClient`]
-    pub fn new() -> Self {
+    pub fn new(mint_url: MintUrl) -> Self {
         Self {
             inner: Client::new(),
+            mint_url,
         }
     }
 
@@ -54,6 +50,7 @@ impl HttpClient {
     /// Specifying `None` for `host_matcher` will use the proxy for all
     /// requests.
     pub fn with_proxy(
+        mint_url: MintUrl,
         proxy: Url,
         host_matcher: Option<&str>,
         accept_invalid_certs: bool,
@@ -76,26 +73,31 @@ impl HttpClient {
             .danger_accept_invalid_certs(accept_invalid_certs) // Allow self-signed certs
             .build()?;
 
-        Ok(Self { inner: client })
+        Ok(Self {
+            inner: client,
+            mint_url,
+        })
     }
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl HttpClientMethods for HttpClient {
+impl MintConnector for HttpClient {
     /// Get Active Mint Keys [NUT-01]
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
-    async fn get_mint_keys(&self, mint_url: MintUrl) -> Result<Vec<KeySet>, Error> {
-        let url = mint_url.join_paths(&["v1", "keys"])?;
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
+    async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
+        let url = self.mint_url.join_paths(&["v1", "keys"])?;
         let keys = self.inner.get(url).send().await?.text().await?;
 
         Ok(convert_http_response!(KeysResponse, keys)?.keysets)
     }
 
     /// Get Keyset Keys [NUT-01]
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
-    async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result<KeySet, Error> {
-        let url = mint_url.join_paths(&["v1", "keys", &keyset_id.to_string()])?;
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
+    async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "keys", &keyset_id.to_string()])?;
         let keys = self.inner.get(url).send().await?.text().await?;
 
         convert_http_response!(KeysResponse, keys)?
@@ -106,22 +108,23 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Get Keysets [NUT-02]
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
-    async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result<KeysetResponse, Error> {
-        let url = mint_url.join_paths(&["v1", "keysets"])?;
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
+    async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
+        let url = self.mint_url.join_paths(&["v1", "keysets"])?;
         let res = self.inner.get(url).send().await?.text().await?;
 
         convert_http_response!(KeysetResponse, res)
     }
 
     /// Mint Quote [NUT-04]
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
     async fn post_mint_quote(
         &self,
-        mint_url: MintUrl,
         request: MintQuoteBolt11Request,
     ) -> Result<MintQuoteBolt11Response<String>, Error> {
-        let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11"])?;
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "mint", "quote", "bolt11"])?;
 
         let res = self
             .inner
@@ -136,13 +139,14 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Mint Quote status
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
     async fn get_mint_quote_status(
         &self,
-        mint_url: MintUrl,
         quote_id: &str,
     ) -> Result<MintQuoteBolt11Response<String>, Error> {
-        let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11", quote_id])?;
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "mint", "quote", "bolt11", quote_id])?;
 
         let res = self.inner.get(url).send().await?.text().await?;
 
@@ -150,13 +154,12 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Mint Tokens [NUT-04]
-    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
+    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
     async fn post_mint(
         &self,
-        mint_url: MintUrl,
         request: MintBolt11Request<String>,
     ) -> Result<MintBolt11Response, Error> {
-        let url = mint_url.join_paths(&["v1", "mint", "bolt11"])?;
+        let url = self.mint_url.join_paths(&["v1", "mint", "bolt11"])?;
 
         let res = self
             .inner
@@ -171,13 +174,14 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Melt Quote [NUT-05]
-    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
+    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
     async fn post_melt_quote(
         &self,
-        mint_url: MintUrl,
         request: MeltQuoteBolt11Request,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11"])?;
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "melt", "quote", "bolt11"])?;
 
         let res = self
             .inner
@@ -192,13 +196,14 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Melt Quote Status
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
     async fn get_melt_quote_status(
         &self,
-        mint_url: MintUrl,
         quote_id: &str,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11", quote_id])?;
+        let url = self
+            .mint_url
+            .join_paths(&["v1", "melt", "quote", "bolt11", quote_id])?;
 
         let res = self.inner.get(url).send().await?.text().await?;
 
@@ -207,13 +212,12 @@ impl HttpClientMethods for HttpClient {
 
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
-    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
+    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
     async fn post_melt(
         &self,
-        mint_url: MintUrl,
         request: MeltBolt11Request<String>,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        let url = mint_url.join_paths(&["v1", "melt", "bolt11"])?;
+        let url = self.mint_url.join_paths(&["v1", "melt", "bolt11"])?;
 
         let res = self
             .inner
@@ -228,13 +232,9 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Swap Token [NUT-03]
-    #[instrument(skip(self, swap_request), fields(mint_url = %mint_url))]
-    async fn post_swap(
-        &self,
-        mint_url: MintUrl,
-        swap_request: SwapRequest,
-    ) -> Result<SwapResponse, Error> {
-        let url = mint_url.join_paths(&["v1", "swap"])?;
+    #[instrument(skip(self, swap_request), fields(mint_url = %self.mint_url))]
+    async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
+        let url = self.mint_url.join_paths(&["v1", "swap"])?;
 
         let res = self
             .inner
@@ -249,9 +249,9 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Get Mint Info [NUT-06]
-    #[instrument(skip(self), fields(mint_url = %mint_url))]
-    async fn get_mint_info(&self, mint_url: MintUrl) -> Result<MintInfo, Error> {
-        let url = mint_url.join_paths(&["v1", "info"])?;
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
+    async fn get_mint_info(&self) -> Result<MintInfo, Error> {
+        let url = self.mint_url.join_paths(&["v1", "info"])?;
 
         let res = self.inner.get(url).send().await?.text().await?;
 
@@ -259,13 +259,12 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Spendable check [NUT-07]
-    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
+    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
     async fn post_check_state(
         &self,
-        mint_url: MintUrl,
         request: CheckStateRequest,
     ) -> Result<CheckStateResponse, Error> {
-        let url = mint_url.join_paths(&["v1", "checkstate"])?;
+        let url = self.mint_url.join_paths(&["v1", "checkstate"])?;
 
         let res = self
             .inner
@@ -280,13 +279,9 @@ impl HttpClientMethods for HttpClient {
     }
 
     /// Restore request [NUT-13]
-    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
-    async fn post_restore(
-        &self,
-        mint_url: MintUrl,
-        request: RestoreRequest,
-    ) -> Result<RestoreResponse, Error> {
-        let url = mint_url.join_paths(&["v1", "restore"])?;
+    #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
+    async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
+        let url = self.mint_url.join_paths(&["v1", "restore"])?;
 
         let res = self
             .inner
@@ -301,83 +296,56 @@ impl HttpClientMethods for HttpClient {
     }
 }
 
-/// Http Client Methods
+/// Interface that connects a wallet to a mint. Typically represents an [HttpClient].
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-pub trait HttpClientMethods: Debug {
+pub trait MintConnector: Debug {
     /// Get Active Mint Keys [NUT-01]
-    async fn get_mint_keys(&self, mint_url: MintUrl) -> Result<Vec<KeySet>, Error>;
-
+    async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error>;
     /// Get Keyset Keys [NUT-01]
-    async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result<KeySet, Error>;
-
+    async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error>;
     /// Get Keysets [NUT-02]
-    async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result<KeysetResponse, Error>;
-
+    async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error>;
     /// Mint Quote [NUT-04]
     async fn post_mint_quote(
         &self,
-        mint_url: MintUrl,
         request: MintQuoteBolt11Request,
     ) -> Result<MintQuoteBolt11Response<String>, Error>;
-
     /// Mint Quote status
     async fn get_mint_quote_status(
         &self,
-        mint_url: MintUrl,
         quote_id: &str,
     ) -> Result<MintQuoteBolt11Response<String>, Error>;
-
     /// Mint Tokens [NUT-04]
     async fn post_mint(
         &self,
-        mint_url: MintUrl,
         request: MintBolt11Request<String>,
     ) -> Result<MintBolt11Response, Error>;
-
     /// Melt Quote [NUT-05]
     async fn post_melt_quote(
         &self,
-        mint_url: MintUrl,
         request: MeltQuoteBolt11Request,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
-
     /// Melt Quote Status
     async fn get_melt_quote_status(
         &self,
-        mint_url: MintUrl,
         quote_id: &str,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
-
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
     async fn post_melt(
         &self,
-        mint_url: MintUrl,
         request: MeltBolt11Request<String>,
     ) -> Result<MeltQuoteBolt11Response<String>, Error>;
-
     /// Split Token [NUT-06]
-    async fn post_swap(
-        &self,
-        mint_url: MintUrl,
-        request: SwapRequest,
-    ) -> Result<SwapResponse, Error>;
-
+    async fn post_swap(&self, request: SwapRequest) -> Result<SwapResponse, Error>;
     /// Get Mint Info [NUT-06]
-    async fn get_mint_info(&self, mint_url: MintUrl) -> Result<MintInfo, Error>;
-
+    async fn get_mint_info(&self) -> Result<MintInfo, Error>;
     /// Spendable check [NUT-07]
     async fn post_check_state(
         &self,
-        mint_url: MintUrl,
         request: CheckStateRequest,
     ) -> Result<CheckStateResponse, Error>;
-
     /// Restore request [NUT-13]
-    async fn post_restore(
-        &self,
-        mint_url: MintUrl,
-        request: RestoreRequest,
-    ) -> Result<RestoreResponse, Error>;
+    async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error>;
 }

+ 3 - 6
crates/cdk/src/wallet/keysets.rs

@@ -13,10 +13,7 @@ impl Wallet {
         let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
             keys
         } else {
-            let keys = self
-                .client
-                .get_mint_keyset(self.mint_url.clone(), keyset_id)
-                .await?;
+            let keys = self.client.get_mint_keyset(keyset_id).await?;
 
             keys.verify_id()?;
 
@@ -33,7 +30,7 @@ impl Wallet {
     /// Queries mint for all keysets
     #[instrument(skip(self))]
     pub async fn get_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
-        let keysets = self.client.get_mint_keysets(self.mint_url.clone()).await?;
+        let keysets = self.client.get_mint_keysets().await?;
 
         self.localstore
             .add_mint_keysets(self.mint_url.clone(), keysets.keysets.clone())
@@ -48,7 +45,7 @@ impl Wallet {
     /// keysets
     #[instrument(skip(self))]
     pub async fn get_active_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
-        let keysets = self.client.get_mint_keysets(self.mint_url.clone()).await?;
+        let keysets = self.client.get_mint_keysets().await?;
         let keysets = keysets.keysets;
 
         self.localstore

+ 3 - 9
crates/cdk/src/wallet/melt.rs

@@ -65,10 +65,7 @@ impl Wallet {
             options,
         };
 
-        let quote_res = self
-            .client
-            .post_melt_quote(self.mint_url.clone(), quote_request)
-            .await?;
+        let quote_res = self.client.post_melt_quote(quote_request).await?;
 
         if quote_res.amount != amount {
             return Err(Error::IncorrectQuoteAmount);
@@ -96,10 +93,7 @@ impl Wallet {
         &self,
         quote_id: &str,
     ) -> Result<MeltQuoteBolt11Response<String>, Error> {
-        let response = self
-            .client
-            .get_melt_quote_status(self.mint_url.clone(), quote_id)
-            .await?;
+        let response = self.client.get_melt_quote_status(quote_id).await?;
 
         match self.localstore.get_melt_quote(quote_id).await? {
             Some(quote) => {
@@ -160,7 +154,7 @@ impl Wallet {
             outputs: Some(premint_secrets.blinded_messages()),
         };
 
-        let melt_response = self.client.post_melt(self.mint_url.clone(), request).await;
+        let melt_response = self.client.post_melt(request).await;
 
         let melt_response = match melt_response {
             Ok(melt_response) => melt_response,

+ 3 - 12
crates/cdk/src/wallet/mint.rs

@@ -71,10 +71,7 @@ impl Wallet {
             description,
         };
 
-        let quote_res = self
-            .client
-            .post_mint_quote(mint_url.clone(), request)
-            .await?;
+        let quote_res = self.client.post_mint_quote(request).await?;
 
         let quote = MintQuote {
             mint_url,
@@ -97,10 +94,7 @@ impl Wallet {
         &self,
         quote_id: &str,
     ) -> Result<MintQuoteBolt11Response<String>, Error> {
-        let response = self
-            .client
-            .get_mint_quote_status(self.mint_url.clone(), quote_id)
-            .await?;
+        let response = self.client.get_mint_quote_status(quote_id).await?;
 
         match self.localstore.get_mint_quote(quote_id).await? {
             Some(quote) => {
@@ -227,10 +221,7 @@ impl Wallet {
             outputs: premint_secrets.blinded_messages(),
         };
 
-        let mint_res = self
-            .client
-            .post_mint(self.mint_url.clone(), request)
-            .await?;
+        let mint_res = self.client.post_mint(request).await?;
 
         let keys = self.get_keyset_keys(active_keyset_id).await?;
 

+ 10 - 12
crates/cdk/src/wallet/mod.rs

@@ -6,7 +6,7 @@ use std::sync::Arc;
 
 use bitcoin::bip32::Xpriv;
 use bitcoin::Network;
-use client::HttpClientMethods;
+use client::MintConnector;
 use tracing::instrument;
 
 use crate::amount::SplitTarget;
@@ -57,7 +57,7 @@ pub struct Wallet {
     /// The targeted amount of proofs to have at each size
     pub target_proof_count: usize,
     xpriv: Xpriv,
-    client: Arc<dyn HttpClientMethods + Send + Sync>,
+    client: Arc<dyn MintConnector + Send + Sync>,
 }
 
 impl Wallet {
@@ -86,11 +86,12 @@ impl Wallet {
         target_proof_count: Option<usize>,
     ) -> Result<Self, Error> {
         let xpriv = Xpriv::new_master(Network::Bitcoin, seed).expect("Could not create master key");
+        let mint_url = MintUrl::from_str(mint_url)?;
 
         Ok(Self {
-            mint_url: MintUrl::from_str(mint_url)?,
+            mint_url: mint_url.clone(),
             unit,
-            client: Arc::new(HttpClient::new()),
+            client: Arc::new(HttpClient::new(mint_url)),
             localstore,
             xpriv,
             target_proof_count: target_proof_count.unwrap_or(3),
@@ -98,8 +99,8 @@ impl Wallet {
     }
 
     /// Change HTTP client
-    pub fn set_client<C: HttpClientMethods + 'static + Send + Sync>(&mut self, client: C) {
-        self.client = Arc::new(client);
+    pub fn set_client(&mut self, client: Arc<dyn MintConnector + Send + Sync>) {
+        self.client = client;
     }
 
     /// Fee required for proof set
@@ -160,10 +161,10 @@ impl Wallet {
         Ok(())
     }
 
-    /// Qeury mint for current mint information
+    /// Query mint for current mint information
     #[instrument(skip(self))]
     pub async fn get_mint_info(&self) -> Result<Option<MintInfo>, Error> {
-        let mint_info = match self.client.get_mint_info(self.mint_url.clone()).await {
+        let mint_info = match self.client.get_mint_info().await {
             Ok(mint_info) => Some(mint_info),
             Err(err) => {
                 tracing::warn!("Could not get mint info {}", err);
@@ -277,10 +278,7 @@ impl Wallet {
                     outputs: premint_secrets.blinded_messages(),
                 };
 
-                let response = self
-                    .client
-                    .post_restore(self.mint_url.clone(), restore_request)
-                    .await?;
+                let response = self.client.post_restore(restore_request).await?;
 
                 if response.signatures.is_empty() {
                     empty_batch += 1;

+ 2 - 5
crates/cdk/src/wallet/proofs.rs

@@ -65,7 +65,7 @@ impl Wallet {
 
         let spendable = self
             .client
-            .post_check_state(self.mint_url.clone(), CheckStateRequest { ys: proof_ys })
+            .post_check_state(CheckStateRequest { ys: proof_ys })
             .await?
             .states;
 
@@ -86,10 +86,7 @@ impl Wallet {
     pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Error> {
         let spendable = self
             .client
-            .post_check_state(
-                self.mint_url.clone(),
-                CheckStateRequest { ys: proofs.ys()? },
-            )
+            .post_check_state(CheckStateRequest { ys: proofs.ys()? })
             .await?;
         let spent_ys: Vec<_> = spendable
             .states

+ 1 - 4
crates/cdk/src/wallet/receive.rs

@@ -128,10 +128,7 @@ impl Wallet {
             }
         }
 
-        let swap_response = self
-            .client
-            .post_swap(mint_url.clone(), pre_swap.swap_request)
-            .await?;
+        let swap_response = self.client.post_swap(pre_swap.swap_request).await?;
 
         // Proof to keep
         let recv_proofs = construct_proofs(

+ 1 - 4
crates/cdk/src/wallet/swap.rs

@@ -33,10 +33,7 @@ impl Wallet {
             )
             .await?;
 
-        let swap_response = self
-            .client
-            .post_swap(mint_url.clone(), pre_swap.swap_request)
-            .await?;
+        let swap_response = self.client.post_swap(pre_swap.swap_request).await?;
 
         let active_keyset_id = pre_swap.pre_mint_secrets.keyset_id;