From 80cdf9c553f67855d59e99bc52da3a507a9c315d Mon Sep 17 00:00:00 2001 From: ok300 <106775972+ok300@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:33:58 +0000 Subject: [PATCH] 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 --- Cargo.lock | 1 + crates/cdk-cli/src/main.rs | 7 +- crates/cdk-cli/src/sub_commands/mint_info.rs | 11 +- crates/cdk-integration-tests/Cargo.toml | 1 + crates/cdk-integration-tests/src/lib.rs | 14 +- .../tests/fake_wallet.rs | 6 +- .../tests/integration_tests_pure.rs | 266 ++++++++++++++++++ crates/cdk-integration-tests/tests/regtest.rs | 12 +- crates/cdk/src/nuts/nut04.rs | 24 ++ crates/cdk/src/nuts/nut05.rs | 29 ++ crates/cdk/src/wallet/client.rs | 150 ++++------ crates/cdk/src/wallet/keysets.rs | 9 +- crates/cdk/src/wallet/melt.rs | 12 +- crates/cdk/src/wallet/mint.rs | 15 +- crates/cdk/src/wallet/mod.rs | 22 +- crates/cdk/src/wallet/proofs.rs | 7 +- crates/cdk/src/wallet/receive.rs | 5 +- crates/cdk/src/wallet/swap.rs | 5 +- 18 files changed, 426 insertions(+), 170 deletions(-) create mode 100644 crates/cdk-integration-tests/tests/integration_tests_pure.rs diff --git a/Cargo.lock b/Cargo.lock index 54fb86a2b..ef13fbe6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,6 +780,7 @@ name = "cdk-integration-tests" version = "0.5.0" dependencies = [ "anyhow", + "async-trait", "axum", "bip39", "cdk", diff --git a/crates/cdk-cli/src/main.rs b/crates/cdk-cli/src/main.rs index 9635045b9..e6c6c730a 100644 --- a/crates/cdk-cli/src/main.rs +++ b/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); diff --git a/crates/cdk-cli/src/sub_commands/mint_info.rs b/crates/cdk-cli/src/sub_commands/mint_info.rs index 739d9934b..b2dc0b1cb 100644 --- a/crates/cdk-cli/src/sub_commands/mint_info.rs +++ b/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, 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); diff --git a/crates/cdk-integration-tests/Cargo.toml b/crates/cdk-integration-tests/Cargo.toml index 135186f34..9be7c4c80 100644 --- a/crates/cdk-integration-tests/Cargo.toml +++ b/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" diff --git a/crates/cdk-integration-tests/src/lib.rs b/crates/cdk-integration-tests/src/lib.rs index 2e52b0345..b0026696c 100644 --- a/crates/cdk-integration-tests/src/lib.rs +++ b/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, diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index cf0ea1bef..cfea089c9 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/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()); diff --git a/crates/cdk-integration-tests/tests/integration_tests_pure.rs b/crates/cdk-integration-tests/tests/integration_tests_pure.rs new file mode 100644 index 000000000..5d4617d68 --- /dev/null +++ b/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, + } + + 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, Error> { + self.mint.pubkeys().await.map(|pks| pks.keysets) + } + + async fn get_mint_keyset(&self, keyset_id: Id) -> Result { + self.mint + .keyset(&keyset_id) + .await + .and_then(|res| res.ok_or(Error::UnknownKeySet)) + } + + async fn get_mint_keysets(&self) -> Result { + self.mint.keysets().await + } + + async fn post_mint_quote( + &self, + request: MintQuoteBolt11Request, + ) -> Result, Error> { + self.mint + .get_mint_bolt11_quote(request) + .await + .map(Into::into) + } + + async fn get_mint_quote_status( + &self, + quote_id: &str, + ) -> Result, Error> { + let quote_id_uuid = Uuid::from_str(quote_id).unwrap(); + self.mint + .check_mint_quote("e_id_uuid) + .await + .map(Into::into) + } + + async fn post_mint( + &self, + request: MintBolt11Request, + ) -> Result { + let request_uuid = request.try_into().unwrap(); + self.mint.process_mint_request(request_uuid).await + } + + async fn post_melt_quote( + &self, + request: MeltQuoteBolt11Request, + ) -> Result, Error> { + self.mint + .get_melt_bolt11_quote(&request) + .await + .map(Into::into) + } + + async fn get_melt_quote_status( + &self, + quote_id: &str, + ) -> Result, Error> { + let quote_id_uuid = Uuid::from_str(quote_id).unwrap(); + self.mint + .check_melt_quote("e_id_uuid) + .await + .map(Into::into) + } + + async fn post_melt( + &self, + request: MeltBolt11Request, + ) -> Result, 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 { + self.mint.process_swap_request(swap_request).await + } + + async fn get_mint_info(&self) -> Result { + Ok(self.mint.mint_info().clone().time(unix_time())) + } + + async fn post_check_state( + &self, + request: CheckStateRequest, + ) -> Result { + self.mint.check_state(&request).await + } + + async fn post_restore(&self, request: RestoreRequest) -> Result { + self.mint.restore(request).await + } + } + + fn get_mint_connector(mint: Arc) -> DirectMintConnection { + DirectMintConnection { mint } + } + + async fn create_and_start_test_mint() -> anyhow::Result> { + 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) -> anyhow::Result> { + 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, amount: u64) -> anyhow::Result { + let desired_amount = Amount::from(amount); + let quote = wallet.mint_quote(desired_amount, None).await?; + + loop { + let status = wallet.mint_quote_state("e.id).await?; + if status.state == MintQuoteState::Paid { + break; + } + } + + wallet + .mint("e.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(()) + } + } +} diff --git a/crates/cdk-integration-tests/tests/regtest.rs b/crates/cdk-integration-tests/tests/regtest.rs index c88a86fb0..846c7eb63 100644 --- a/crates/cdk-integration-tests/tests/regtest.rs +++ b/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(()) diff --git a/crates/cdk/src/nuts/nut04.rs b/crates/cdk/src/nuts/nut04.rs index 7f90ba320..fea9d9a52 100644 --- a/crates/cdk/src/nuts/nut04.rs +++ b/crates/cdk/src/nuts/nut04.rs @@ -96,6 +96,18 @@ pub struct MintQuoteBolt11Response { pub expiry: Option, } +#[cfg(feature = "mint")] +impl From> for MintQuoteBolt11Response { + fn from(value: MintQuoteBolt11Response) -> Self { + Self { + quote: value.quote.to_string(), + request: value.request, + state: value.state, + expiry: value.expiry, + } + } +} + #[cfg(feature = "mint")] impl From for MintQuoteBolt11Response { fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response { @@ -121,6 +133,18 @@ pub struct MintBolt11Request { pub outputs: Vec, } +#[cfg(feature = "mint")] +impl TryFrom> for MintBolt11Request { + type Error = uuid::Error; + + fn try_from(value: MintBolt11Request) -> Result { + Ok(Self { + quote: Uuid::from_str(&value.quote)?, + outputs: value.outputs, + }) + } +} + impl MintBolt11Request { /// Total [`Amount`] of outputs pub fn total_amount(&self) -> Result { diff --git a/crates/cdk/src/nuts/nut05.rs b/crates/cdk/src/nuts/nut05.rs index 3510ef4fc..3a6f5ae3e 100644 --- a/crates/cdk/src/nuts/nut05.rs +++ b/crates/cdk/src/nuts/nut05.rs @@ -115,6 +115,22 @@ pub struct MeltQuoteBolt11Response { pub change: Option>, } +#[cfg(feature = "mint")] +impl From> for MeltQuoteBolt11Response { + fn from(value: MeltQuoteBolt11Response) -> 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 { fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response { @@ -247,6 +263,19 @@ pub struct MeltBolt11Request { pub outputs: Option>, } +#[cfg(feature = "mint")] +impl TryFrom> for MeltBolt11Request { + type Error = uuid::Error; + + fn try_from(value: MeltBolt11Request) -> Result { + Ok(Self { + quote: Uuid::from_str(&value.quote)?, + inputs: value.inputs, + outputs: value.outputs, + }) + } +} + impl MeltBolt11Request { /// Total [`Amount`] of [`Proofs`] pub fn proofs_amount(&self) -> Result { diff --git a/crates/cdk/src/wallet/client.rs b/crates/cdk/src/wallet/client.rs index 52091d03b..311981733 100644 --- a/crates/cdk/src/wallet/client.rs +++ b/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, 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, 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 { - 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 { + 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 { - let url = mint_url.join_paths(&["v1", "keysets"])?; + #[instrument(skip(self), fields(mint_url = %self.mint_url))] + async fn get_mint_keysets(&self) -> Result { + 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, 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, 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, ) -> Result { - 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, 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, 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, ) -> Result, 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 { - 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 { + 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 { - let url = mint_url.join_paths(&["v1", "info"])?; + #[instrument(skip(self), fields(mint_url = %self.mint_url))] + async fn get_mint_info(&self) -> Result { + 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 { - 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 { - 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 { + 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, Error>; - + async fn get_mint_keys(&self) -> Result, Error>; /// Get Keyset Keys [NUT-01] - async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result; - + async fn get_mint_keyset(&self, keyset_id: Id) -> Result; /// Get Keysets [NUT-02] - async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result; - + async fn get_mint_keysets(&self) -> Result; /// Mint Quote [NUT-04] async fn post_mint_quote( &self, - mint_url: MintUrl, request: MintQuoteBolt11Request, ) -> Result, Error>; - /// Mint Quote status async fn get_mint_quote_status( &self, - mint_url: MintUrl, quote_id: &str, ) -> Result, Error>; - /// Mint Tokens [NUT-04] async fn post_mint( &self, - mint_url: MintUrl, request: MintBolt11Request, ) -> Result; - /// Melt Quote [NUT-05] async fn post_melt_quote( &self, - mint_url: MintUrl, request: MeltQuoteBolt11Request, ) -> Result, Error>; - /// Melt Quote Status async fn get_melt_quote_status( &self, - mint_url: MintUrl, quote_id: &str, ) -> Result, Error>; - /// Melt [NUT-05] /// [Nut-08] Lightning fee return if outputs defined async fn post_melt( &self, - mint_url: MintUrl, request: MeltBolt11Request, ) -> Result, Error>; - /// Split Token [NUT-06] - async fn post_swap( - &self, - mint_url: MintUrl, - request: SwapRequest, - ) -> Result; - + async fn post_swap(&self, request: SwapRequest) -> Result; /// Get Mint Info [NUT-06] - async fn get_mint_info(&self, mint_url: MintUrl) -> Result; - + async fn get_mint_info(&self) -> Result; /// Spendable check [NUT-07] async fn post_check_state( &self, - mint_url: MintUrl, request: CheckStateRequest, ) -> Result; - /// Restore request [NUT-13] - async fn post_restore( - &self, - mint_url: MintUrl, - request: RestoreRequest, - ) -> Result; + async fn post_restore(&self, request: RestoreRequest) -> Result; } diff --git a/crates/cdk/src/wallet/keysets.rs b/crates/cdk/src/wallet/keysets.rs index 30ec387fb..16223347a 100644 --- a/crates/cdk/src/wallet/keysets.rs +++ b/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()?; @@ -58,7 +55,7 @@ impl Wallet { /// Queries mint for all keysets #[instrument(skip(self))] pub async fn get_mint_keysets(&self) -> Result, 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()) @@ -73,7 +70,7 @@ impl Wallet { /// keysets #[instrument(skip(self))] pub async fn get_active_mint_keysets(&self) -> Result, 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 diff --git a/crates/cdk/src/wallet/melt.rs b/crates/cdk/src/wallet/melt.rs index def4b59d4..8e1910e04 100644 --- a/crates/cdk/src/wallet/melt.rs +++ b/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, 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, diff --git a/crates/cdk/src/wallet/mint.rs b/crates/cdk/src/wallet/mint.rs index 5bebefdd6..69116daa3 100644 --- a/crates/cdk/src/wallet/mint.rs +++ b/crates/cdk/src/wallet/mint.rs @@ -72,10 +72,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, @@ -98,10 +95,7 @@ impl Wallet { &self, quote_id: &str, ) -> Result, 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) => { @@ -275,10 +269,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?; diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index bff6d4bfe..afcf8321b 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/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, + client: Arc, } impl Wallet { @@ -86,11 +86,12 @@ impl Wallet { target_proof_count: Option, ) -> Result { 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(&mut self, client: C) { - self.client = Arc::new(client); + pub fn set_client(&mut self, client: Arc) { + 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, 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; diff --git a/crates/cdk/src/wallet/proofs.rs b/crates/cdk/src/wallet/proofs.rs index c56956ce6..b6c0aba60 100644 --- a/crates/cdk/src/wallet/proofs.rs +++ b/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, 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 diff --git a/crates/cdk/src/wallet/receive.rs b/crates/cdk/src/wallet/receive.rs index c98c3cc72..1a1cb75a4 100644 --- a/crates/cdk/src/wallet/receive.rs +++ b/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( diff --git a/crates/cdk/src/wallet/swap.rs b/crates/cdk/src/wallet/swap.rs index 784d180d2..e50c986d0 100644 --- a/crates/cdk/src/wallet/swap.rs +++ b/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;