diff --git a/bindings/cdk-js/src/wallet.rs b/bindings/cdk-js/src/wallet.rs index 18791101..42634ce5 100644 --- a/bindings/cdk-js/src/wallet.rs +++ b/bindings/cdk-js/src/wallet.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; +use cdk::amount::SplitTarget; use cdk::nuts::{Proofs, SecretKey}; use cdk::url::UncheckedUrl; use cdk::wallet::Wallet; @@ -146,12 +147,20 @@ impl JsWallet { } #[wasm_bindgen(js_name = mint)] - pub async fn mint(&mut self, mint_url: String, quote_id: String) -> Result { + pub async fn mint( + &mut self, + mint_url: String, + quote_id: String, + split_target_amount: Option, + ) -> Result { + let target = split_target_amount + .map(|a| SplitTarget::Value(*a.deref())) + .unwrap_or_default(); let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?; Ok(self .inner - .mint(mint_url, "e_id) + .mint(mint_url, "e_id, target) .await .map_err(into_err)? .into()) @@ -192,12 +201,20 @@ impl JsWallet { } #[wasm_bindgen(js_name = melt)] - pub async fn melt(&mut self, mint_url: String, quote_id: String) -> Result { + pub async fn melt( + &mut self, + mint_url: String, + quote_id: String, + split_target_amount: Option, + ) -> Result { + let target = split_target_amount + .map(|a| SplitTarget::Value(*a.deref())) + .unwrap_or_default(); let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?; let melted = self .inner - .melt(&mint_url, "e_id) + .melt(&mint_url, "e_id, target) .await .map_err(into_err)?; @@ -216,12 +233,18 @@ impl JsWallet { Ok(self .inner - .receive(&encoded_token, None, signing_keys, preimages) + .receive( + &encoded_token, + &SplitTarget::default(), + signing_keys, + preimages, + ) .await .map_err(into_err)? .into()) } + #[allow(clippy::too_many_arguments)] #[wasm_bindgen(js_name = send)] pub async fn send( &mut self, @@ -231,6 +254,7 @@ impl JsWallet { amount: u64, p2pk_condition: Option, htlc_condition: Option, + split_target_amount: Option, ) -> Result { let conditions = match (p2pk_condition, htlc_condition) { (Some(_), Some(_)) => { @@ -245,19 +269,23 @@ impl JsWallet { let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?; + let target = split_target_amount + .map(|a| SplitTarget::Value(*a.deref())) + .unwrap_or_default(); self.inner .send( &mint_url, &unit.into(), memo, Amount::from(amount), - None, + &target, conditions, ) .await .map_err(into_err) } + #[allow(clippy::too_many_arguments)] #[wasm_bindgen(js_name = swap)] pub async fn swap( &mut self, @@ -267,6 +295,7 @@ impl JsWallet { input_proofs: Vec, p2pk_condition: Option, htlc_condition: Option, + split_target_amount: Option, ) -> Result { let conditions = match (p2pk_condition, htlc_condition) { (Some(_), Some(_)) => { @@ -283,13 +312,16 @@ impl JsWallet { let proofs: Proofs = input_proofs.iter().map(|p| p.deref()).cloned().collect(); + let target = split_target_amount + .map(|a| SplitTarget::Value(*a.deref())) + .unwrap_or_default(); let post_swap_proofs = self .inner .swap( &mint_url, &unit.into(), Some(Amount::from(amount)), - None, + &target, proofs, conditions, ) diff --git a/crates/cdk/src/amount.rs b/crates/cdk/src/amount.rs index 1e3782c8..35442721 100644 --- a/crates/cdk/src/amount.rs +++ b/crates/cdk/src/amount.rs @@ -24,18 +24,16 @@ impl Amount { /// Split into parts that are powers of two by target pub fn split_targeted(&self, target: &SplitTarget) -> Vec { - let mut parts = vec![]; - let mut parts_total = Amount::ZERO; - - match *target { - SplitTarget::None => { - parts = self.split(); - } + let mut parts = match *target { + SplitTarget::None => self.split(), SplitTarget::Value(amount) => { if self.le(&amount) { return self.split(); } + let mut parts_total = Amount::ZERO; + let mut parts = Vec::new(); + // The powers of two that are need to create target value let parts_of_value = amount.split(); @@ -55,8 +53,10 @@ impl Amount { } } } + + parts } - } + }; parts.sort(); parts @@ -68,10 +68,10 @@ impl Amount { Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize, )] pub enum SplitTarget { - /// Default target least amount of proofs + /// Default target; least amount of proofs #[default] None, - /// Tagrget amount for wallet to have most proofs that add up to value + /// Target amount for wallet to have most proofs that add up to value Value(Amount), } diff --git a/crates/cdk/src/nuts/nut00.rs b/crates/cdk/src/nuts/nut00.rs index 99fa0d60..4e2f38e6 100644 --- a/crates/cdk/src/nuts/nut00.rs +++ b/crates/cdk/src/nuts/nut00.rs @@ -356,8 +356,12 @@ pub struct PreMintSecrets { impl PreMintSecrets { /// Outputs for speceifed amount with random secret - pub fn random(keyset_id: Id, amount: Amount) -> Result { - let amount_split = amount.split(); + pub fn random( + keyset_id: Id, + amount: Amount, + amount_split_target: &SplitTarget, + ) -> Result { + let amount_split = amount.split_targeted(amount_split_target); let mut output = Vec::with_capacity(amount_split.len()); @@ -427,10 +431,10 @@ impl PreMintSecrets { pub fn with_conditions( keyset_id: Id, amount: Amount, - amount_split_target: SplitTarget, + amount_split_target: &SplitTarget, conditions: SpendingConditions, ) -> Result { - let amount_split = amount.split_targeted(&amount_split_target); + let amount_split = amount.split_targeted(amount_split_target); let mut output = Vec::with_capacity(amount_split.len()); diff --git a/crates/cdk/src/nuts/nut13.rs b/crates/cdk/src/nuts/nut13.rs index b4522939..763fe05f 100644 --- a/crates/cdk/src/nuts/nut13.rs +++ b/crates/cdk/src/nuts/nut13.rs @@ -7,6 +7,7 @@ use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey}; use super::nut00::{BlindedMessage, PreMint, PreMintSecrets}; use super::nut01::SecretKey; use super::nut02::Id; +use crate::amount::SplitTarget; use crate::dhke::blind_message; use crate::error::Error; use crate::secret::Secret; @@ -46,12 +47,13 @@ impl PreMintSecrets { xpriv: ExtendedPrivKey, amount: Amount, zero_amount: bool, + amount_split_target: &SplitTarget, ) -> Result { let mut pre_mint_secrets = PreMintSecrets::default(); let mut counter = counter; - for amount in amount.split() { + for amount in amount.split_targeted(amount_split_target) { let secret = Secret::from_xpriv(xpriv, keyset_id, counter)?; let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, counter)?; diff --git a/crates/cdk/src/wallet.rs b/crates/cdk/src/wallet.rs index ed371316..f6ad7a38 100644 --- a/crates/cdk/src/wallet.rs +++ b/crates/cdk/src/wallet.rs @@ -444,7 +444,9 @@ impl Wallet { .await?; if mint_quote_response.paid { - let amount = self.mint(mint_quote.mint_url, &mint_quote.id).await?; + let amount = self + .mint(mint_quote.mint_url, &mint_quote.id, SplitTarget::default()) + .await?; total_amount += amount; } else if mint_quote.expiry.le(&unix_time()) { self.localstore.remove_mint_quote(&mint_quote.id).await?; @@ -511,7 +513,12 @@ impl Wallet { /// Mint #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))] - pub async fn mint(&self, mint_url: UncheckedUrl, quote_id: &str) -> Result { + pub async fn mint( + &self, + mint_url: UncheckedUrl, + quote_id: &str, + amount_split_target: SplitTarget, + ) -> Result { // Check that mint is in store of mints if self.localstore.get_mint(mint_url.clone()).await?.is_none() { self.add_mint(mint_url.clone()).await?; @@ -544,6 +551,7 @@ impl Wallet { self.xpriv, quote_info.amount, false, + &amount_split_target, )?; let mint_res = self @@ -599,7 +607,7 @@ impl Wallet { mint_url: &UncheckedUrl, unit: &CurrencyUnit, amount: Option, - amount_split_target: Option, + amount_split_target: &SplitTarget, input_proofs: Proofs, spending_conditions: Option, ) -> Result, Error> { @@ -706,7 +714,7 @@ impl Wallet { mint_url: &UncheckedUrl, unit: &CurrencyUnit, amount: Option, - amount_split_target: Option, + amount_split_target: &SplitTarget, proofs: Proofs, spending_conditions: Option, ) -> Result { @@ -733,13 +741,14 @@ impl Wallet { self.xpriv, change_amount, false, + amount_split_target, )?; ( PreMintSecrets::with_conditions( active_keyset_id, desired_amount, - amount_split_target.unwrap_or_default(), + amount_split_target, conditions, )?, change_premint_secrets, @@ -759,6 +768,7 @@ impl Wallet { self.xpriv, desired_amount, false, + amount_split_target, )?; count += premint_secrets.len() as u32; @@ -769,6 +779,7 @@ impl Wallet { self.xpriv, change_amount, false, + amount_split_target, )?; (premint_secrets, change_premint_secrets) @@ -796,7 +807,7 @@ impl Wallet { unit: &CurrencyUnit, memo: Option, amount: Amount, - amount_split_target: Option, + amount_split_target: &SplitTarget, conditions: Option, ) -> Result { let input_proofs = self.select_proofs(mint_url.clone(), unit, amount).await?; @@ -955,7 +966,12 @@ impl Wallet { /// Melt #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))] - pub async fn melt(&mut self, mint_url: &UncheckedUrl, quote_id: &str) -> Result { + pub async fn melt( + &mut self, + mint_url: &UncheckedUrl, + quote_id: &str, + amount_split_target: SplitTarget, + ) -> Result { let quote_info = self.localstore.get_melt_quote(quote_id).await?; let quote_info = if let Some(quote) = quote_info { @@ -983,8 +999,14 @@ impl Wallet { let count = count.map_or(0, |c| c + 1); - let premint_secrets = - PreMintSecrets::from_xpriv(active_keyset_id, count, self.xpriv, proofs_amount, true)?; + let premint_secrets = PreMintSecrets::from_xpriv( + active_keyset_id, + count, + self.xpriv, + proofs_amount, + true, + &amount_split_target, + )?; let melt_response = self .client @@ -1045,7 +1067,7 @@ impl Wallet { pub async fn receive( &self, encoded_token: &str, - amount_split_target: Option, + amount_split_target: &SplitTarget, signing_keys: Option>, preimages: Option>, ) -> Result { @@ -1198,7 +1220,7 @@ impl Wallet { pub async fn nostr_receive( &self, nostr_signing_key: SecretKey, - amount_split_target: Option, + amount_split_target: SplitTarget, ) -> Result { use nostr_sdk::{Keys, Kind}; @@ -1247,7 +1269,7 @@ impl Wallet { match self .receive( token, - amount_split_target, + &amount_split_target, Some(vec![nostr_signing_key.clone()]), None, )