Browse Source

Merge remote-tracking branch 'origin/main' into nut-17-ws-subscription

Cesar Rodas 4 months ago
parent
commit
23ca672d79
46 changed files with 542 additions and 242 deletions
  1. 0 1
      .github/workflows/ci.yml
  2. 152 0
      DEVELOPMENT.md
  3. 3 1
      README.md
  4. 1 1
      bindings/cdk-js/src/types/melt_quote.rs
  5. 1 1
      bindings/cdk-js/src/types/mint_quote.rs
  6. 1 0
      crates/cdk-cli/src/sub_commands/mint_info.rs
  7. 3 3
      crates/cdk-cli/src/sub_commands/pay_request.rs
  8. 2 2
      crates/cdk-cln/src/lib.rs
  9. 2 2
      crates/cdk-fake-wallet/src/lib.rs
  10. 1 0
      crates/cdk-integration-tests/src/init_regtest.rs
  11. 17 11
      crates/cdk-integration-tests/src/lib.rs
  12. 18 20
      crates/cdk-integration-tests/tests/fake_wallet.rs
  13. 3 1
      crates/cdk-integration-tests/tests/mint.rs
  14. 13 8
      crates/cdk-integration-tests/tests/regtest.rs
  15. 2 2
      crates/cdk-lnbits/src/lib.rs
  16. 2 2
      crates/cdk-lnd/src/lib.rs
  17. 8 7
      crates/cdk-mintd/src/main.rs
  18. 2 2
      crates/cdk-phoenixd/src/lib.rs
  19. 5 5
      crates/cdk-strike/src/lib.rs
  20. 1 0
      crates/cdk/Cargo.toml
  21. 10 3
      crates/cdk/src/mint/keysets.rs
  22. 7 4
      crates/cdk/src/mint/melt.rs
  23. 5 5
      crates/cdk/src/mint/mint_nut04.rs
  24. 39 19
      crates/cdk/src/mint/mod.rs
  25. 23 18
      crates/cdk/src/nuts/nut00/mod.rs
  26. 5 5
      crates/cdk/src/nuts/nut00/token.rs
  27. 2 2
      crates/cdk/src/nuts/nut03.rs
  28. 2 2
      crates/cdk/src/nuts/nut04.rs
  29. 2 2
      crates/cdk/src/nuts/nut05.rs
  30. 3 0
      crates/cdk/src/nuts/nut09.rs
  31. 2 2
      crates/cdk/src/nuts/nut18.rs
  32. 1 1
      crates/cdk/src/types.rs
  33. 2 2
      crates/cdk/src/wallet/balance.rs
  34. 114 62
      crates/cdk/src/wallet/client.rs
  35. 19 12
      crates/cdk/src/wallet/melt.rs
  36. 17 5
      crates/cdk/src/wallet/mint.rs
  37. 11 5
      crates/cdk/src/wallet/mod.rs
  38. 5 5
      crates/cdk/src/wallet/multi_mint_wallet.rs
  39. 8 4
      crates/cdk/src/wallet/proofs.rs
  40. 2 2
      crates/cdk/src/wallet/receive.rs
  41. 6 1
      crates/cdk/src/wallet/send.rs
  42. 5 3
      crates/cdk/src/wallet/swap.rs
  43. 6 6
      flake.lock
  44. 4 2
      flake.nix
  45. 1 1
      justfile
  46. 4 0
      rust-toolchain.toml

+ 0 - 1
.github/workflows/ci.yml

@@ -144,7 +144,6 @@ jobs:
             -p cdk --no-default-features,
             -p cdk --no-default-features,
             -p cdk --no-default-features --features wallet,
             -p cdk --no-default-features --features wallet,
             -p cdk --no-default-features --features mint,
             -p cdk --no-default-features --features mint,
-            -p cdk --no-default-features --features "mint swagger",
             -p cdk-axum,
             -p cdk-axum,
             -p cdk-strike,
             -p cdk-strike,
             -p cdk-lnbits,
             -p cdk-lnbits,

+ 152 - 0
DEVELOPMENT.md

