From 7eb34497101035b14dd43417a8fd19cf8a1b7693 Mon Sep 17 00:00:00 2001 From: David Caseria Date: Mon, 4 Nov 2024 14:14:40 -0500 Subject: [PATCH] Abstract HttpClient into HttpClientMethods trait (#429) * Abstract HttpClientMethods trait --------- Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com> --- crates/cdk-cli/src/sub_commands/mint_info.rs | 1 + crates/cdk-integration-tests/src/lib.rs | 27 +-- .../tests/fake_wallet.rs | 22 ++- crates/cdk-integration-tests/tests/regtest.rs | 22 ++- crates/cdk/src/nuts/nut03.rs | 4 +- crates/cdk/src/wallet/client.rs | 176 ++++++++++++------ crates/cdk/src/wallet/melt.rs | 27 ++- crates/cdk/src/wallet/mint.rs | 16 +- crates/cdk/src/wallet/mod.rs | 9 +- crates/cdk/src/wallet/proofs.rs | 8 +- 10 files changed, 201 insertions(+), 111 deletions(-) diff --git a/crates/cdk-cli/src/sub_commands/mint_info.rs b/crates/cdk-cli/src/sub_commands/mint_info.rs index bcf9f5ec..739d9934 100644 --- a/crates/cdk-cli/src/sub_commands/mint_info.rs +++ b/crates/cdk-cli/src/sub_commands/mint_info.rs @@ -1,5 +1,6 @@ use anyhow::Result; use cdk::mint_url::MintUrl; +use cdk::wallet::client::HttpClientMethods; use cdk::HttpClient; use clap::Args; use url::Url; diff --git a/crates/cdk-integration-tests/src/lib.rs b/crates/cdk-integration-tests/src/lib.rs index eacc3b3a..ce3ac33b 100644 --- a/crates/cdk-integration-tests/src/lib.rs +++ b/crates/cdk-integration-tests/src/lib.rs @@ -11,11 +11,11 @@ use cdk::cdk_lightning::MintLightning; use cdk::dhke::construct_proofs; use cdk::mint::FeeReserve; use cdk::nuts::{ - CurrencyUnit, Id, KeySet, MeltMethodSettings, MintInfo, MintMethodSettings, MintQuoteState, - Nuts, PaymentMethod, PreMintSecrets, Proofs, State, + CurrencyUnit, Id, KeySet, MeltMethodSettings, MintBolt11Request, MintInfo, MintMethodSettings, + MintQuoteBolt11Request, MintQuoteState, Nuts, PaymentMethod, PreMintSecrets, Proofs, State, }; use cdk::types::{LnKey, QuoteTTL}; -use cdk::wallet::client::HttpClient; +use cdk::wallet::client::{HttpClient, HttpClientMethods}; use cdk::{Mint, Wallet}; use cdk_fake_wallet::FakeWallet; use init_regtest::{get_mint_addr, get_mint_port, get_mint_url}; @@ -158,8 +158,14 @@ pub async fn mint_proofs( let wallet_client = HttpClient::new(); + let request = MintQuoteBolt11Request { + amount, + unit: CurrencyUnit::Sat, + description, + }; + let mint_quote = wallet_client - .post_mint_quote(mint_url.parse()?, 1.into(), CurrencyUnit::Sat, description) + .post_mint_quote(mint_url.parse()?, request) .await?; println!("Please pay: {}", mint_quote.request); @@ -179,13 +185,12 @@ pub async fn mint_proofs( let premint_secrets = PreMintSecrets::random(keyset_id, amount, &SplitTarget::default())?; - let mint_response = wallet_client - .post_mint( - mint_url.parse()?, - &mint_quote.quote, - premint_secrets.clone(), - ) - .await?; + let request = MintBolt11Request { + quote: mint_quote.quote, + outputs: premint_secrets.blinded_messages(), + }; + + let mint_response = wallet_client.post_mint(mint_url.parse()?, 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 9b6f3ddb..7b504444 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -5,8 +5,11 @@ use bip39::Mnemonic; use cdk::{ amount::SplitTarget, cdk_database::WalletMemoryDatabase, - nuts::{CurrencyUnit, MeltQuoteState, PreMintSecrets, State}, - wallet::{client::HttpClient, Wallet}, + nuts::{CurrencyUnit, MeltBolt11Request, MeltQuoteState, PreMintSecrets, State}, + wallet::{ + client::{HttpClient, HttpClientMethods}, + Wallet, + }, }; use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription}; use cdk_integration_tests::attempt_to_swap_pending; @@ -354,14 +357,13 @@ async fn test_fake_melt_change_in_quote() -> Result<()> { let client = HttpClient::new(); - let melt_response = client - .post_melt( - MINT_URL.parse()?, - melt_quote.id.clone(), - proofs.clone(), - Some(premint_secrets.blinded_messages()), - ) - .await?; + let melt_request = MeltBolt11Request { + quote: melt_quote.id.clone(), + inputs: proofs.clone(), + outputs: Some(premint_secrets.blinded_messages()), + }; + + let melt_response = client.post_melt(MINT_URL.parse()?, melt_request).await?; assert!(melt_response.change.is_some()); diff --git a/crates/cdk-integration-tests/tests/regtest.rs b/crates/cdk-integration-tests/tests/regtest.rs index c10d5ffe..0b1d194a 100644 --- a/crates/cdk-integration-tests/tests/regtest.rs +++ b/crates/cdk-integration-tests/tests/regtest.rs @@ -5,8 +5,13 @@ use bip39::Mnemonic; use cdk::{ amount::{Amount, SplitTarget}, cdk_database::WalletMemoryDatabase, - nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, PreMintSecrets, State}, - wallet::{client::HttpClient, Wallet}, + nuts::{ + CurrencyUnit, MeltQuoteState, MintBolt11Request, MintQuoteState, PreMintSecrets, State, + }, + wallet::{ + client::{HttpClient, HttpClientMethods}, + Wallet, + }, }; use cdk_integration_tests::init_regtest::{get_mint_url, init_cln_client, init_lnd_client}; use lightning_invoice::Bolt11Invoice; @@ -289,15 +294,16 @@ async fn test_cached_mint() -> Result<()> { let premint_secrets = PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap(); + let request = MintBolt11Request { + quote: quote.id, + outputs: premint_secrets.blinded_messages(), + }; + let response = http_client - .post_mint( - get_mint_url().as_str().parse()?, - "e.id, - premint_secrets.clone(), - ) + .post_mint(get_mint_url().as_str().parse()?, request.clone()) .await?; let response1 = http_client - .post_mint(get_mint_url().as_str().parse()?, "e.id, premint_secrets) + .post_mint(get_mint_url().as_str().parse()?, request) .await?; assert!(response == response1); diff --git a/crates/cdk/src/nuts/nut03.rs b/crates/cdk/src/nuts/nut03.rs index 535c89a9..cee5f063 100644 --- a/crates/cdk/src/nuts/nut03.rs +++ b/crates/cdk/src/nuts/nut03.rs @@ -32,11 +32,11 @@ pub struct PreSwap { pub fee: Amount, } -/// Split Request [NUT-06] +/// Swap Request [NUT-03] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))] pub struct SwapRequest { - /// Proofs that are to be spent in `Split` + /// Proofs that are to be spent in a `Swap` #[cfg_attr(feature = "swagger", schema(value_type = Vec))] pub inputs: Proofs, /// Blinded Messages for Mint to sign diff --git a/crates/cdk/src/wallet/client.rs b/crates/cdk/src/wallet/client.rs index d5b413ce..4296e6c2 100644 --- a/crates/cdk/src/wallet/client.rs +++ b/crates/cdk/src/wallet/client.rs @@ -1,5 +1,8 @@ //! Wallet client +use std::fmt::Debug; + +use async_trait::async_trait; use reqwest::Client; use serde_json::Value; use tracing::instrument; @@ -8,15 +11,12 @@ use url::Url; use super::Error; use crate::error::ErrorResponse; use crate::mint_url::MintUrl; -use crate::nuts::nut15::Mpp; use crate::nuts::{ - BlindedMessage, CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysResponse, - KeysetResponse, MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, - MintBolt11Request, MintBolt11Response, MintInfo, MintQuoteBolt11Request, - MintQuoteBolt11Response, PreMintSecrets, Proof, PublicKey, RestoreRequest, RestoreResponse, - SwapRequest, SwapResponse, + CheckStateRequest, CheckStateResponse, Id, KeySet, KeysResponse, KeysetResponse, + MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, + MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, RestoreRequest, + RestoreResponse, SwapRequest, SwapResponse, }; -use crate::{Amount, Bolt11Invoice}; /// Http Client #[derive(Debug, Clone)] @@ -67,10 +67,14 @@ impl HttpClient { Ok(Self { inner: client }) } +} +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl HttpClientMethods for HttpClient { /// Get Active Mint Keys [NUT-01] #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_mint_keys(&self, mint_url: MintUrl) -> Result, Error> { + async fn get_mint_keys(&self, mint_url: MintUrl) -> Result, Error> { let url = mint_url.join_paths(&["v1", "keys"])?; let keys = self.inner.get(url).send().await?.json::().await?; @@ -82,7 +86,7 @@ impl HttpClient { /// Get Keyset Keys [NUT-01] #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result { + 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()])?; let keys = self.inner.get(url).send().await?.json::().await?; @@ -94,7 +98,7 @@ impl HttpClient { /// Get Keysets [NUT-02] #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result { + async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result { let url = mint_url.join_paths(&["v1", "keysets"])?; let res = self.inner.get(url).send().await?.json::().await?; @@ -106,21 +110,13 @@ impl HttpClient { /// Mint Quote [NUT-04] #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn post_mint_quote( + async fn post_mint_quote( &self, mint_url: MintUrl, - amount: Amount, - unit: CurrencyUnit, - description: Option, + request: MintQuoteBolt11Request, ) -> Result { let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11"])?; - let request = MintQuoteBolt11Request { - amount, - unit, - description, - }; - let res = self .inner .post(url) @@ -141,7 +137,7 @@ impl HttpClient { /// Mint Quote status #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_mint_quote_status( + async fn get_mint_quote_status( &self, mint_url: MintUrl, quote_id: &str, @@ -160,20 +156,14 @@ impl HttpClient { } /// Mint Tokens [NUT-04] - #[instrument(skip(self, quote, premint_secrets), fields(mint_url = %mint_url))] - pub async fn post_mint( + #[instrument(skip(self, request), fields(mint_url = %mint_url))] + async fn post_mint( &self, mint_url: MintUrl, - quote: &str, - premint_secrets: PreMintSecrets, + request: MintBolt11Request, ) -> Result { let url = mint_url.join_paths(&["v1", "mint", "bolt11"])?; - let request = MintBolt11Request { - quote: quote.to_string(), - outputs: premint_secrets.blinded_messages(), - }; - let res = self .inner .post(url) @@ -191,23 +181,13 @@ impl HttpClient { /// Melt Quote [NUT-05] #[instrument(skip(self, request), fields(mint_url = %mint_url))] - pub async fn post_melt_quote( + async fn post_melt_quote( &self, mint_url: MintUrl, - unit: CurrencyUnit, - request: Bolt11Invoice, - mpp_amount: Option, + request: MeltQuoteBolt11Request, ) -> Result { let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11"])?; - let options = mpp_amount.map(|amount| Mpp { amount }); - - let request = MeltQuoteBolt11Request { - request, - unit, - options, - }; - let res = self .inner .post(url) @@ -225,7 +205,7 @@ impl HttpClient { /// Melt Quote Status #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_melt_quote_status( + async fn get_melt_quote_status( &self, mint_url: MintUrl, quote_id: &str, @@ -242,22 +222,14 @@ impl HttpClient { /// Melt [NUT-05] /// [Nut-08] Lightning fee return if outputs defined - #[instrument(skip(self, quote, inputs, outputs), fields(mint_url = %mint_url))] - pub async fn post_melt( + #[instrument(skip(self, request), fields(mint_url = %mint_url))] + async fn post_melt( &self, mint_url: MintUrl, - quote: String, - inputs: Vec, - outputs: Option>, + request: MeltBolt11Request, ) -> Result { let url = mint_url.join_paths(&["v1", "melt", "bolt11"])?; - let request = MeltBolt11Request { - quote, - inputs, - outputs, - }; - let res = self .inner .post(url) @@ -278,9 +250,9 @@ impl HttpClient { } } - /// Split Token [NUT-06] + /// Swap Token [NUT-03] #[instrument(skip(self, swap_request), fields(mint_url = %mint_url))] - pub async fn post_swap( + async fn post_swap( &self, mint_url: MintUrl, swap_request: SwapRequest, @@ -304,7 +276,7 @@ impl HttpClient { /// Get Mint Info [NUT-06] #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn get_mint_info(&self, mint_url: MintUrl) -> Result { + async fn get_mint_info(&self, mint_url: MintUrl) -> Result { let url = mint_url.join_paths(&["v1", "info"])?; let res = self.inner.get(url).send().await?.json::().await?; @@ -319,14 +291,13 @@ impl HttpClient { } /// Spendable check [NUT-07] - #[instrument(skip(self), fields(mint_url = %mint_url))] - pub async fn post_check_state( + #[instrument(skip(self, request), fields(mint_url = %mint_url))] + async fn post_check_state( &self, mint_url: MintUrl, - ys: Vec, + request: CheckStateRequest, ) -> Result { let url = mint_url.join_paths(&["v1", "checkstate"])?; - let request = CheckStateRequest { ys }; let res = self .inner @@ -345,7 +316,7 @@ impl HttpClient { /// Restore request [NUT-13] #[instrument(skip(self, request), fields(mint_url = %mint_url))] - pub async fn post_restore( + async fn post_restore( &self, mint_url: MintUrl, request: RestoreRequest, @@ -367,3 +338,84 @@ impl HttpClient { } } } + +/// Http Client Methods +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait HttpClientMethods: Debug { + /// Get Active Mint Keys [NUT-01] + async fn get_mint_keys(&self, mint_url: MintUrl) -> Result, Error>; + + /// Get Keyset Keys [NUT-01] + async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result; + + /// Get Keysets [NUT-02] + async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result; + + /// Mint Quote [NUT-04] + async fn post_mint_quote( + &self, + mint_url: MintUrl, + request: MintQuoteBolt11Request, + ) -> Result; + + /// Mint Quote status + async fn get_mint_quote_status( + &self, + mint_url: MintUrl, + quote_id: &str, + ) -> Result; + + /// 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; + + /// Melt Quote Status + async fn get_melt_quote_status( + &self, + mint_url: MintUrl, + quote_id: &str, + ) -> Result; + + /// Melt [NUT-05] + /// [Nut-08] Lightning fee return if outputs defined + async fn post_melt( + &self, + mint_url: MintUrl, + request: MeltBolt11Request, + ) -> Result; + + /// Split Token [NUT-06] + async fn post_swap( + &self, + mint_url: MintUrl, + request: SwapRequest, + ) -> Result; + + /// Get Mint Info [NUT-06] + async fn get_mint_info(&self, mint_url: MintUrl) -> 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; +} diff --git a/crates/cdk/src/wallet/melt.rs b/crates/cdk/src/wallet/melt.rs index c31f2ff4..5da40256 100644 --- a/crates/cdk/src/wallet/melt.rs +++ b/crates/cdk/src/wallet/melt.rs @@ -4,6 +4,7 @@ use lightning_invoice::Bolt11Invoice; use tracing::instrument; use crate::nuts::nut00::ProofsMethods; +use crate::nuts::{MeltBolt11Request, MeltQuoteBolt11Request, Mpp}; use crate::{ dhke::construct_proofs, nuts::{CurrencyUnit, MeltQuoteBolt11Response, PreMintSecrets, Proofs, State}, @@ -57,9 +58,17 @@ impl Wallet { _ => return Err(Error::UnitUnsupported), }; + let options = mpp.map(|amount| Mpp { amount }); + + let quote_request = MeltQuoteBolt11Request { + request: Bolt11Invoice::from_str(&request)?, + unit: self.unit, + options, + }; + let quote_res = self .client - .post_melt_quote(self.mint_url.clone(), self.unit, invoice, mpp) + .post_melt_quote(self.mint_url.clone(), quote_request) .await?; if quote_res.amount != amount { @@ -146,15 +155,13 @@ impl Wallet { proofs_total - quote_info.amount, )?; - let melt_response = self - .client - .post_melt( - self.mint_url.clone(), - quote_id.to_string(), - proofs.clone(), - Some(premint_secrets.blinded_messages()), - ) - .await; + let request = MeltBolt11Request { + quote: quote_id.to_string(), + inputs: proofs.clone(), + outputs: Some(premint_secrets.blinded_messages()), + }; + + let melt_response = self.client.post_melt(self.mint_url.clone(), 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 b429daaf..0246395d 100644 --- a/crates/cdk/src/wallet/mint.rs +++ b/crates/cdk/src/wallet/mint.rs @@ -2,6 +2,7 @@ use tracing::instrument; use super::MintQuote; use crate::nuts::nut00::ProofsMethods; +use crate::nuts::{MintBolt11Request, MintQuoteBolt11Request}; use crate::{ amount::SplitTarget, dhke::construct_proofs, @@ -64,9 +65,15 @@ impl Wallet { } } + let request = MintQuoteBolt11Request { + amount, + unit, + description, + }; + let quote_res = self .client - .post_mint_quote(mint_url.clone(), amount, unit, description) + .post_mint_quote(mint_url.clone(), request) .await?; let quote = MintQuote { @@ -212,9 +219,14 @@ impl Wallet { )?, }; + let request = MintBolt11Request { + quote: quote_id.to_string(), + outputs: premint_secrets.blinded_messages(), + }; + let mint_res = self .client - .post_mint(self.mint_url.clone(), quote_id, premint_secrets.clone()) + .post_mint(self.mint_url.clone(), 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 85045eaa..3658b6c1 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use bitcoin::bip32::Xpriv; use bitcoin::Network; +use client::HttpClientMethods; use tracing::instrument; use crate::amount::SplitTarget; @@ -55,7 +56,7 @@ pub struct Wallet { /// The targeted amount of proofs to have at each size pub target_proof_count: usize, xpriv: Xpriv, - client: HttpClient, + client: Arc, } impl Wallet { @@ -88,7 +89,7 @@ impl Wallet { Ok(Self { mint_url: MintUrl::from_str(mint_url)?, unit, - client: HttpClient::new(), + client: Arc::new(HttpClient::new()), localstore, xpriv, target_proof_count: target_proof_count.unwrap_or(3), @@ -96,8 +97,8 @@ impl Wallet { } /// Change HTTP client - pub fn set_client(&mut self, client: HttpClient) { - self.client = client; + pub fn set_client(&mut self, client: C) { + self.client = Arc::new(client); } /// Fee required for proof set diff --git a/crates/cdk/src/wallet/proofs.rs b/crates/cdk/src/wallet/proofs.rs index 12fed0ef..cb97f998 100644 --- a/crates/cdk/src/wallet/proofs.rs +++ b/crates/cdk/src/wallet/proofs.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use tracing::instrument; use crate::nuts::nut00::ProofsMethods; +use crate::nuts::CheckStateRequest; use crate::{ amount::SplitTarget, nuts::{Proof, ProofState, Proofs, PublicKey, SpendingConditions, State}, @@ -65,7 +66,7 @@ impl Wallet { let spendable = self .client - .post_check_state(self.mint_url.clone(), proof_ys) + .post_check_state(self.mint_url.clone(), CheckStateRequest { ys: proof_ys }) .await? .states; @@ -86,7 +87,10 @@ impl Wallet { pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result, Error> { let spendable = self .client - .post_check_state(self.mint_url.clone(), proofs.ys()?) + .post_check_state( + self.mint_url.clone(), + CheckStateRequest { ys: proofs.ys()? }, + ) .await?; let spent_ys: Vec<_> = spendable .states