Browse Source

feat: support custom unit

thesimplekid 5 months ago
parent
commit
3db25640dc

+ 1 - 1
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)]

+ 1 - 1
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)]

+ 3 - 3
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,
     };
 

+ 2 - 2
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,
         }
     }

+ 2 - 2
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,
         }
     }

+ 1 - 0
crates/cdk-integration-tests/src/init_regtest.rs

@@ -176,6 +176,7 @@ where
         Arc::new(database),
         ln_backends,
         supported_units,
+        HashMap::new(),
     )
     .await?;
 

+ 1 - 0
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;

+ 6 - 10
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(())
 }

+ 3 - 1
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);

+ 2 - 2
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,
         }
     }

+ 2 - 2
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,
         }
     }

+ 8 - 7
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?;
 

+ 2 - 2
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,
         }
     }

+ 5 - 5
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());

+ 10 - 3
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<CurrencyUnit, DerivationPath>,
     ) -> 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,
         );

+ 7 - 4
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);

+ 5 - 5
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(),

+ 39 - 19
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<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>,
         // Hashmap where the key is the unit and value is (input fee ppk, max_order)
         supported_units: HashMap<CurrencyUnit, (u64, u8)>,
+        custom_paths: HashMap<CurrencyUnit, DerivationPath>,
     ) -> Result<Self, Error> {
         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<CurrencyUnit, Vec<MintKeySetInfo>> =
                 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<C: secp256k1::Signing>(
     );
     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<C: secp256k1::Signing>(
     (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<DerivationPath> {
+    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();
 

+ 23 - 18
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<u32> {
         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<Self, Self::Err> {
-        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())
         }
     }
 }

+ 5 - 5
crates/cdk/src/nuts/nut00/token.rs

@@ -92,8 +92,8 @@ impl Token {
     /// Unit
     pub fn unit(&self) -> Option<CurrencyUnit> {
         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();
 

+ 2 - 2
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<MintMethodSettings> {
         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());
             }
         }
 

+ 2 - 2
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<MeltMethodSettings> {
         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());
             }
         }
 

+ 3 - 0
crates/cdk/src/nuts/nut09.rs

@@ -22,6 +22,9 @@ pub struct RestoreResponse {
     pub outputs: Vec<BlindedMessage>,
     /// Signatures
     pub signatures: Vec<BlindSignature>,
+    /// Promises
+    // Temp compatibility with cashu-ts
+    pub promises: Option<Vec<BlindSignature>>,
 }
 
 mod test {

+ 2 - 2
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")?]

+ 1 - 1
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,

+ 2 - 2
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
         });
 

+ 3 - 3
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::<Result<Vec<ProofInfo>, _>>()?

+ 4 - 4
crates/cdk/src/wallet/mint.rs

@@ -46,7 +46,7 @@ impl Wallet {
         description: Option<String>,
     ) -> Result<MintQuote, Error> {
         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::<Result<Vec<ProofInfo>, _>>()?;

+ 6 - 1
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::<Result<Vec<ProofInfo>, _>>()?;
 

+ 5 - 5
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()));
         }

+ 2 - 2
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,
             )

+ 2 - 2
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::<Result<Vec<ProofInfo>, _>>()?;
         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::<Result<Vec<ProofInfo>, _>>()?;
         self.localstore
             .update_proofs(

+ 6 - 1
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

+ 5 - 3
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::<Result<Vec<ProofInfo>, _>>()?;
                 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::<Result<Vec<ProofInfo>, _>>()?;
         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,
             )

+ 3 - 3
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": {