@@ -0,0 +1,152 @@
+# Development Guide
+
+This guide will help you set up your development environment for working with the CDK repository.
+
+## Prerequisites
+
+Before you begin, ensure you have:
+- Git installed on your system
+- GitHub account
+- Basic familiarity with command line operations
+
+## Initial Setup
+
+### 1. Fork and Clone the Repository
+
+1. Navigate to the CDK repository on GitHub
+2. Click the "Fork" button in the top-right corner
+3. Clone your forked repository:
+```bash
+git clone https://github.com/YOUR-USERNAME/cdk.git
+cd cdk
+```
+
+### 2. Install Nix
+
+<!-- 
+MIT License
+
+Copyright (c) 2021 elsirion
+https://github.com/fedimint/fedimint/blob/master/docs/dev-env.md
+-->
+
+CDK uses [Nix](https://nixos.org/explore.html) for building, CI, and managing dev environment.
+Note: only `Nix` (the language & package manager) and not the NixOS (the Linux distribution) is needed.
+Nix can be installed on any Linux distribution and macOS.
+
+While it is technically possible to not use Nix, it is highly recommended as
+it ensures consistent and reproducible environment for all developers.
+
+### Install Nix
+
+You have 2 options to install nix:
+
+* **RECOMMENDED:** The [Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer)
+* [The official installer](https://nixos.org/download.html)
+
+Example:
+
+```
+> nix --version
+nix (Nix) 2.9.1
+```
+
+The exact version might be different.
+
+### Enable nix flakes
+
+If you installed Nix using the "determinate installer" you can skip this step. If you used the "official installer", edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
+
+```
+experimental-features = nix-command flakes
+```
+
+If the Nix installation is in multi-user mode, don’t forget to restart the nix-daemon.
+
+## Use Nix Shell
+
+```sh
+  nix develop -c $SHELL  
+```
+
+## Common Development Tasks
+
+### Building the Project
+```sh
+just build
+```
+
+### Running Unit Tests
+```bash
+just test
+```
+
+### Running Integration Tests
+```bash
+just itest REDB/SQLITE/MEMEORY
+```
+
+### Running Format
+```bash
+just format
+```
+
+
+### Running Clippy
+```bash
+just clippy
+```
+
+### Running final check before commit
+```sh
+just final-check
+```
+
+
+## Best Practices
+
+1. **Branch Management**
+   - Create feature branches from `main`
+   - Use descriptive branch names: `feature/new-feature` or `fix/bug-description`
+
+2. **Commit Messages**
+   - Follow conventional commits format
+   - Begin with type: `feat:`, `fix:`, `docs:`, `chore:`, etc.
+   - Provide clear, concise descriptions
+
+3. **Testing**
+   - Write tests for new features
+   - Ensure all tests pass before submitting PR
+   - Include integration tests where applicable
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Development Shell Issues**
+   - Clean Nix store: `nix-collect-garbage -d`
+   - Remove and recreate development shell
+
+### Getting Help
+
+- Open an issue on GitHub
+- Check existing issues for similar problems
+- Include relevant error messages and system information
+- Reach out in Discord [Invite link](https://discord.gg/tUxMKd5YjN)
+
+## Contributing
+
+1. Create a feature branch
+2. Make your changes
+3. Run tests and formatting
+4. Submit a pull request
+5. Wait for review and address feedback
+
+## Additional Resources
+
+- [Nix Documentation](https://nixos.org/manual/nix/stable/)
+- [Contributing Guidelines](CONTRIBUTING.md)
+
+## License
+
+Refer to the LICENSE file in the repository for terms of use and distribution.

+ 3 - 1
README.md

@@ -59,7 +59,6 @@ The project is split up into several crates in the `crates/` directory:
 | [16][16] | Animated QR codes | :x: |
 | [16][16] | Animated QR codes | :x: |
 | [17][17] | WebSocket subscriptions  | :construction: |
 | [17][17] | WebSocket subscriptions  | :construction: |
 
 
-MSRV
 
 
 ## Bindings
 ## Bindings
 
 
@@ -75,6 +74,9 @@ All contributions welcome.
 
 
 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.
 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions.
 
 
+Please see the [development guide](DEVELOPMENT.md).
+
+
 [00]: https://github.com/cashubtc/nuts/blob/main/00.md
 [00]: https://github.com/cashubtc/nuts/blob/main/00.md
 [01]: https://github.com/cashubtc/nuts/blob/main/01.md
 [01]: https://github.com/cashubtc/nuts/blob/main/01.md
 [02]: https://github.com/cashubtc/nuts/blob/main/02.md
 [02]: https://github.com/cashubtc/nuts/blob/main/02.md

+ 1 - 1
bindings/cdk-js/src/types/melt_quote.rs

@@ -33,7 +33,7 @@ impl JsMeltQuote {
 
 
     #[wasm_bindgen(getter)]
     #[wasm_bindgen(getter)]
     pub fn unit(&self) -> JsCurrencyUnit {
     pub fn unit(&self) -> JsCurrencyUnit {
-        self.inner.unit.into()
+        self.inner.unit.clone().into()
     }
     }
 
 
     #[wasm_bindgen(getter)]
     #[wasm_bindgen(getter)]

+ 1 - 1
bindings/cdk-js/src/types/mint_quote.rs

@@ -33,7 +33,7 @@ impl JsMintQuote {
 
 
     #[wasm_bindgen(getter)]
     #[wasm_bindgen(getter)]
     pub fn unit(&self) -> JsCurrencyUnit {
     pub fn unit(&self) -> JsCurrencyUnit {
-        self.inner.unit.into()
+        self.inner.unit.clone().into()
     }
     }
 
 
     #[wasm_bindgen(getter)]
     #[wasm_bindgen(getter)]

+ 1 - 0
crates/cdk-cli/src/sub_commands/mint_info.rs

@@ -1,5 +1,6 @@
 use anyhow::Result;
 use anyhow::Result;
 use cdk::mint_url::MintUrl;
 use cdk::mint_url::MintUrl;
+use cdk::wallet::client::HttpClientMethods;
 use cdk::HttpClient;
 use cdk::HttpClient;
 use clap::Args;
 use clap::Args;
 use url::Url;
 use url::Url;

+ 3 - 3
crates/cdk-cli/src/sub_commands/pay_request.rs

@@ -21,7 +21,7 @@ pub async fn pay_request(
 ) -> Result<()> {
 ) -> Result<()> {
     let payment_request = &sub_command_args.payment_request;
     let payment_request = &sub_command_args.payment_request;
 
 
-    let unit = payment_request.unit;
+    let unit = &payment_request.unit;
 
 
     let amount = match payment_request.amount {
     let amount = match payment_request.amount {
         Some(amount) => amount,
         Some(amount) => amount,
@@ -56,7 +56,7 @@ pub async fn pay_request(
         }
         }
 
 
         if let Some(unit) = unit {
         if let Some(unit) = unit {
-            if wallet.unit != unit {
+            if &wallet.unit != unit {
                 continue;
                 continue;
             }
             }
         }
         }
@@ -97,7 +97,7 @@ pub async fn pay_request(
         id: payment_request.payment_id.clone(),
         id: payment_request.payment_id.clone(),
         memo: None,
         memo: None,
         mint: matching_wallet.mint_url.clone(),
         mint: matching_wallet.mint_url.clone(),
-        unit: matching_wallet.unit,
+        unit: matching_wallet.unit.clone(),
         proofs,
         proofs,
     };
     };
 
 

+ 2 - 2
crates/cdk-cln/src/lib.rs

@@ -81,8 +81,8 @@ impl MintLightning for Cln {
         Settings {
         Settings {
             mpp: true,
             mpp: true,
             unit: CurrencyUnit::Msat,
             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,
             invoice_description: true,
         }
         }
     }
     }

+ 2 - 2
crates/cdk-fake-wallet/src/lib.rs

@@ -112,8 +112,8 @@ impl MintLightning for FakeWallet {
         Settings {
         Settings {
             mpp: true,
             mpp: true,
             unit: CurrencyUnit::Msat,
             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,
             invoice_description: true,
         }
         }
     }
     }

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

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

+ 17 - 11
crates/cdk-integration-tests/src/lib.rs

@@ -11,11 +11,11 @@ use cdk::cdk_lightning::MintLightning;
 use cdk::dhke::construct_proofs;
 use cdk::dhke::construct_proofs;
 use cdk::mint::FeeReserve;
 use cdk::mint::FeeReserve;
 use cdk::nuts::{
 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::types::{LnKey, QuoteTTL};
-use cdk::wallet::client::HttpClient;
+use cdk::wallet::client::{HttpClient, HttpClientMethods};
 use cdk::{Mint, Wallet};
 use cdk::{Mint, Wallet};
 use cdk_fake_wallet::FakeWallet;
 use cdk_fake_wallet::FakeWallet;
 use init_regtest::{get_mint_addr, get_mint_port, get_mint_url};
 use init_regtest::{get_mint_addr, get_mint_port, get_mint_url};
@@ -82,6 +82,7 @@ pub async fn start_mint(
         Arc::new(MintMemoryDatabase::default()),
         Arc::new(MintMemoryDatabase::default()),
         ln_backends.clone(),
         ln_backends.clone(),
         supported_units,
         supported_units,
+        HashMap::new(),
     )
     )
     .await?;
     .await?;
     let cache_time_to_live = 3600;
     let cache_time_to_live = 3600;
@@ -158,8 +159,14 @@ pub async fn mint_proofs(
 
 
     let wallet_client = HttpClient::new();
     let wallet_client = HttpClient::new();
 
 
+    let request = MintQuoteBolt11Request {
+        amount,
+        unit: CurrencyUnit::Sat,
+        description,
+    };
+
     let mint_quote = wallet_client
     let mint_quote = wallet_client
-        .post_mint_quote(mint_url.parse()?, 1.into(), CurrencyUnit::Sat, description)
+        .post_mint_quote(mint_url.parse()?, request)
         .await?;
         .await?;
 
 
     println!("Please pay: {}", mint_quote.request);
     println!("Please pay: {}", mint_quote.request);
@@ -179,13 +186,12 @@ pub async fn mint_proofs(
 
 
     let premint_secrets = PreMintSecrets::random(keyset_id, amount, &SplitTarget::default())?;
     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(
     let pre_swap_proofs = construct_proofs(
         mint_response.signatures,
         mint_response.signatures,

+ 18 - 20
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -5,8 +5,11 @@ use bip39::Mnemonic;
 use cdk::{
 use cdk::{
     amount::SplitTarget,
     amount::SplitTarget,
     cdk_database::WalletMemoryDatabase,
     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_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
 use cdk_integration_tests::attempt_to_swap_pending;
 use cdk_integration_tests::attempt_to_swap_pending;
@@ -354,28 +357,23 @@ async fn test_fake_melt_change_in_quote() -> Result<()> {
 
 
     let client = HttpClient::new();
     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());
     assert!(melt_response.change.is_some());
 
 
     let check = wallet.melt_quote_status(&melt_quote.id).await?;
     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));
+
+    let mut check = check.change.unwrap();
+    check.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))
-    );
+    assert_eq!(melt_change, check);
     Ok(())
     Ok(())
 }
 }

+ 3 - 1
crates/cdk-integration-tests/tests/mint.rs

@@ -52,6 +52,7 @@ async fn new_mint(fee: u64) -> Mint {
         Arc::new(MintMemoryDatabase::default()),
         Arc::new(MintMemoryDatabase::default()),
         HashMap::new(),
         HashMap::new(),
         supported_units,
         supported_units,
+        HashMap::new(),
     )
     )
     .await
     .await
     .unwrap()
     .unwrap()
@@ -327,7 +328,8 @@ async fn test_swap_unbalanced() -> Result<()> {
 async fn test_swap_overpay_underpay_fee() -> Result<()> {
 async fn test_swap_overpay_underpay_fee() -> Result<()> {
     let mint = new_mint(1).await;
     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 keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
     let keyset_id = Id::from(&keys);

+ 13 - 8
crates/cdk-integration-tests/tests/regtest.rs

@@ -6,9 +6,13 @@ use cdk::{
     amount::{Amount, SplitTarget},
     amount::{Amount, SplitTarget},
     cdk_database::WalletMemoryDatabase,
     cdk_database::WalletMemoryDatabase,
     nuts::{
     nuts::{
-        CurrencyUnit, MeltQuoteState, MintQuoteState, NotificationPayload, PreMintSecrets, State,
+        CurrencyUnit, MeltQuoteState, MintBolt11Request, MintQuoteState, NotificationPayload,
+        PreMintSecrets, State,
+    },
+    wallet::{
+        client::{HttpClient, HttpClientMethods},
+        Wallet,
     },
     },
-    wallet::{client::HttpClient, Wallet},
 };
 };
 use cdk_integration_tests::init_regtest::{
 use cdk_integration_tests::init_regtest::{
     get_mint_url, get_mint_ws_url, init_cln_client, init_lnd_client,
     get_mint_url, get_mint_ws_url, init_cln_client, init_lnd_client,
@@ -362,15 +366,16 @@ async fn test_cached_mint() -> Result<()> {
     let premint_secrets =
     let premint_secrets =
         PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap();
         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
     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?;
         .await?;
     let response1 = http_client
     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?;
         .await?;
 
 
     assert!(response == response1);
     assert!(response == response1);

+ 2 - 2
crates/cdk-lnbits/src/lib.rs

@@ -80,8 +80,8 @@ impl MintLightning for LNbits {
         Settings {
         Settings {
             mpp: false,
             mpp: false,
             unit: CurrencyUnit::Sat,
             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,
             invoice_description: true,
         }
         }
     }
     }

+ 2 - 2
crates/cdk-lnd/src/lib.rs

@@ -88,8 +88,8 @@ impl MintLightning for Lnd {
         Settings {
         Settings {
             mpp: true,
             mpp: true,
             unit: CurrencyUnit::Msat,
             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,
             invoice_description: true,
         }
         }
     }
     }

+ 8 - 7
crates/cdk-mintd/src/main.rs

@@ -188,7 +188,7 @@ async fn main() -> anyhow::Result<()> {
                     api_key.clone(),
                     api_key.clone(),
                     MintMethodSettings::default(),
                     MintMethodSettings::default(),
                     MeltMethodSettings::default(),
                     MeltMethodSettings::default(),
-                    unit,
+                    unit.clone(),
                     Arc::new(Mutex::new(Some(receiver))),
                     Arc::new(Mutex::new(Some(receiver))),
                     webhook_url.to_string(),
                     webhook_url.to_string(),
                 )
                 )
@@ -199,7 +199,7 @@ async fn main() -> anyhow::Result<()> {
                     .await?;
                     .await?;
                 routers.push(router);
                 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));
                 ln_backends.insert(ln_key, Arc::new(strike));
 
 
@@ -237,7 +237,7 @@ async fn main() -> anyhow::Result<()> {
 
 
             let unit = CurrencyUnit::Sat;
             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));
             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;
             let units = settings.fake_wallet.unwrap_or_default().supported_units;
 
 
             for unit in 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(
                 let wallet = Arc::new(FakeWallet::new(
                     fee_reserve.clone(),
                     fee_reserve.clone(),
@@ -361,13 +361,13 @@ async fn main() -> anyhow::Result<()> {
 
 
             let m = MppMethodSettings {
             let m = MppMethodSettings {
                 method: key.method,
                 method: key.method,
-                unit: key.unit,
+                unit: key.unit.clone(),
                 mpp: settings.mpp,
                 mpp: settings.mpp,
             };
             };
 
 
             let n4 = MintMethodSettings {
             let n4 = MintMethodSettings {
                 method: key.method,
                 method: key.method,
-                unit: key.unit,
+                unit: key.unit.clone(),
                 min_amount: settings.mint_settings.min_amount,
                 min_amount: settings.mint_settings.min_amount,
                 max_amount: settings.mint_settings.max_amount,
                 max_amount: settings.mint_settings.max_amount,
                 description: settings.invoice_description,
                 description: settings.invoice_description,
@@ -375,7 +375,7 @@ async fn main() -> anyhow::Result<()> {
 
 
             let n5 = MeltMethodSettings {
             let n5 = MeltMethodSettings {
                 method: key.method,
                 method: key.method,
-                unit: key.unit,
+                unit: key.unit.clone(),
                 min_amount: settings.melt_settings.min_amount,
                 min_amount: settings.melt_settings.min_amount,
                 max_amount: settings.melt_settings.max_amount,
                 max_amount: settings.melt_settings.max_amount,
             };
             };
@@ -438,6 +438,7 @@ async fn main() -> anyhow::Result<()> {
         localstore,
         localstore,
         ln_backends.clone(),
         ln_backends.clone(),
         supported_units,
         supported_units,
+        HashMap::new(),
     )
     )
     .await?;
     .await?;
 
 

+ 2 - 2
crates/cdk-phoenixd/src/lib.rs

@@ -86,8 +86,8 @@ impl MintLightning for Phoenixd {
         Settings {
         Settings {
             mpp: false,
             mpp: false,
             unit: CurrencyUnit::Sat,
             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,
             invoice_description: true,
         }
         }
     }
     }

+ 5 - 5
crates/cdk-strike/src/lib.rs

@@ -77,9 +77,9 @@ impl MintLightning for Strike {
     fn get_settings(&self) -> Settings {
     fn get_settings(&self) -> Settings {
         Settings {
         Settings {
             mpp: false,
             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,
             invoice_description: true,
         }
         }
     }
     }
@@ -288,7 +288,7 @@ impl MintLightning for Strike {
                     payment_preimage: None,
                     payment_preimage: None,
                     status: state,
                     status: state,
                     total_spent: from_strike_amount(invoice.total_amount, &self.unit)?.into(),
                     total_spent: from_strike_amount(invoice.total_amount, &self.unit)?.into(),
-                    unit: self.unit,
+                    unit: self.unit.clone(),
                 }
                 }
             }
             }
             Err(err) => match err {
             Err(err) => match err {
@@ -297,7 +297,7 @@ impl MintLightning for Strike {
                     payment_preimage: None,
                     payment_preimage: None,
                     status: MeltQuoteState::Unknown,
                     status: MeltQuoteState::Unknown,
                     total_spent: Amount::ZERO,
                     total_spent: Amount::ZERO,
-                    unit: self.unit,
+                    unit: self.unit.clone(),
                 },
                 },
                 _ => {
                 _ => {
                     return Err(Error::from(err).into());
                     return Err(Error::from(err).into());

+ 1 - 0
crates/cdk/Cargo.toml

@@ -13,6 +13,7 @@ license = "MIT"
 [features]
 [features]
 default = ["mint", "wallet"]
 default = ["mint", "wallet"]
 mint = ["dep:futures"]
 mint = ["dep:futures"]
+# We do not commit to a MSRV with swagger enabled
 swagger = ["mint", "dep:utoipa"]
 swagger = ["mint", "dep:utoipa"]
 wallet = ["dep:reqwest"]
 wallet = ["dep:reqwest"]
 bench = []
 bench = []

+ 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 tracing::instrument;
 
 
 use crate::Error;
 use crate::Error;
@@ -89,14 +90,20 @@ impl Mint {
         derivation_path_index: u32,
         derivation_path_index: u32,
         max_order: u8,
         max_order: u8,
         input_fee_ppk: u64,
         input_fee_ppk: u64,
+        custom_paths: HashMap<CurrencyUnit, DerivationPath>,
     ) -> Result<(), Error> {
     ) -> 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(
         let (keyset, keyset_info) = create_new_keyset(
             &self.secp_ctx,
             &self.secp_ctx,
             self.xpriv,
             self.xpriv,
             derivation_path,
             derivation_path,
             Some(derivation_path_index),
             Some(derivation_path_index),
-            unit,
+            unit.clone(),
             max_order,
             max_order,
             input_fee_ppk,
             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
         let ln = self
             .ln
             .ln
-            .get(&LnKey::new(*unit, PaymentMethod::Bolt11))
+            .get(&LnKey::new(unit.clone(), PaymentMethod::Bolt11))
             .ok_or_else(|| {
             .ok_or_else(|| {
                 tracing::info!("Could not get ln backend for {}, bolt11 ", unit);
                 tracing::info!("Could not get ln backend for {}, bolt11 ", unit);
 
 
@@ -97,7 +97,7 @@ impl Mint {
 
 
         let quote = MeltQuote::new(
         let quote = MeltQuote::new(
             request.to_string(),
             request.to_string(),
-            *unit,
+            unit.clone(),
             payment_quote.amount,
             payment_quote.amount,
             payment_quote.fee,
             payment_quote.fee,
             unix_time() + self.quote_ttl.melt_ttl,
             unix_time() + self.quote_ttl.melt_ttl,
@@ -457,7 +457,10 @@ impl Mint {
                     }
                     }
                     _ => None,
                     _ => 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,
                     Some(ln) => ln,
                     None => {
                     None => {
                         tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
                         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(
     fn check_mint_request_acceptable(
         &self,
         &self,
         amount: Amount,
         amount: Amount,
-        unit: CurrencyUnit,
+        unit: &CurrencyUnit,
     ) -> Result<(), Error> {
     ) -> Result<(), Error> {
         let nut04 = &self.mint_info.nuts.nut04;
         let nut04 = &self.mint_info.nuts.nut04;
 
 
@@ -20,7 +20,7 @@ impl Mint {
             return Err(Error::MintingDisabled);
             return Err(Error::MintingDisabled);
         }
         }
 
 
-        match nut04.get_settings(&unit, &PaymentMethod::Bolt11) {
+        match nut04.get_settings(unit, &PaymentMethod::Bolt11) {
             Some(settings) => {
             Some(settings) => {
                 if settings
                 if settings
                     .max_amount
                     .max_amount
@@ -64,11 +64,11 @@ impl Mint {
             description,
             description,
         } = mint_quote_request;
         } = mint_quote_request;
 
 
-        self.check_mint_request_acceptable(amount, unit)?;
+        self.check_mint_request_acceptable(amount, &unit)?;
 
 
         let ln = self
         let ln = self
             .ln
             .ln
-            .get(&LnKey::new(unit, PaymentMethod::Bolt11))
+            .get(&LnKey::new(unit.clone(), PaymentMethod::Bolt11))
             .ok_or_else(|| {
             .ok_or_else(|| {
                 tracing::info!("Bolt11 mint request for unsupported unit");
                 tracing::info!("Bolt11 mint request for unsupported unit");
 
 
@@ -98,7 +98,7 @@ impl Mint {
         let quote = MintQuote::new(
         let quote = MintQuote::new(
             self.mint_url.clone(),
             self.mint_url.clone(),
             create_invoice_response.request.to_string(),
             create_invoice_response.request.to_string(),
-            unit,
+            unit.clone(),
             amount,
             amount,
             create_invoice_response.expiry.unwrap_or(0),
             create_invoice_response.expiry.unwrap_or(0),
             create_invoice_response.request_lookup_id.clone(),
             create_invoice_response.request_lookup_id.clone(),

+ 39 - 19
crates/cdk/src/mint/mod.rs

@@ -56,6 +56,7 @@ pub struct Mint {
 
 
 impl Mint {
 impl Mint {
     /// Create new [`Mint`]
     /// Create new [`Mint`]
+    #[allow(clippy::too_many_arguments)]
     pub async fn new(
     pub async fn new(
         mint_url: &str,
         mint_url: &str,
         seed: &[u8],
         seed: &[u8],
@@ -65,6 +66,7 @@ impl Mint {
         ln: HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>,
         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)
         // Hashmap where the key is the unit and value is (input fee ppk, max_order)
         supported_units: HashMap<CurrencyUnit, (u64, u8)>,
         supported_units: HashMap<CurrencyUnit, (u64, u8)>,
+        custom_paths: HashMap<CurrencyUnit, DerivationPath>,
     ) -> Result<Self, Error> {
     ) -> Result<Self, Error> {
         let secp_ctx = Secp256k1::new();
         let secp_ctx = Secp256k1::new();
         let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
         let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
@@ -85,7 +87,7 @@ impl Mint {
 
 
             let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
             let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
                 keysets_infos.iter().fold(HashMap::new(), |mut acc, ks| {
                 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
                     acc
                 });
                 });
 
 
@@ -114,7 +116,7 @@ impl Mint {
                             &secp_ctx,
                             &secp_ctx,
                             xpriv,
                             xpriv,
                             highest_index_keyset.max_order,
                             highest_index_keyset.max_order,
-                            highest_index_keyset.unit,
+                            highest_index_keyset.unit.clone(),
                             highest_index_keyset.derivation_path.clone(),
                             highest_index_keyset.derivation_path.clone(),
                         );
                         );
                         active_keysets.insert(id, keyset);
                         active_keysets.insert(id, keyset);
@@ -127,37 +129,46 @@ impl Mint {
                         highest_index_keyset.derivation_path_index.unwrap_or(0) + 1
                         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(
                     let (keyset, keyset_info) = create_new_keyset(
                         &secp_ctx,
                         &secp_ctx,
                         xpriv,
                         xpriv,
                         derivation_path,
                         derivation_path,
                         Some(derivation_path_index),
                         Some(derivation_path_index),
-                        unit,
+                        unit.clone(),
                         *max_order,
                         *max_order,
                         *input_fee_ppk,
                         *input_fee_ppk,
                     );
                     );
 
 
                     let id = keyset_info.id;
                     let id = keyset_info.id;
                     localstore.add_keyset_info(keyset_info).await?;
                     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_keysets.insert(id, keyset);
-                    active_keyset_units.push(unit);
+                    active_keyset_units.push(unit.clone());
                 }
                 }
             }
             }
         }
         }
 
 
         for (unit, (fee, max_order)) in supported_units {
         for (unit, (fee, max_order)) in supported_units {
             if !active_keyset_units.contains(&unit) {
             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(
                 let (keyset, keyset_info) = create_new_keyset(
                     &secp_ctx,
                     &secp_ctx,
                     xpriv,
                     xpriv,
                     derivation_path,
                     derivation_path,
                     Some(0),
                     Some(0),
-                    unit,
+                    unit.clone(),
                     max_order,
                     max_order,
                     fee,
                     fee,
                 );
                 );
@@ -197,7 +208,7 @@ impl Mint {
                 let mint = Arc::clone(&mint_arc);
                 let mint = Arc::clone(&mint_arc);
                 let ln = Arc::clone(ln);
                 let ln = Arc::clone(ln);
                 let shutdown = Arc::clone(&shutdown);
                 let shutdown = Arc::clone(&shutdown);
-                let key = *key;
+                let key = key.clone();
                 join_set.spawn(async move {
                 join_set.spawn(async move {
             if !ln.is_wait_invoice_active() {
             if !ln.is_wait_invoice_active() {
             loop {
             loop {
@@ -441,7 +452,8 @@ impl Mint {
 
 
         Ok(RestoreResponse {
         Ok(RestoreResponse {
             outputs,
             outputs,
-            signatures,
+            signatures: signatures.clone(),
+            promises: Some(signatures),
         })
         })
     }
     }
 
 
@@ -562,7 +574,7 @@ fn create_new_keyset<C: secp256k1::Signing>(
     );
     );
     let keyset_info = MintKeySetInfo {
     let keyset_info = MintKeySetInfo {
         id: keyset.id,
         id: keyset.id,
-        unit: keyset.unit,
+        unit: keyset.unit.clone(),
         active: true,
         active: true,
         valid_from: unix_time(),
         valid_from: unix_time(),
         valid_to: None,
         valid_to: None,
@@ -574,12 +586,17 @@ fn create_new_keyset<C: secp256k1::Signing>(
     (keyset, keyset_info)
     (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(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"),
         ChildNumber::from_hardened_idx(index).expect("0 is a valid index"),
-    ])
+    ]))
 }
 }
 
 
 #[cfg(test)]
 #[cfg(test)]
@@ -601,7 +618,7 @@ mod tests {
             seed,
             seed,
             2,
             2,
             CurrencyUnit::Sat,
             CurrencyUnit::Sat,
-            derivation_path_from_unit(CurrencyUnit::Sat, 0),
+            derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
         );
         );
 
 
         assert_eq!(keyset.unit, CurrencyUnit::Sat);
         assert_eq!(keyset.unit, CurrencyUnit::Sat);
@@ -645,7 +662,7 @@ mod tests {
             xpriv,
             xpriv,
             2,
             2,
             CurrencyUnit::Sat,
             CurrencyUnit::Sat,
-            derivation_path_from_unit(CurrencyUnit::Sat, 0),
+            derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
         );
         );
 
 
         assert_eq!(keyset.unit, CurrencyUnit::Sat);
         assert_eq!(keyset.unit, CurrencyUnit::Sat);
@@ -725,6 +742,7 @@ mod tests {
             localstore,
             localstore,
             HashMap::new(),
             HashMap::new(),
             config.supported_units,
             config.supported_units,
+            HashMap::new(),
         )
         )
         .await
         .await
     }
     }
@@ -780,7 +798,8 @@ mod tests {
         assert!(keysets.keysets.is_empty());
         assert!(keysets.keysets.is_empty());
 
 
         // generate the first keyset and set it to active
         // 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();
         let keysets = mint.keysets().await.unwrap();
         assert!(keysets.keysets.len().eq(&1));
         assert!(keysets.keysets.len().eq(&1));
@@ -788,7 +807,8 @@ mod tests {
         let first_keyset_id = keysets.keysets[0].id;
         let first_keyset_id = keysets.keysets[0].id;
 
 
         // set the first keyset to inactive and generate a new keyset
         // 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();
         let keysets = mint.keysets().await.unwrap();
 
 

+ 23 - 18
crates/cdk/src/nuts/nut00/mod.rs

@@ -361,7 +361,7 @@ where
 
 
 /// Currency Unit
 /// Currency Unit
 #[non_exhaustive]
 #[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))]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum CurrencyUnit {
 pub enum CurrencyUnit {
     /// Sat
     /// Sat
@@ -373,17 +373,20 @@ pub enum CurrencyUnit {
     Usd,
     Usd,
     /// Euro
     /// Euro
     Eur,
     Eur,
+    /// Custom currency unit
+    Custom(String),
 }
 }
 
 
 #[cfg(feature = "mint")]
 #[cfg(feature = "mint")]
 impl CurrencyUnit {
 impl CurrencyUnit {
     /// Derivation index mint will use for unit
     /// Derivation index mint will use for unit
-    pub fn derivation_index(&self) -> u32 {
+    pub fn derivation_index(&self) -> Option<u32> {
         match self {
         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 {
 impl FromStr for CurrencyUnit {
     type Err = Error;
     type Err = Error;
     fn from_str(value: &str) -> Result<Self, Self::Err> {
     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 {
 impl fmt::Display for CurrencyUnit {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let s = match self {
         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() {
         if let Some(width) = f.width() {
-            write!(f, "{:width$}", s, width = width)
+            write!(f, "{:width$}", s.to_lowercase(), width = width)
         } else {
         } 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
     /// Unit
     pub fn unit(&self) -> Option<CurrencyUnit> {
     pub fn unit(&self) -> Option<CurrencyUnit> {
         match self {
         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
     /// Unit
     #[inline]
     #[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,
             token.token[0].proofs[0].clone().keyset_id,
             Id::from_str("009a1f293253e41e").unwrap()
             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();
         let encoded = &token.to_string();
 
 

+ 2 - 2
crates/cdk/src/nuts/nut03.rs

@@ -32,11 +32,11 @@ pub struct PreSwap {
     pub fee: Amount,
     pub fee: Amount,
 }
 }
 
 
-/// Split Request [NUT-06]
+/// Swap Request [NUT-03]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct SwapRequest {
 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>))]
     #[cfg_attr(feature = "swagger", schema(value_type = Vec<Proof>))]
     pub inputs: Proofs,
     pub inputs: Proofs,
     /// Blinded Messages for Mint to sign
     /// Blinded Messages for Mint to sign

+ 2 - 2
crates/cdk/src/nuts/nut04.rs

@@ -137,7 +137,7 @@ pub struct MintBolt11Response {
 }
 }
 
 
 /// Mint Method Settings
 /// 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))]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintMethodSettings {
 pub struct MintMethodSettings {
     /// Payment Method e.g. bolt11
     /// Payment Method e.g. bolt11
@@ -179,7 +179,7 @@ impl Settings {
     ) -> Option<MintMethodSettings> {
     ) -> Option<MintMethodSettings> {
         for method_settings in self.methods.iter() {
         for method_settings in self.methods.iter() {
             if method_settings.method.eq(method) && method_settings.unit.eq(unit) {
             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

@@ -252,7 +252,7 @@ impl MeltBolt11Request {
 }
 }
 
 
 /// Melt Method Settings
 /// 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))]
 #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MeltMethodSettings {
 pub struct MeltMethodSettings {
     /// Payment Method e.g. bolt11
     /// Payment Method e.g. bolt11
@@ -281,7 +281,7 @@ impl Settings {
     ) -> Option<MeltMethodSettings> {
     ) -> Option<MeltMethodSettings> {
         for method_settings in self.methods.iter() {
         for method_settings in self.methods.iter() {
             if method_settings.method.eq(method) && method_settings.unit.eq(unit) {
             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>,
     pub outputs: Vec<BlindedMessage>,
     /// Signatures
     /// Signatures
     pub signatures: Vec<BlindSignature>,
     pub signatures: Vec<BlindSignature>,
+    /// Promises
+    // Temp compatibility with cashu-ts
+    pub promises: Option<Vec<BlindSignature>>,
 }
 }
 
 
 mod test {
 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.payment_id.unwrap(), "b7a90176");
         assert_eq!(req.amount.unwrap(), 10.into());
         assert_eq!(req.amount.unwrap(), 10.into());
-        assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat);
+        assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat);
         assert_eq!(
         assert_eq!(
             req.mints.unwrap(),
             req.mints.unwrap(),
             vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?]
             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.payment_id.unwrap(), "b7a90176");
         assert_eq!(req.amount.unwrap(), 10.into());
         assert_eq!(req.amount.unwrap(), 10.into());
-        assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat);
+        assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat);
         assert_eq!(
         assert_eq!(
             req.mints.unwrap(),
             req.mints.unwrap(),
             vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?]
             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
 /// Key used in hashmap of ln backends to identify what unit and payment method
 /// it is for
 /// it is for
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub struct LnKey {
 pub struct LnKey {
     /// Unit of Payment backend
     /// Unit of Payment backend
     pub unit: CurrencyUnit,
     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?
         // 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| {
         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
             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?
         // 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| {
         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
             acc
         });
         });
 
 

+ 114 - 62
crates/cdk/src/wallet/client.rs

@@ -1,5 +1,8 @@
 //! Wallet client
 //! Wallet client
 
 
+use std::fmt::Debug;
+
+use async_trait::async_trait;
 use reqwest::Client;
 use reqwest::Client;
 use serde_json::Value;
 use serde_json::Value;
 use tracing::instrument;
 use tracing::instrument;
@@ -8,15 +11,12 @@ use url::Url;
 use super::Error;
 use super::Error;
 use crate::error::ErrorResponse;
 use crate::error::ErrorResponse;
 use crate::mint_url::MintUrl;
 use crate::mint_url::MintUrl;
-use crate::nuts::nut15::Mpp;
 use crate::nuts::{
 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
 /// Http Client
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
@@ -67,10 +67,14 @@ impl HttpClient {
 
 
         Ok(Self { inner: client })
         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]
     /// Get Active Mint Keys [NUT-01]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[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 url = mint_url.join_paths(&["v1", "keys"])?;
         let keys = self.inner.get(url).send().await?.json::<Value>().await?;
         let keys = self.inner.get(url).send().await?.json::<Value>().await?;
 
 
@@ -82,7 +86,7 @@ impl HttpClient {
 
 
     /// Get Keyset Keys [NUT-01]
     /// Get Keyset Keys [NUT-01]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[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 url = mint_url.join_paths(&["v1", "keys", &keyset_id.to_string()])?;
         let keys = self.inner.get(url).send().await?.json::<Value>().await?;
         let keys = self.inner.get(url).send().await?.json::<Value>().await?;
 
 
@@ -94,7 +98,7 @@ impl HttpClient {
 
 
     /// Get Keysets [NUT-02]
     /// Get Keysets [NUT-02]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[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 url = mint_url.join_paths(&["v1", "keysets"])?;
         let res = self.inner.get(url).send().await?.json::<Value>().await?;
         let res = self.inner.get(url).send().await?.json::<Value>().await?;
 
 
@@ -106,21 +110,13 @@ impl HttpClient {
 
 
     /// Mint Quote [NUT-04]
     /// Mint Quote [NUT-04]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
-    pub async fn post_mint_quote(
+    async fn post_mint_quote(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
-        amount: Amount,
-        unit: CurrencyUnit,
-        description: Option<String>,
+        request: MintQuoteBolt11Request,
     ) -> Result<MintQuoteBolt11Response, Error> {
     ) -> Result<MintQuoteBolt11Response, Error> {
         let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11"])?;
         let url = mint_url.join_paths(&["v1", "mint", "quote", "bolt11"])?;
 
 
-        let request = MintQuoteBolt11Request {
-            amount,
-            unit,
-            description,
-        };
-
         let res = self
         let res = self
             .inner
             .inner
             .post(url)
             .post(url)
@@ -141,7 +137,7 @@ impl HttpClient {
 
 
     /// Mint Quote status
     /// Mint Quote status
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
-    pub async fn get_mint_quote_status(
+    async fn get_mint_quote_status(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
         quote_id: &str,
         quote_id: &str,
@@ -160,20 +156,14 @@ impl HttpClient {
     }
     }
 
 
     /// Mint Tokens [NUT-04]
     /// 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,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
-        quote: &str,
-        premint_secrets: PreMintSecrets,
+        request: MintBolt11Request,
     ) -> Result<MintBolt11Response, Error> {
     ) -> Result<MintBolt11Response, Error> {
         let url = mint_url.join_paths(&["v1", "mint", "bolt11"])?;
         let url = mint_url.join_paths(&["v1", "mint", "bolt11"])?;
 
 
-        let request = MintBolt11Request {
-            quote: quote.to_string(),
-            outputs: premint_secrets.blinded_messages(),
-        };
-
         let res = self
         let res = self
             .inner
             .inner
             .post(url)
             .post(url)
@@ -191,23 +181,13 @@ impl HttpClient {
 
 
     /// Melt Quote [NUT-05]
     /// Melt Quote [NUT-05]
     #[instrument(skip(self, request), fields(mint_url = %mint_url))]
     #[instrument(skip(self, request), fields(mint_url = %mint_url))]
-    pub async fn post_melt_quote(
+    async fn post_melt_quote(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
-        unit: CurrencyUnit,
-        request: Bolt11Invoice,
-        mpp_amount: Option<Amount>,
+        request: MeltQuoteBolt11Request,
     ) -> Result<MeltQuoteBolt11Response, Error> {
     ) -> Result<MeltQuoteBolt11Response, Error> {
         let url = mint_url.join_paths(&["v1", "melt", "quote", "bolt11"])?;
         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
         let res = self
             .inner
             .inner
             .post(url)
             .post(url)
@@ -225,7 +205,7 @@ impl HttpClient {
 
 
     /// Melt Quote Status
     /// Melt Quote Status
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
-    pub async fn get_melt_quote_status(
+    async fn get_melt_quote_status(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
         quote_id: &str,
         quote_id: &str,
@@ -242,22 +222,14 @@ impl HttpClient {
 
 
     /// Melt [NUT-05]
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
     /// [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,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
-        quote: String,
-        inputs: Vec<Proof>,
-        outputs: Option<Vec<BlindedMessage>>,
+        request: MeltBolt11Request,
     ) -> Result<MeltQuoteBolt11Response, Error> {
     ) -> Result<MeltQuoteBolt11Response, Error> {
         let url = mint_url.join_paths(&["v1", "melt", "bolt11"])?;
         let url = mint_url.join_paths(&["v1", "melt", "bolt11"])?;
 
 
-        let request = MeltBolt11Request {
-            quote,
-            inputs,
-            outputs,
-        };
-
         let res = self
         let res = self
             .inner
             .inner
             .post(url)
             .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))]
     #[instrument(skip(self, swap_request), fields(mint_url = %mint_url))]
-    pub async fn post_swap(
+    async fn post_swap(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
         swap_request: SwapRequest,
         swap_request: SwapRequest,
@@ -304,7 +276,7 @@ impl HttpClient {
 
 
     /// Get Mint Info [NUT-06]
     /// Get Mint Info [NUT-06]
     #[instrument(skip(self), fields(mint_url = %mint_url))]
     #[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 url = mint_url.join_paths(&["v1", "info"])?;
 
 
         let res = self.inner.get(url).send().await?.json::<Value>().await?;
         let res = self.inner.get(url).send().await?.json::<Value>().await?;
@@ -319,14 +291,13 @@ impl HttpClient {
     }
     }
 
 
     /// Spendable check [NUT-07]
     /// 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,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
-        ys: Vec<PublicKey>,
+        request: CheckStateRequest,
     ) -> Result<CheckStateResponse, Error> {
     ) -> Result<CheckStateResponse, Error> {
         let url = mint_url.join_paths(&["v1", "checkstate"])?;
         let url = mint_url.join_paths(&["v1", "checkstate"])?;
-        let request = CheckStateRequest { ys };
 
 
         let res = self
         let res = self
             .inner
             .inner
@@ -345,7 +316,7 @@ impl HttpClient {
 
 
     /// Restore request [NUT-13]
     /// Restore request [NUT-13]
     #[instrument(skip(self, request), fields(mint_url = %mint_url))]
     #[instrument(skip(self, request), fields(mint_url = %mint_url))]
-    pub async fn post_restore(
+    async fn post_restore(
         &self,
         &self,
         mint_url: MintUrl,
         mint_url: MintUrl,
         request: RestoreRequest,
         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>;
+}

+ 19 - 12
crates/cdk/src/wallet/melt.rs

@@ -4,6 +4,7 @@ use lightning_invoice::Bolt11Invoice;
 use tracing::instrument;
 use tracing::instrument;
 
 
 use crate::nuts::nut00::ProofsMethods;
 use crate::nuts::nut00::ProofsMethods;
+use crate::nuts::{MeltBolt11Request, MeltQuoteBolt11Request, Mpp};
 use crate::{
 use crate::{
     dhke::construct_proofs,
     dhke::construct_proofs,
     nuts::{CurrencyUnit, MeltQuoteBolt11Response, PreMintSecrets, Proofs, State},
     nuts::{CurrencyUnit, MeltQuoteBolt11Response, PreMintSecrets, Proofs, State},
@@ -57,9 +58,17 @@ impl Wallet {
             _ => return Err(Error::UnitUnsupported),
             _ => return Err(Error::UnitUnsupported),
         };
         };
 
 
+        let options = mpp.map(|amount| Mpp { amount });
+
+        let quote_request = MeltQuoteBolt11Request {
+            request: Bolt11Invoice::from_str(&request)?,
+            unit: self.unit.clone(),
+            options,
+        };
+
         let quote_res = self
         let quote_res = self
             .client
             .client
-            .post_melt_quote(self.mint_url.clone(), self.unit, invoice, mpp)
+            .post_melt_quote(self.mint_url.clone(), quote_request)
             .await?;
             .await?;
 
 
         if quote_res.amount != amount {
         if quote_res.amount != amount {
@@ -70,7 +79,7 @@ impl Wallet {
             id: quote_res.quote,
             id: quote_res.quote,
             amount,
             amount,
             request,
             request,
-            unit: self.unit,
+            unit: self.unit.clone(),
             fee_reserve: quote_res.fee_reserve,
             fee_reserve: quote_res.fee_reserve,
             state: quote_res.state,
             state: quote_res.state,
             expiry: quote_res.expiry,
             expiry: quote_res.expiry,
@@ -146,15 +155,13 @@ impl Wallet {
             proofs_total - quote_info.amount,
             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 {
         let melt_response = match melt_response {
             Ok(melt_response) => melt_response,
             Ok(melt_response) => melt_response,
@@ -226,7 +233,7 @@ impl Wallet {
                             proof,
                             proof,
                             self.mint_url.clone(),
                             self.mint_url.clone(),
                             State::Unspent,
                             State::Unspent,
-                            quote_info.unit,
+                            quote_info.unit.clone(),
                         )
                         )
                     })
                     })
                     .collect::<Result<Vec<ProofInfo>, _>>()?
                     .collect::<Result<Vec<ProofInfo>, _>>()?

+ 17 - 5
crates/cdk/src/wallet/mint.rs

@@ -2,6 +2,7 @@ use tracing::instrument;
 
 
 use super::MintQuote;
 use super::MintQuote;
 use crate::nuts::nut00::ProofsMethods;
 use crate::nuts::nut00::ProofsMethods;
+use crate::nuts::{MintBolt11Request, MintQuoteBolt11Request};
 use crate::{
 use crate::{
     amount::SplitTarget,
     amount::SplitTarget,
     dhke::construct_proofs,
     dhke::construct_proofs,
@@ -45,7 +46,7 @@ impl Wallet {
         description: Option<String>,
         description: Option<String>,
     ) -> Result<MintQuote, Error> {
     ) -> Result<MintQuote, Error> {
         let mint_url = self.mint_url.clone();
         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 we have a description, we check that the mint supports it.
         if description.is_some() {
         if description.is_some() {
@@ -64,16 +65,22 @@ impl Wallet {
             }
             }
         }
         }
 
 
+        let request = MintQuoteBolt11Request {
+            amount,
+            unit: unit.clone(),
+            description,
+        };
+
         let quote_res = self
         let quote_res = self
             .client
             .client
-            .post_mint_quote(mint_url.clone(), amount, unit, description)
+            .post_mint_quote(mint_url.clone(), request)
             .await?;
             .await?;
 
 
         let quote = MintQuote {
         let quote = MintQuote {
             mint_url,
             mint_url,
             id: quote_res.quote.clone(),
             id: quote_res.quote.clone(),
             amount,
             amount,
-            unit,
+            unit: unit.clone(),
             request: quote_res.request,
             request: quote_res.request,
             state: quote_res.state,
             state: quote_res.state,
             expiry: quote_res.expiry.unwrap_or(0),
             expiry: quote_res.expiry.unwrap_or(0),
@@ -212,9 +219,14 @@ impl Wallet {
             )?,
             )?,
         };
         };
 
 
+        let request = MintBolt11Request {
+            quote: quote_id.to_string(),
+            outputs: premint_secrets.blinded_messages(),
+        };
+
         let mint_res = self
         let mint_res = self
             .client
             .client
-            .post_mint(self.mint_url.clone(), quote_id, premint_secrets.clone())
+            .post_mint(self.mint_url.clone(), request)
             .await?;
             .await?;
 
 
         let keys = self.get_keyset_keys(active_keyset_id).await?;
         let keys = self.get_keyset_keys(active_keyset_id).await?;
@@ -257,7 +269,7 @@ impl Wallet {
                     proof,
                     proof,
                     self.mint_url.clone(),
                     self.mint_url.clone(),
                     State::Unspent,
                     State::Unspent,
-                    quote_info.unit,
+                    quote_info.unit.clone(),
                 )
                 )
             })
             })
             .collect::<Result<Vec<ProofInfo>, _>>()?;
             .collect::<Result<Vec<ProofInfo>, _>>()?;

+ 11 - 5
crates/cdk/src/wallet/mod.rs

@@ -6,6 +6,7 @@ use std::sync::Arc;
 
 
 use bitcoin::bip32::Xpriv;
 use bitcoin::bip32::Xpriv;
 use bitcoin::Network;
 use bitcoin::Network;
+use client::HttpClientMethods;
 use tracing::instrument;
 use tracing::instrument;
 
 
 use crate::amount::SplitTarget;
 use crate::amount::SplitTarget;
@@ -55,7 +56,7 @@ pub struct Wallet {
     /// The targeted amount of proofs to have at each size
     /// The targeted amount of proofs to have at each size
     pub target_proof_count: usize,
     pub target_proof_count: usize,
     xpriv: Xpriv,
     xpriv: Xpriv,
-    client: HttpClient,
+    client: Arc<dyn HttpClientMethods + Send + Sync>,
 }
 }
 
 
 impl Wallet {
 impl Wallet {
@@ -88,7 +89,7 @@ impl Wallet {
         Ok(Self {
         Ok(Self {
             mint_url: MintUrl::from_str(mint_url)?,
             mint_url: MintUrl::from_str(mint_url)?,
             unit,
             unit,
-            client: HttpClient::new(),
+            client: Arc::new(HttpClient::new()),
             localstore,
             localstore,
             xpriv,
             xpriv,
             target_proof_count: target_proof_count.unwrap_or(3),
             target_proof_count: target_proof_count.unwrap_or(3),
@@ -96,8 +97,8 @@ impl Wallet {
     }
     }
 
 
     /// Change HTTP client
     /// 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
     /// Fee required for proof set
@@ -329,7 +330,12 @@ impl Wallet {
                 let unspent_proofs = unspent_proofs
                 let unspent_proofs = unspent_proofs
                     .into_iter()
                     .into_iter()
                     .map(|proof| {
                     .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>, _>>()?;
                     .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: Arc::new(Mutex::new(
                 wallets
                 wallets
                     .into_iter()
                     .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(),
                     .collect(),
             )),
             )),
         }
         }
@@ -64,7 +64,7 @@ impl MultiMintWallet {
     /// Add wallet to MultiMintWallet
     /// Add wallet to MultiMintWallet
     #[instrument(skip(self, wallet))]
     #[instrument(skip(self, wallet))]
     pub async fn add_wallet(&self, wallet: 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;
         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() {
         for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
             let wallet_proofs = wallet.get_unspent_proofs().await?;
             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)
         Ok(mint_proofs)
     }
     }
@@ -198,7 +198,7 @@ impl MultiMintWallet {
                     let amount = wallet.check_all_mint_quotes().await?;
                     let amount = wallet.check_all_mint_quotes().await?;
 
 
                     amount_minted
                     amount_minted
-                        .entry(wallet.unit)
+                        .entry(wallet.unit.clone())
                         .and_modify(|b| *b += amount)
                         .and_modify(|b| *b += amount)
                         .or_insert(amount);
                         .or_insert(amount);
                 }
                 }
@@ -246,7 +246,7 @@ impl MultiMintWallet {
         let mint_url = token_data.mint_url()?;
         let mint_url = token_data.mint_url()?;
 
 
         // Check that all mints in tokes have wallets
         // 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 {
         if !self.has(&wallet_key).await {
             return Err(Error::UnknownWallet(wallet_key.clone()));
             return Err(Error::UnknownWallet(wallet_key.clone()));
         }
         }

+ 8 - 4
crates/cdk/src/wallet/proofs.rs

@@ -3,6 +3,7 @@ use std::collections::HashSet;
 use tracing::instrument;
 use tracing::instrument;
 
 
 use crate::nuts::nut00::ProofsMethods;
 use crate::nuts::nut00::ProofsMethods;
+use crate::nuts::CheckStateRequest;
 use crate::{
 use crate::{
     amount::SplitTarget,
     amount::SplitTarget,
     nuts::{Proof, ProofState, Proofs, PublicKey, SpendingConditions, State},
     nuts::{Proof, ProofState, Proofs, PublicKey, SpendingConditions, State},
@@ -40,7 +41,7 @@ impl Wallet {
             .localstore
             .localstore
             .get_proofs(
             .get_proofs(
                 Some(self.mint_url.clone()),
                 Some(self.mint_url.clone()),
-                Some(self.unit),
+                Some(self.unit.clone()),
                 state,
                 state,
                 spending_conditions,
                 spending_conditions,
             )
             )
@@ -65,7 +66,7 @@ impl Wallet {
 
 
         let spendable = self
         let spendable = self
             .client
             .client
-            .post_check_state(self.mint_url.clone(), proof_ys)
+            .post_check_state(self.mint_url.clone(), CheckStateRequest { ys: proof_ys })
             .await?
             .await?
             .states;
             .states;
 
 
@@ -86,7 +87,10 @@ impl Wallet {
     pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Error> {
     pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Error> {
         let spendable = self
         let spendable = self
             .client
             .client
-            .post_check_state(self.mint_url.clone(), proofs.ys()?)
+            .post_check_state(
+                self.mint_url.clone(),
+                CheckStateRequest { ys: proofs.ys()? },
+            )
             .await?;
             .await?;
         let spent_ys: Vec<_> = spendable
         let spent_ys: Vec<_> = spendable
             .states
             .states
@@ -111,7 +115,7 @@ impl Wallet {
             .localstore
             .localstore
             .get_proofs(
             .get_proofs(
                 Some(self.mint_url.clone()),
                 Some(self.mint_url.clone()),
-                Some(self.unit),
+                Some(self.unit.clone()),
                 Some(vec![State::Pending, State::Reserved]),
                 Some(vec![State::Pending, State::Reserved]),
                 None,
                 None,
             )
             )

+ 2 - 2
crates/cdk/src/wallet/receive.rs

@@ -111,7 +111,7 @@ impl Wallet {
         let proofs_info = proofs
         let proofs_info = proofs
             .clone()
             .clone()
             .into_iter()
             .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>, _>>()?;
             .collect::<Result<Vec<ProofInfo>, _>>()?;
         self.localstore
         self.localstore
             .update_proofs(proofs_info.clone(), vec![])
             .update_proofs(proofs_info.clone(), vec![])
@@ -150,7 +150,7 @@ impl Wallet {
 
 
         let recv_proof_infos = recv_proofs
         let recv_proof_infos = recv_proofs
             .into_iter()
             .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>, _>>()?;
             .collect::<Result<Vec<ProofInfo>, _>>()?;
         self.localstore
         self.localstore
             .update_proofs(
             .update_proofs(

+ 6 - 1
crates/cdk/src/wallet/send.rs

@@ -16,7 +16,12 @@ impl Wallet {
         let ys = proofs.ys()?;
         let ys = proofs.ys()?;
         self.localstore.reserve_proofs(ys).await?;
         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
     /// Send

+ 5 - 3
crates/cdk/src/wallet/swap.rs

@@ -111,7 +111,9 @@ impl Wallet {
                 let send_proofs_info = proofs_to_send
                 let send_proofs_info = proofs_to_send
                     .clone()
                     .clone()
                     .into_iter()
                     .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>, _>>()?;
                     .collect::<Result<Vec<ProofInfo>, _>>()?;
                 added_proofs = send_proofs_info;
                 added_proofs = send_proofs_info;
 
 
@@ -126,7 +128,7 @@ impl Wallet {
 
 
         let keep_proofs = change_proofs
         let keep_proofs = change_proofs
             .into_iter()
             .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>, _>>()?;
             .collect::<Result<Vec<ProofInfo>, _>>()?;
         added_proofs.extend(keep_proofs);
         added_proofs.extend(keep_proofs);
 
 
@@ -154,7 +156,7 @@ impl Wallet {
             .localstore
             .localstore
             .get_proofs(
             .get_proofs(
                 Some(self.mint_url.clone()),
                 Some(self.mint_url.clone()),
-                Some(self.unit),
+                Some(self.unit.clone()),
                 Some(vec![State::Unspent]),
                 Some(vec![State::Unspent]),
                 None,
                 None,
             )
             )

+ 6 - 6
flake.lock

@@ -57,11 +57,11 @@
     },
     },
     "nixpkgs": {
     "nixpkgs": {
       "locked": {
       "locked": {
-        "lastModified": 1730137625,
-        "narHash": "sha256-9z8oOgFZiaguj+bbi3k4QhAD6JabWrnv7fscC/mt0KE=",
+        "lastModified": 1730741070,
+        "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
         "owner": "NixOS",
         "owner": "NixOS",
         "repo": "nixpkgs",
         "repo": "nixpkgs",
-        "rev": "64b80bfb316b57cdb8919a9110ef63393d74382a",
+        "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
         "type": "github"
         "type": "github"
       },
       },
       "original": {
       "original": {
@@ -139,11 +139,11 @@
         ]
         ]
       },
       },
       "locked": {
       "locked": {
-        "lastModified": 1730341826,
-        "narHash": "sha256-RFaeY7EWzXOmAL2IQEACbnrEza3TgD5UQApHR4hGHhY=",
+        "lastModified": 1730687492,
+        "narHash": "sha256-xQVadjquBA/tFxDt5A55LJ1D1AvkVWsnrKC2o+pr8F4=",
         "owner": "oxalica",
         "owner": "oxalica",
         "repo": "rust-overlay",
         "repo": "rust-overlay",
-        "rev": "815d1b3ee71716fc91a7bd149801e1f04d45fbc5",
+        "rev": "41814763a2c597755b0755dbe3e721367a5e420f",
         "type": "github"
         "type": "github"
       },
       },
       "original": {
       "original": {

+ 4 - 2
flake.nix

@@ -37,7 +37,7 @@
 
 
         # Toolchains
         # Toolchains
         # latest stable
         # latest stable
-        stable_toolchain = pkgs.rust-bin.stable.latest.default.override {
+        stable_toolchain = pkgs.rust-bin.stable."1.82.0".default.override {
           targets = [ "wasm32-unknown-unknown" ]; # wasm
           targets = [ "wasm32-unknown-unknown" ]; # wasm
         };
         };
 
 
@@ -63,7 +63,7 @@
           pkg-config
           pkg-config
           curl
           curl
           just
           just
-          protobuf3_20
+          protobuf
           nixpkgs-fmt
           nixpkgs-fmt
           rust-analyzer
           rust-analyzer
           typos
           typos
@@ -139,6 +139,7 @@
               cargo update -p bumpalo --precise 3.12.0
               cargo update -p bumpalo --precise 3.12.0
               cargo update -p moka --precise 0.11.1
               cargo update -p moka --precise 0.11.1
               cargo update -p triomphe --precise 0.1.11
               cargo update -p triomphe --precise 0.1.11
+              cargo update -p url --precise 2.5.2
               ";
               ";
               buildInputs = buildInputs ++ WASMInputs ++ [ msrv_toolchain ];
               buildInputs = buildInputs ++ WASMInputs ++ [ msrv_toolchain ];
               inherit nativeBuildInputs;
               inherit nativeBuildInputs;
@@ -160,6 +161,7 @@
               cargo update -p tokio-stream --precise 0.1.15
               cargo update -p tokio-stream --precise 0.1.15
               cargo update -p serde_with --precise 3.1.0
               cargo update -p serde_with --precise 3.1.0
               cargo update -p reqwest --precise 0.12.4
               cargo update -p reqwest --precise 0.12.4
+              cargo update -p url --precise 2.5.2
               ";
               ";
               buildInputs = buildInputs ++ WASMInputs ++ [ db_msrv_toolchain ];
               buildInputs = buildInputs ++ WASMInputs ++ [ db_msrv_toolchain ];
               inherit nativeBuildInputs;
               inherit nativeBuildInputs;

+ 1 - 1
justfile

@@ -45,7 +45,7 @@ test: build
   if [ ! -f Cargo.toml ]; then
   if [ ! -f Cargo.toml ]; then
     cd {{invocation_directory()}}
     cd {{invocation_directory()}}
   fi
   fi
-  cargo test
+  cargo test --lib
 
 
 # run `cargo clippy` on everything
 # run `cargo clippy` on everything
 clippy *ARGS="--locked --offline --workspace --all-targets":
 clippy *ARGS="--locked --offline --workspace --all-targets":

+ 4 - 0
rust-toolchain.toml

@@ -0,0 +1,4 @@
+[toolchain]
+channel="1.82.0"
+components = ["rustfmt", "clippy", "rust-analyzer"]
+