From 5c5fd10171f67aefcbc1d87a6fdfc977772ffc02 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Sat, 26 Oct 2024 21:39:34 +0100 Subject: [PATCH] feat: support custom unit --- bindings/cdk-js/src/types/melt_quote.rs | 2 +- bindings/cdk-js/src/types/mint_quote.rs | 2 +- .../cdk-cli/src/sub_commands/pay_request.rs | 6 +- crates/cdk-cln/src/lib.rs | 4 +- crates/cdk-fake-wallet/src/lib.rs | 4 +- .../cdk-integration-tests/src/init_regtest.rs | 1 + crates/cdk-integration-tests/src/lib.rs | 1 + .../tests/fake_wallet.rs | 16 ++--- crates/cdk-integration-tests/tests/mint.rs | 4 +- crates/cdk-lnbits/src/lib.rs | 4 +- crates/cdk-lnd/src/lib.rs | 4 +- crates/cdk-mintd/src/main.rs | 15 ++--- crates/cdk-phoenixd/src/lib.rs | 4 +- crates/cdk-strike/src/lib.rs | 10 ++-- crates/cdk/src/mint/keysets.rs | 13 ++++- crates/cdk/src/mint/melt.rs | 11 ++-- crates/cdk/src/mint/mint_nut04.rs | 10 ++-- crates/cdk/src/mint/mod.rs | 58 +++++++++++++------ crates/cdk/src/nuts/nut00/mod.rs | 41 +++++++------ crates/cdk/src/nuts/nut00/token.rs | 10 ++-- crates/cdk/src/nuts/nut04.rs | 4 +- crates/cdk/src/nuts/nut05.rs | 4 +- crates/cdk/src/nuts/nut09.rs | 3 + crates/cdk/src/nuts/nut18.rs | 4 +- crates/cdk/src/types.rs | 2 +- crates/cdk/src/wallet/balance.rs | 4 +- crates/cdk/src/wallet/melt.rs | 6 +- crates/cdk/src/wallet/mint.rs | 8 +-- crates/cdk/src/wallet/mod.rs | 7 ++- crates/cdk/src/wallet/multi_mint_wallet.rs | 10 ++-- crates/cdk/src/wallet/proofs.rs | 4 +- crates/cdk/src/wallet/receive.rs | 4 +- crates/cdk/src/wallet/send.rs | 7 ++- crates/cdk/src/wallet/swap.rs | 8 ++- flake.lock | 6 +- 35 files changed, 176 insertions(+), 125 deletions(-) diff --git a/bindings/cdk-js/src/types/melt_quote.rs b/bindings/cdk-js/src/types/melt_quote.rs index d6e6f3255..c00ac8b7a 100644 --- a/bindings/cdk-js/src/types/melt_quote.rs +++ b/bindings/cdk-js/src/types/melt_quote.rs @@ -33,7 +33,7 @@ impl JsMeltQuote { #[wasm_bindgen(getter)] pub fn unit(&self) -> JsCurrencyUnit { - self.inner.unit.into() + self.inner.unit.clone().into() } #[wasm_bindgen(getter)] diff --git a/bindings/cdk-js/src/types/mint_quote.rs b/bindings/cdk-js/src/types/mint_quote.rs index fed36a925..ebc7f0d77 100644 --- a/bindings/cdk-js/src/types/mint_quote.rs +++ b/bindings/cdk-js/src/types/mint_quote.rs @@ -33,7 +33,7 @@ impl JsMintQuote { #[wasm_bindgen(getter)] pub fn unit(&self) -> JsCurrencyUnit { - self.inner.unit.into() + self.inner.unit.clone().into() } #[wasm_bindgen(getter)] diff --git a/crates/cdk-cli/src/sub_commands/pay_request.rs b/crates/cdk-cli/src/sub_commands/pay_request.rs index 1498f9804..308df1d0e 100644 --- a/crates/cdk-cli/src/sub_commands/pay_request.rs +++ b/crates/cdk-cli/src/sub_commands/pay_request.rs @@ -21,7 +21,7 @@ pub async fn pay_request( ) -> Result<()> { let payment_request = &sub_command_args.payment_request; - let unit = payment_request.unit; + let unit = &payment_request.unit; let amount = match payment_request.amount { Some(amount) => amount, @@ -56,7 +56,7 @@ pub async fn pay_request( } if let Some(unit) = unit { - if wallet.unit != unit { + if &wallet.unit != unit { continue; } } @@ -97,7 +97,7 @@ pub async fn pay_request( id: payment_request.payment_id.clone(), memo: None, mint: matching_wallet.mint_url.clone(), - unit: matching_wallet.unit, + unit: matching_wallet.unit.clone(), proofs, }; diff --git a/crates/cdk-cln/src/lib.rs b/crates/cdk-cln/src/lib.rs index 1e8f609c4..49bdc5c44 100644 --- a/crates/cdk-cln/src/lib.rs +++ b/crates/cdk-cln/src/lib.rs @@ -81,8 +81,8 @@ impl MintLightning for Cln { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-fake-wallet/src/lib.rs b/crates/cdk-fake-wallet/src/lib.rs index e0fba0727..6c66466dd 100644 --- a/crates/cdk-fake-wallet/src/lib.rs +++ b/crates/cdk-fake-wallet/src/lib.rs @@ -112,8 +112,8 @@ impl MintLightning for FakeWallet { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-integration-tests/src/init_regtest.rs b/crates/cdk-integration-tests/src/init_regtest.rs index 769a33500..bae3755ac 100644 --- a/crates/cdk-integration-tests/src/init_regtest.rs +++ b/crates/cdk-integration-tests/src/init_regtest.rs @@ -176,6 +176,7 @@ where Arc::new(database), ln_backends, supported_units, + HashMap::new(), ) .await?; diff --git a/crates/cdk-integration-tests/src/lib.rs b/crates/cdk-integration-tests/src/lib.rs index ce3ac33bd..31e457667 100644 --- a/crates/cdk-integration-tests/src/lib.rs +++ b/crates/cdk-integration-tests/src/lib.rs @@ -82,6 +82,7 @@ pub async fn start_mint( Arc::new(MintMemoryDatabase::default()), ln_backends.clone(), supported_units, + HashMap::new(), ) .await?; let cache_time_to_live = 3600; diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index 7b5044447..e1eee4025 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -368,16 +368,12 @@ async fn test_fake_melt_change_in_quote() -> Result<()> { assert!(melt_response.change.is_some()); let check = wallet.melt_quote_status(&melt_quote.id).await?; + let mut melt_change = melt_response.change.unwrap(); + melt_change.sort_by(|a, b| a.amount.cmp(&b.amount)); - assert_eq!( - melt_response - .change - .unwrap() - .sort_by(|a, b| a.amount.cmp(&b.amount)), - check - .change - .unwrap() - .sort_by(|a, b| a.amount.cmp(&b.amount)) - ); + let mut check = check.change.unwrap(); + check.sort_by(|a, b| a.amount.cmp(&b.amount)); + + assert_eq!(melt_change, check); Ok(()) } diff --git a/crates/cdk-integration-tests/tests/mint.rs b/crates/cdk-integration-tests/tests/mint.rs index c86e2dd31..e2ddb6ada 100644 --- a/crates/cdk-integration-tests/tests/mint.rs +++ b/crates/cdk-integration-tests/tests/mint.rs @@ -48,6 +48,7 @@ async fn new_mint(fee: u64) -> Mint { Arc::new(MintMemoryDatabase::default()), HashMap::new(), supported_units, + HashMap::new(), ) .await .unwrap() @@ -270,7 +271,8 @@ async fn test_swap_unbalanced() -> Result<()> { async fn test_swap_overpay_underpay_fee() -> Result<()> { let mint = new_mint(1).await; - mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1).await?; + mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, HashMap::new()) + .await?; let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys; let keyset_id = Id::from(&keys); diff --git a/crates/cdk-lnbits/src/lib.rs b/crates/cdk-lnbits/src/lib.rs index 64e7bef7e..21103d12d 100644 --- a/crates/cdk-lnbits/src/lib.rs +++ b/crates/cdk-lnbits/src/lib.rs @@ -80,8 +80,8 @@ impl MintLightning for LNbits { Settings { mpp: false, unit: CurrencyUnit::Sat, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-lnd/src/lib.rs b/crates/cdk-lnd/src/lib.rs index c5ecbeb8f..b2319004e 100644 --- a/crates/cdk-lnd/src/lib.rs +++ b/crates/cdk-lnd/src/lib.rs @@ -88,8 +88,8 @@ impl MintLightning for Lnd { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-mintd/src/main.rs b/crates/cdk-mintd/src/main.rs index 4adc2809d..e72f44cc5 100644 --- a/crates/cdk-mintd/src/main.rs +++ b/crates/cdk-mintd/src/main.rs @@ -188,7 +188,7 @@ async fn main() -> anyhow::Result<()> { api_key.clone(), MintMethodSettings::default(), MeltMethodSettings::default(), - unit, + unit.clone(), Arc::new(Mutex::new(Some(receiver))), webhook_url.to_string(), ) @@ -199,7 +199,7 @@ async fn main() -> anyhow::Result<()> { .await?; routers.push(router); - let ln_key = LnKey::new(unit, PaymentMethod::Bolt11); + let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); ln_backends.insert(ln_key, Arc::new(strike)); @@ -237,7 +237,7 @@ async fn main() -> anyhow::Result<()> { let unit = CurrencyUnit::Sat; - let ln_key = LnKey::new(unit, PaymentMethod::Bolt11); + let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); ln_backends.insert(ln_key, Arc::new(lnbits)); @@ -326,7 +326,7 @@ async fn main() -> anyhow::Result<()> { let units = settings.fake_wallet.unwrap_or_default().supported_units; for unit in units { - let ln_key = LnKey::new(unit, PaymentMethod::Bolt11); + let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); let wallet = Arc::new(FakeWallet::new( fee_reserve.clone(), @@ -361,13 +361,13 @@ async fn main() -> anyhow::Result<()> { let m = MppMethodSettings { method: key.method, - unit: key.unit, + unit: key.unit.clone(), mpp: settings.mpp, }; let n4 = MintMethodSettings { method: key.method, - unit: key.unit, + unit: key.unit.clone(), min_amount: settings.mint_settings.min_amount, max_amount: settings.mint_settings.max_amount, description: settings.invoice_description, @@ -375,7 +375,7 @@ async fn main() -> anyhow::Result<()> { let n5 = MeltMethodSettings { method: key.method, - unit: key.unit, + unit: key.unit.clone(), min_amount: settings.melt_settings.min_amount, max_amount: settings.melt_settings.max_amount, }; @@ -438,6 +438,7 @@ async fn main() -> anyhow::Result<()> { localstore, ln_backends.clone(), supported_units, + HashMap::new(), ) .await?; diff --git a/crates/cdk-phoenixd/src/lib.rs b/crates/cdk-phoenixd/src/lib.rs index 4aecfac6b..16197f65d 100644 --- a/crates/cdk-phoenixd/src/lib.rs +++ b/crates/cdk-phoenixd/src/lib.rs @@ -86,8 +86,8 @@ impl MintLightning for Phoenixd { Settings { mpp: false, unit: CurrencyUnit::Sat, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-strike/src/lib.rs b/crates/cdk-strike/src/lib.rs index 76eb0f256..7273112e3 100644 --- a/crates/cdk-strike/src/lib.rs +++ b/crates/cdk-strike/src/lib.rs @@ -77,9 +77,9 @@ impl MintLightning for Strike { fn get_settings(&self) -> Settings { Settings { mpp: false, - unit: self.unit, - mint_settings: self.mint_settings, - melt_settings: self.melt_settings, + unit: self.unit.clone(), + mint_settings: self.mint_settings.clone(), + melt_settings: self.melt_settings.clone(), invoice_description: true, } } @@ -288,7 +288,7 @@ impl MintLightning for Strike { payment_preimage: None, status: state, total_spent: from_strike_amount(invoice.total_amount, &self.unit)?.into(), - unit: self.unit, + unit: self.unit.clone(), } } Err(err) => match err { @@ -297,7 +297,7 @@ impl MintLightning for Strike { payment_preimage: None, status: MeltQuoteState::Unknown, total_spent: Amount::ZERO, - unit: self.unit, + unit: self.unit.clone(), }, _ => { return Err(Error::from(err).into()); diff --git a/crates/cdk/src/mint/keysets.rs b/crates/cdk/src/mint/keysets.rs index 3c9642be3..66445608d 100644 --- a/crates/cdk/src/mint/keysets.rs +++ b/crates/cdk/src/mint/keysets.rs @@ -1,5 +1,6 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; +use bitcoin::bip32::DerivationPath; use tracing::instrument; use crate::Error; @@ -89,14 +90,20 @@ impl Mint { derivation_path_index: u32, max_order: u8, input_fee_ppk: u64, + custom_paths: HashMap, ) -> Result<(), Error> { - let derivation_path = derivation_path_from_unit(unit, derivation_path_index); + let derivation_path = match custom_paths.get(&unit) { + Some(path) => path.clone(), + None => derivation_path_from_unit(unit.clone(), derivation_path_index) + .ok_or(Error::UnsupportedUnit)?, + }; + let (keyset, keyset_info) = create_new_keyset( &self.secp_ctx, self.xpriv, derivation_path, Some(derivation_path_index), - unit, + unit.clone(), max_order, input_fee_ppk, ); diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index 53268b4d2..abe2473b0 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -74,11 +74,11 @@ impl Mint { } }; - self.check_melt_request_acceptable(amount, *unit, PaymentMethod::Bolt11)?; + self.check_melt_request_acceptable(amount, unit.clone(), PaymentMethod::Bolt11)?; let ln = self .ln - .get(&LnKey::new(*unit, PaymentMethod::Bolt11)) + .get(&LnKey::new(unit.clone(), PaymentMethod::Bolt11)) .ok_or_else(|| { tracing::info!("Could not get ln backend for {}, bolt11 ", unit); @@ -97,7 +97,7 @@ impl Mint { let quote = MeltQuote::new( request.to_string(), - *unit, + unit.clone(), payment_quote.amount, payment_quote.fee, unix_time() + self.quote_ttl.melt_ttl, @@ -447,7 +447,10 @@ impl Mint { } _ => None, }; - let ln = match self.ln.get(&LnKey::new(quote.unit, PaymentMethod::Bolt11)) { + let ln = match self + .ln + .get(&LnKey::new(quote.unit.clone(), PaymentMethod::Bolt11)) + { Some(ln) => ln, None => { tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit); diff --git a/crates/cdk/src/mint/mint_nut04.rs b/crates/cdk/src/mint/mint_nut04.rs index 7d46263db..1f9d64a40 100644 --- a/crates/cdk/src/mint/mint_nut04.rs +++ b/crates/cdk/src/mint/mint_nut04.rs @@ -12,7 +12,7 @@ impl Mint { fn check_mint_request_acceptable( &self, amount: Amount, - unit: CurrencyUnit, + unit: &CurrencyUnit, ) -> Result<(), Error> { let nut04 = &self.mint_info.nuts.nut04; @@ -20,7 +20,7 @@ impl Mint { return Err(Error::MintingDisabled); } - match nut04.get_settings(&unit, &PaymentMethod::Bolt11) { + match nut04.get_settings(unit, &PaymentMethod::Bolt11) { Some(settings) => { if settings .max_amount @@ -64,11 +64,11 @@ impl Mint { description, } = mint_quote_request; - self.check_mint_request_acceptable(amount, unit)?; + self.check_mint_request_acceptable(amount, &unit)?; let ln = self .ln - .get(&LnKey::new(unit, PaymentMethod::Bolt11)) + .get(&LnKey::new(unit.clone(), PaymentMethod::Bolt11)) .ok_or_else(|| { tracing::info!("Bolt11 mint request for unsupported unit"); @@ -98,7 +98,7 @@ impl Mint { let quote = MintQuote::new( self.mint_url.clone(), create_invoice_response.request.to_string(), - unit, + unit.clone(), amount, create_invoice_response.expiry.unwrap_or(0), create_invoice_response.request_lookup_id.clone(), diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 0699cf7f7..7cc3837c1 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -54,6 +54,7 @@ pub struct Mint { impl Mint { /// Create new [`Mint`] + #[allow(clippy::too_many_arguments)] pub async fn new( mint_url: &str, seed: &[u8], @@ -63,6 +64,7 @@ impl Mint { ln: HashMap + Send + Sync>>, // Hashmap where the key is the unit and value is (input fee ppk, max_order) supported_units: HashMap, + custom_paths: HashMap, ) -> Result { let secp_ctx = Secp256k1::new(); let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted"); @@ -83,7 +85,7 @@ impl Mint { let keysets_by_unit: HashMap> = keysets_infos.iter().fold(HashMap::new(), |mut acc, ks| { - acc.entry(ks.unit).or_default().push(ks.clone()); + acc.entry(ks.unit.clone()).or_default().push(ks.clone()); acc }); @@ -112,7 +114,7 @@ impl Mint { &secp_ctx, xpriv, highest_index_keyset.max_order, - highest_index_keyset.unit, + highest_index_keyset.unit.clone(), highest_index_keyset.derivation_path.clone(), ); active_keysets.insert(id, keyset); @@ -125,37 +127,46 @@ impl Mint { highest_index_keyset.derivation_path_index.unwrap_or(0) + 1 }; - let derivation_path = derivation_path_from_unit(unit, derivation_path_index); + let derivation_path = match custom_paths.get(&unit) { + Some(path) => path.clone(), + None => derivation_path_from_unit(unit.clone(), derivation_path_index) + .ok_or(Error::UnsupportedUnit)?, + }; let (keyset, keyset_info) = create_new_keyset( &secp_ctx, xpriv, derivation_path, Some(derivation_path_index), - unit, + unit.clone(), *max_order, *input_fee_ppk, ); let id = keyset_info.id; localstore.add_keyset_info(keyset_info).await?; - localstore.set_active_keyset(unit, id).await?; + localstore.set_active_keyset(unit.clone(), id).await?; active_keysets.insert(id, keyset); - active_keyset_units.push(unit); + active_keyset_units.push(unit.clone()); } } } for (unit, (fee, max_order)) in supported_units { if !active_keyset_units.contains(&unit) { - let derivation_path = derivation_path_from_unit(unit, 0); + let derivation_path = match custom_paths.get(&unit) { + Some(path) => path.clone(), + None => { + derivation_path_from_unit(unit.clone(), 0).ok_or(Error::UnsupportedUnit)? + } + }; let (keyset, keyset_info) = create_new_keyset( &secp_ctx, xpriv, derivation_path, Some(0), - unit, + unit.clone(), max_order, fee, ); @@ -194,7 +205,7 @@ impl Mint { let mint = Arc::clone(&mint_arc); let ln = Arc::clone(ln); let shutdown = Arc::clone(&shutdown); - let key = *key; + let key = key.clone(); join_set.spawn(async move { if !ln.is_wait_invoice_active() { loop { @@ -438,7 +449,8 @@ impl Mint { Ok(RestoreResponse { outputs, - signatures, + signatures: signatures.clone(), + promises: Some(signatures), }) } @@ -559,7 +571,7 @@ fn create_new_keyset( ); let keyset_info = MintKeySetInfo { id: keyset.id, - unit: keyset.unit, + unit: keyset.unit.clone(), active: true, valid_from: unix_time(), valid_to: None, @@ -571,12 +583,17 @@ fn create_new_keyset( (keyset, keyset_info) } -fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> DerivationPath { - DerivationPath::from(vec![ +fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> Option { + let unit_index = match unit.derivation_index() { + Some(index) => index, + None => return None, + }; + + Some(DerivationPath::from(vec![ ChildNumber::from_hardened_idx(0).expect("0 is a valid index"), - ChildNumber::from_hardened_idx(unit.derivation_index()).expect("0 is a valid index"), + ChildNumber::from_hardened_idx(unit_index).expect("0 is a valid index"), ChildNumber::from_hardened_idx(index).expect("0 is a valid index"), - ]) + ])) } #[cfg(test)] @@ -598,7 +615,7 @@ mod tests { seed, 2, CurrencyUnit::Sat, - derivation_path_from_unit(CurrencyUnit::Sat, 0), + derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(), ); assert_eq!(keyset.unit, CurrencyUnit::Sat); @@ -642,7 +659,7 @@ mod tests { xpriv, 2, CurrencyUnit::Sat, - derivation_path_from_unit(CurrencyUnit::Sat, 0), + derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(), ); assert_eq!(keyset.unit, CurrencyUnit::Sat); @@ -722,6 +739,7 @@ mod tests { localstore, HashMap::new(), config.supported_units, + HashMap::new(), ) .await } @@ -777,7 +795,8 @@ mod tests { assert!(keysets.keysets.is_empty()); // generate the first keyset and set it to active - mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1).await?; + mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1, HashMap::new()) + .await?; let keysets = mint.keysets().await.unwrap(); assert!(keysets.keysets.len().eq(&1)); @@ -785,7 +804,8 @@ mod tests { let first_keyset_id = keysets.keysets[0].id; // set the first keyset to inactive and generate a new keyset - mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1).await?; + mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1, HashMap::new()) + .await?; let keysets = mint.keysets().await.unwrap(); diff --git a/crates/cdk/src/nuts/nut00/mod.rs b/crates/cdk/src/nuts/nut00/mod.rs index d6c3dffdb..929b0c058 100644 --- a/crates/cdk/src/nuts/nut00/mod.rs +++ b/crates/cdk/src/nuts/nut00/mod.rs @@ -361,7 +361,7 @@ where /// Currency Unit #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))] pub enum CurrencyUnit { /// Sat @@ -373,17 +373,20 @@ pub enum CurrencyUnit { Usd, /// Euro Eur, + /// Custom currency unit + Custom(String), } #[cfg(feature = "mint")] impl CurrencyUnit { /// Derivation index mint will use for unit - pub fn derivation_index(&self) -> u32 { + pub fn derivation_index(&self) -> Option { match self { - Self::Sat => 0, - Self::Msat => 1, - Self::Usd => 2, - Self::Eur => 3, + Self::Sat => Some(0), + Self::Msat => Some(1), + Self::Usd => Some(2), + Self::Eur => Some(3), + _ => None, } } } @@ -391,12 +394,13 @@ impl CurrencyUnit { impl FromStr for CurrencyUnit { type Err = Error; fn from_str(value: &str) -> Result { - match value { - "sat" => Ok(Self::Sat), - "msat" => Ok(Self::Msat), - "usd" => Ok(Self::Usd), - "eur" => Ok(Self::Eur), - _ => Err(Error::UnsupportedUnit), + let value = &value.to_uppercase(); + match value.as_str() { + "SAT" => Ok(Self::Sat), + "MSAT" => Ok(Self::Msat), + "USD" => Ok(Self::Usd), + "EUR" => Ok(Self::Eur), + c => Ok(Self::Custom(c.to_string())), } } } @@ -404,15 +408,16 @@ impl FromStr for CurrencyUnit { impl fmt::Display for CurrencyUnit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { - CurrencyUnit::Sat => "sat", - CurrencyUnit::Msat => "msat", - CurrencyUnit::Usd => "usd", - CurrencyUnit::Eur => "eur", + CurrencyUnit::Sat => "SAT", + CurrencyUnit::Msat => "MSAT", + CurrencyUnit::Usd => "USD", + CurrencyUnit::Eur => "EUR", + CurrencyUnit::Custom(unit) => unit, }; if let Some(width) = f.width() { - write!(f, "{:width$}", s, width = width) + write!(f, "{:width$}", s.to_lowercase(), width = width) } else { - write!(f, "{}", s) + write!(f, "{}", s.to_lowercase()) } } } diff --git a/crates/cdk/src/nuts/nut00/token.rs b/crates/cdk/src/nuts/nut00/token.rs index 96e15302a..dccd695ba 100644 --- a/crates/cdk/src/nuts/nut00/token.rs +++ b/crates/cdk/src/nuts/nut00/token.rs @@ -92,8 +92,8 @@ impl Token { /// Unit pub fn unit(&self) -> Option { match self { - Self::TokenV3(token) => *token.unit(), - Self::TokenV4(token) => Some(token.unit()), + Self::TokenV3(token) => token.unit().clone(), + Self::TokenV4(token) => Some(token.unit().clone()), } } @@ -326,8 +326,8 @@ impl TokenV4 { /// Unit #[inline] - pub fn unit(&self) -> CurrencyUnit { - self.unit + pub fn unit(&self) -> &CurrencyUnit { + &self.unit } } @@ -525,7 +525,7 @@ mod tests { token.token[0].proofs[0].clone().keyset_id, Id::from_str("009a1f293253e41e").unwrap() ); - assert_eq!(token.unit.unwrap(), CurrencyUnit::Sat); + assert_eq!(token.unit.clone().unwrap(), CurrencyUnit::Sat); let encoded = &token.to_string(); diff --git a/crates/cdk/src/nuts/nut04.rs b/crates/cdk/src/nuts/nut04.rs index 207a7a9a3..40a6f8d4b 100644 --- a/crates/cdk/src/nuts/nut04.rs +++ b/crates/cdk/src/nuts/nut04.rs @@ -137,7 +137,7 @@ pub struct MintBolt11Response { } /// Mint Method Settings -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))] pub struct MintMethodSettings { /// Payment Method e.g. bolt11 @@ -179,7 +179,7 @@ impl Settings { ) -> Option { for method_settings in self.methods.iter() { if method_settings.method.eq(method) && method_settings.unit.eq(unit) { - return Some(*method_settings); + return Some(method_settings.clone()); } } diff --git a/crates/cdk/src/nuts/nut05.rs b/crates/cdk/src/nuts/nut05.rs index e7ac3153a..2177cd21c 100644 --- a/crates/cdk/src/nuts/nut05.rs +++ b/crates/cdk/src/nuts/nut05.rs @@ -235,7 +235,7 @@ impl MeltBolt11Request { } /// Melt Method Settings -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))] pub struct MeltMethodSettings { /// Payment Method e.g. bolt11 @@ -264,7 +264,7 @@ impl Settings { ) -> Option { for method_settings in self.methods.iter() { if method_settings.method.eq(method) && method_settings.unit.eq(unit) { - return Some(*method_settings); + return Some(method_settings.clone()); } } diff --git a/crates/cdk/src/nuts/nut09.rs b/crates/cdk/src/nuts/nut09.rs index abcd8f85a..5f04045ff 100644 --- a/crates/cdk/src/nuts/nut09.rs +++ b/crates/cdk/src/nuts/nut09.rs @@ -22,6 +22,9 @@ pub struct RestoreResponse { pub outputs: Vec, /// Signatures pub signatures: Vec, + /// Promises + // Temp compatibility with cashu-ts + pub promises: Option>, } mod test { diff --git a/crates/cdk/src/nuts/nut18.rs b/crates/cdk/src/nuts/nut18.rs index d165b1484..cb19735eb 100644 --- a/crates/cdk/src/nuts/nut18.rs +++ b/crates/cdk/src/nuts/nut18.rs @@ -154,7 +154,7 @@ mod tests { assert_eq!(&req.payment_id.unwrap(), "b7a90176"); assert_eq!(req.amount.unwrap(), 10.into()); - assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat); + assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat); assert_eq!( req.mints.unwrap(), vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?] @@ -190,7 +190,7 @@ mod tests { assert_eq!(&req.payment_id.unwrap(), "b7a90176"); assert_eq!(req.amount.unwrap(), 10.into()); - assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat); + assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat); assert_eq!( req.mints.unwrap(), vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?] diff --git a/crates/cdk/src/types.rs b/crates/cdk/src/types.rs index 213a8e7ef..e3d91eca4 100644 --- a/crates/cdk/src/types.rs +++ b/crates/cdk/src/types.rs @@ -141,7 +141,7 @@ impl ProofInfo { /// Key used in hashmap of ln backends to identify what unit and payment method /// it is for -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct LnKey { /// Unit of Payment backend pub unit: CurrencyUnit, diff --git a/crates/cdk/src/wallet/balance.rs b/crates/cdk/src/wallet/balance.rs index 6d1276462..46c814acd 100644 --- a/crates/cdk/src/wallet/balance.rs +++ b/crates/cdk/src/wallet/balance.rs @@ -19,7 +19,7 @@ impl Wallet { // TODO If only the proofs for this wallet's unit are retrieved, why build a map with key = unit? let balances = proofs.iter().fold(HashMap::new(), |mut acc, proof| { - *acc.entry(self.unit).or_insert(Amount::ZERO) += proof.amount; + *acc.entry(self.unit.clone()).or_insert(Amount::ZERO) += proof.amount; acc }); @@ -33,7 +33,7 @@ impl Wallet { // TODO If only the proofs for this wallet's unit are retrieved, why build a map with key = unit? let balances = proofs.iter().fold(HashMap::new(), |mut acc, proof| { - *acc.entry(self.unit).or_insert(Amount::ZERO) += proof.amount; + *acc.entry(self.unit.clone()).or_insert(Amount::ZERO) += proof.amount; acc }); diff --git a/crates/cdk/src/wallet/melt.rs b/crates/cdk/src/wallet/melt.rs index 5da402562..4948ecd3d 100644 --- a/crates/cdk/src/wallet/melt.rs +++ b/crates/cdk/src/wallet/melt.rs @@ -62,7 +62,7 @@ impl Wallet { let quote_request = MeltQuoteBolt11Request { request: Bolt11Invoice::from_str(&request)?, - unit: self.unit, + unit: self.unit.clone(), options, }; @@ -79,7 +79,7 @@ impl Wallet { id: quote_res.quote, amount, request, - unit: self.unit, + unit: self.unit.clone(), fee_reserve: quote_res.fee_reserve, state: quote_res.state, expiry: quote_res.expiry, @@ -233,7 +233,7 @@ impl Wallet { proof, self.mint_url.clone(), State::Unspent, - quote_info.unit, + quote_info.unit.clone(), ) }) .collect::, _>>()? diff --git a/crates/cdk/src/wallet/mint.rs b/crates/cdk/src/wallet/mint.rs index 0246395d7..743044f07 100644 --- a/crates/cdk/src/wallet/mint.rs +++ b/crates/cdk/src/wallet/mint.rs @@ -46,7 +46,7 @@ impl Wallet { description: Option, ) -> Result { let mint_url = self.mint_url.clone(); - let unit = self.unit; + let unit = self.unit.clone(); // If we have a description, we check that the mint supports it. if description.is_some() { @@ -67,7 +67,7 @@ impl Wallet { let request = MintQuoteBolt11Request { amount, - unit, + unit: unit.clone(), description, }; @@ -80,7 +80,7 @@ impl Wallet { mint_url, id: quote_res.quote.clone(), amount, - unit, + unit: unit.clone(), request: quote_res.request, state: quote_res.state, expiry: quote_res.expiry.unwrap_or(0), @@ -269,7 +269,7 @@ impl Wallet { proof, self.mint_url.clone(), State::Unspent, - quote_info.unit, + quote_info.unit.clone(), ) }) .collect::, _>>()?; diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index 3658b6c11..7b8de8687 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -330,7 +330,12 @@ impl Wallet { let unspent_proofs = unspent_proofs .into_iter() .map(|proof| { - ProofInfo::new(proof, self.mint_url.clone(), State::Unspent, keyset.unit) + ProofInfo::new( + proof, + self.mint_url.clone(), + State::Unspent, + keyset.unit.clone(), + ) }) .collect::, _>>()?; diff --git a/crates/cdk/src/wallet/multi_mint_wallet.rs b/crates/cdk/src/wallet/multi_mint_wallet.rs index 8d26e9e46..b0f048b2e 100644 --- a/crates/cdk/src/wallet/multi_mint_wallet.rs +++ b/crates/cdk/src/wallet/multi_mint_wallet.rs @@ -55,7 +55,7 @@ impl MultiMintWallet { wallets: Arc::new(Mutex::new( wallets .into_iter() - .map(|w| (WalletKey::new(w.mint_url.clone(), w.unit), w)) + .map(|w| (WalletKey::new(w.mint_url.clone(), w.unit.clone()), w)) .collect(), )), } @@ -64,7 +64,7 @@ impl MultiMintWallet { /// Add wallet to MultiMintWallet #[instrument(skip(self, wallet))] pub async fn add_wallet(&self, wallet: Wallet) { - let wallet_key = WalletKey::new(wallet.mint_url.clone(), wallet.unit); + let wallet_key = WalletKey::new(wallet.mint_url.clone(), wallet.unit.clone()); let mut wallets = self.wallets.lock().await; @@ -126,7 +126,7 @@ impl MultiMintWallet { for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() { let wallet_proofs = wallet.get_unspent_proofs().await?; - mint_proofs.insert(mint_url.clone(), (wallet_proofs, *u)); + mint_proofs.insert(mint_url.clone(), (wallet_proofs, u.clone())); } Ok(mint_proofs) } @@ -198,7 +198,7 @@ impl MultiMintWallet { let amount = wallet.check_all_mint_quotes().await?; amount_minted - .entry(wallet.unit) + .entry(wallet.unit.clone()) .and_modify(|b| *b += amount) .or_insert(amount); } @@ -246,7 +246,7 @@ impl MultiMintWallet { let mint_url = token_data.mint_url()?; // Check that all mints in tokes have wallets - let wallet_key = WalletKey::new(mint_url.clone(), unit); + let wallet_key = WalletKey::new(mint_url.clone(), unit.clone()); if !self.has(&wallet_key).await { return Err(Error::UnknownWallet(wallet_key.clone())); } diff --git a/crates/cdk/src/wallet/proofs.rs b/crates/cdk/src/wallet/proofs.rs index cb97f9984..fd67015ab 100644 --- a/crates/cdk/src/wallet/proofs.rs +++ b/crates/cdk/src/wallet/proofs.rs @@ -41,7 +41,7 @@ impl Wallet { .localstore .get_proofs( Some(self.mint_url.clone()), - Some(self.unit), + Some(self.unit.clone()), state, spending_conditions, ) @@ -115,7 +115,7 @@ impl Wallet { .localstore .get_proofs( Some(self.mint_url.clone()), - Some(self.unit), + Some(self.unit.clone()), Some(vec![State::Pending, State::Reserved]), None, ) diff --git a/crates/cdk/src/wallet/receive.rs b/crates/cdk/src/wallet/receive.rs index d5999232a..2a594b1f2 100644 --- a/crates/cdk/src/wallet/receive.rs +++ b/crates/cdk/src/wallet/receive.rs @@ -111,7 +111,7 @@ impl Wallet { let proofs_info = proofs .clone() .into_iter() - .map(|p| ProofInfo::new(p, self.mint_url.clone(), State::Pending, self.unit)) + .map(|p| ProofInfo::new(p, self.mint_url.clone(), State::Pending, self.unit.clone())) .collect::, _>>()?; self.localstore .update_proofs(proofs_info.clone(), vec![]) @@ -150,7 +150,7 @@ impl Wallet { let recv_proof_infos = recv_proofs .into_iter() - .map(|proof| ProofInfo::new(proof, mint_url.clone(), State::Unspent, self.unit)) + .map(|proof| ProofInfo::new(proof, mint_url.clone(), State::Unspent, self.unit.clone())) .collect::, _>>()?; self.localstore .update_proofs( diff --git a/crates/cdk/src/wallet/send.rs b/crates/cdk/src/wallet/send.rs index 7885db5cb..0c6e7b79c 100644 --- a/crates/cdk/src/wallet/send.rs +++ b/crates/cdk/src/wallet/send.rs @@ -16,7 +16,12 @@ impl Wallet { let ys = proofs.ys()?; self.localstore.reserve_proofs(ys).await?; - Ok(Token::new(self.mint_url.clone(), proofs, memo, self.unit)) + Ok(Token::new( + self.mint_url.clone(), + proofs, + memo, + self.unit.clone(), + )) } /// Send diff --git a/crates/cdk/src/wallet/swap.rs b/crates/cdk/src/wallet/swap.rs index 043d37c55..650ceac94 100644 --- a/crates/cdk/src/wallet/swap.rs +++ b/crates/cdk/src/wallet/swap.rs @@ -111,7 +111,9 @@ impl Wallet { let send_proofs_info = proofs_to_send .clone() .into_iter() - .map(|proof| ProofInfo::new(proof, mint_url.clone(), State::Reserved, *unit)) + .map(|proof| { + ProofInfo::new(proof, mint_url.clone(), State::Reserved, unit.clone()) + }) .collect::, _>>()?; added_proofs = send_proofs_info; @@ -126,7 +128,7 @@ impl Wallet { let keep_proofs = change_proofs .into_iter() - .map(|proof| ProofInfo::new(proof, mint_url.clone(), State::Unspent, *unit)) + .map(|proof| ProofInfo::new(proof, mint_url.clone(), State::Unspent, unit.clone())) .collect::, _>>()?; added_proofs.extend(keep_proofs); @@ -154,7 +156,7 @@ impl Wallet { .localstore .get_proofs( Some(self.mint_url.clone()), - Some(self.unit), + Some(self.unit.clone()), Some(vec![State::Unspent]), None, ) diff --git a/flake.lock b/flake.lock index 4073e5a1b..14dc95449 100644 --- a/flake.lock +++ b/flake.lock @@ -57,11 +57,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1730327045, - "narHash": "sha256-xKel5kd1AbExymxoIfQ7pgcX6hjw9jCgbiBjiUfSVJ8=", + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "080166c15633801df010977d9d7474b4a6c549d7", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", "type": "github" }, "original": {