Abstract HttpClient into HttpClientMethods trait (#429)

* Abstract HttpClientMethods trait
---------

Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
David Caseria
2024-11-04 14:14:40 -05:00
committed by GitHub
parent d9fb5f814a
commit 7eb3449710
10 changed files with 201 additions and 111 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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());

View File

@@ -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()?,
&quote.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()?, &quote.id, premint_secrets)
.post_mint(get_mint_url().as_str().parse()?, request)
.await?;
assert!(response == response1);

View File

@@ -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<Proof>))]
pub inputs: Proofs,
/// Blinded Messages for Mint to sign

View File

@@ -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<Vec<KeySet>, Error> {
async fn get_mint_keys(&self, mint_url: MintUrl) -> Result<Vec<KeySet>, Error> {
let url = mint_url.join_paths(&["v1", "keys"])?;
let keys = self.inner.get(url).send().await?.json::<Value>().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<KeySet, Error> {
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()])?;
let keys = self.inner.get(url).send().await?.json::<Value>().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<KeysetResponse, Error> {
async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result<KeysetResponse, Error> {
let url = mint_url.join_paths(&["v1", "keysets"])?;
let res = self.inner.get(url).send().await?.json::<Value>().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<String>,
request: MintQuoteBolt11Request,
) -> Result<MintQuoteBolt11Response, Error> {
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<MintBolt11Response, Error> {
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<Amount>,
request: MeltQuoteBolt11Request,
) -> Result<MeltQuoteBolt11Response, Error> {
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<Proof>,
outputs: Option<Vec<BlindedMessage>>,
request: MeltBolt11Request,
) -> Result<MeltQuoteBolt11Response, Error> {
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<MintInfo, Error> {
async fn get_mint_info(&self, mint_url: MintUrl) -> Result<MintInfo, Error> {
let url = mint_url.join_paths(&["v1", "info"])?;
let res = self.inner.get(url).send().await?.json::<Value>().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<PublicKey>,
request: CheckStateRequest,
) -> Result<CheckStateResponse, Error> {
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<Vec<KeySet>, Error>;
/// Get Keyset Keys [NUT-01]
async fn get_mint_keyset(&self, mint_url: MintUrl, keyset_id: Id) -> Result<KeySet, Error>;
/// Get Keysets [NUT-02]
async fn get_mint_keysets(&self, mint_url: MintUrl) -> Result<KeysetResponse, Error>;
/// Mint Quote [NUT-04]
async fn post_mint_quote(
&self,
mint_url: MintUrl,
request: MintQuoteBolt11Request,
) -> Result<MintQuoteBolt11Response, Error>;
/// Mint Quote status
async fn get_mint_quote_status(
&self,
mint_url: MintUrl,
quote_id: &str,
) -> Result<MintQuoteBolt11Response, Error>;
/// Mint Tokens [NUT-04]
async fn post_mint(
&self,
mint_url: MintUrl,
request: MintBolt11Request,
) -> Result<MintBolt11Response, Error>;
/// Melt Quote [NUT-05]
async fn post_melt_quote(
&self,
mint_url: MintUrl,
request: MeltQuoteBolt11Request,
) -> Result<MeltQuoteBolt11Response, Error>;
/// Melt Quote Status
async fn get_melt_quote_status(
&self,
mint_url: MintUrl,
quote_id: &str,
) -> Result<MeltQuoteBolt11Response, Error>;
/// Melt [NUT-05]
/// [Nut-08] Lightning fee return if outputs defined
async fn post_melt(
&self,
mint_url: MintUrl,
request: MeltBolt11Request,
) -> Result<MeltQuoteBolt11Response, Error>;
/// Split Token [NUT-06]
async fn post_swap(
&self,
mint_url: MintUrl,
request: SwapRequest,
) -> Result<SwapResponse, Error>;
/// Get Mint Info [NUT-06]
async fn get_mint_info(&self, mint_url: MintUrl) -> 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>;
}

View File

@@ -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,

View File

@@ -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?;

View File

@@ -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<dyn HttpClientMethods + Send + Sync>,
}
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<C: HttpClientMethods + 'static + Send + Sync>(&mut self, client: C) {
self.client = Arc::new(client);
}
/// Fee required for proof set

View File

@@ -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<Vec<ProofState>, 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