Эх сурвалжийг харах

Refactor wallet to accept seed for internal Xpriv

David Caseria 10 сар өмнө
parent
commit
b9234817db

+ 1 - 1
Cargo.toml

@@ -28,7 +28,7 @@ cdk = { path = "./crates/cdk", default-features = false }
 cdk-rexie = { path = "./crates/cdk-rexie", default-features = false }
 tokio = { version = "1.32", default-features = false }
 thiserror = "1"
-tracing = { version = "0.1", default-features = false }
+tracing = { version = "0.1", default-features = false, features = ["attributes"] }
 serde = { version = "1", default-features = false, features = ["derive"] }
 serde_json = "1"
 serde-wasm-bindgen = { version = "0.6.5", default-features = false }

+ 1 - 0
bindings/cdk-js/src/lib.rs

@@ -3,6 +3,7 @@ use wasm_bindgen::prelude::*;
 pub mod error;
 pub mod nuts;
 pub mod types;
+#[cfg(all(feature = "wallet", target_arch = "wasm32"))]
 pub mod wallet;
 
 #[wasm_bindgen(start)]

+ 38 - 2
bindings/cdk-js/src/wallet.rs

@@ -10,6 +10,8 @@ use cdk_rexie::RexieWalletDatabase;
 use wasm_bindgen::prelude::*;
 
 use crate::error::{into_err, Result};
+use crate::nuts::nut04::JsMintQuoteBolt11Response;
+use crate::nuts::nut05::JsMeltQuoteBolt11Response;
 use crate::nuts::nut11::JsP2PKSpendingConditions;
 use crate::nuts::nut14::JsHTLCSpendingConditions;
 use crate::nuts::{JsCurrencyUnit, JsMintInfo, JsProof};
@@ -37,11 +39,11 @@ impl From<Wallet> for JsWallet {
 #[wasm_bindgen(js_class = Wallet)]
 impl JsWallet {
     #[wasm_bindgen(constructor)]
-    pub async fn new() -> Self {
+    pub async fn new(seed: Vec<u8>) -> Self {
         let client = HttpClient::new();
         let db = RexieWalletDatabase::new().await.unwrap();
 
-        Wallet::new(client, Arc::new(db), None).await.into()
+        Wallet::new(client, Arc::new(db), &seed).await.into()
     }
 
     #[wasm_bindgen(js_name = totalBalance)]
@@ -95,6 +97,23 @@ impl JsWallet {
         Ok(quote.into())
     }
 
+    #[wasm_bindgen(js_name = mintQuoteStatus)]
+    pub async fn mint_quote_status(
+        &self,
+        mint_url: String,
+        quote_id: String,
+    ) -> Result<JsMintQuoteBolt11Response> {
+        let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
+
+        let quote = self
+            .inner
+            .mint_quote_status(mint_url, &quote_id)
+            .await
+            .map_err(into_err)?;
+
+        Ok(quote.into())
+    }
+
     #[wasm_bindgen(js_name = mint)]
     pub async fn mint(&mut self, mint_url: String, quote_id: String) -> Result<JsAmount> {
         let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
@@ -124,6 +143,23 @@ impl JsWallet {
         Ok(melt_quote.into())
     }
 
+    #[wasm_bindgen(js_name = meltQuoteStatus)]
+    pub async fn melt_quote_status(
+        &self,
+        mint_url: String,
+        quote_id: String,
+    ) -> Result<JsMeltQuoteBolt11Response> {
+        let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
+
+        let quote = self
+            .inner
+            .melt_quote_status(mint_url, &quote_id)
+            .await
+            .map_err(into_err)?;
+
+        Ok(quote.into())
+    }
+
     #[wasm_bindgen(js_name = melt)]
     pub async fn melt(&mut self, mint_url: String, quote_id: String) -> Result<JsMelted> {
         let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;

+ 26 - 3
crates/cdk-redb/src/wallet.rs

@@ -10,6 +10,7 @@ use cdk::types::{MeltQuote, MintQuote};
 use cdk::url::UncheckedUrl;
 use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
 use tokio::sync::Mutex;
+use tracing::instrument;
 
 use super::error::Error;
 
@@ -23,7 +24,7 @@ const PROOFS_TABLE: MultimapTableDefinition<&str, &str> = MultimapTableDefinitio
 const PENDING_PROOFS_TABLE: MultimapTableDefinition<&str, &str> =
     MultimapTableDefinition::new("pending_proofs");
 const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
-const KEYSET_COUNTER: TableDefinition<&str, u64> = TableDefinition::new("keyset_counter");
+const KEYSET_COUNTER: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
 
 const DATABASE_VERSION: u32 = 0;
 
@@ -79,6 +80,7 @@ impl RedbWalletDatabase {
 impl WalletDatabase for RedbWalletDatabase {
     type Err = cdk_database::Error;
 
+    #[instrument(skip(self))]
     async fn add_mint(
         &self,
         mint_url: UncheckedUrl,
@@ -104,6 +106,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self))]
     async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Into::<Error>::into)?;
@@ -119,6 +122,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(None)
     }
 
+    #[instrument(skip(self))]
     async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
@@ -138,6 +142,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(mints)
     }
 
+    #[instrument(skip(self))]
     async fn add_mint_keysets(
         &self,
         mint_url: UncheckedUrl,
@@ -168,6 +173,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self))]
     async fn get_mint_keysets(
         &self,
         mint_url: UncheckedUrl,
@@ -188,6 +194,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(keysets)
     }
 
+    #[instrument(skip_all)]
     async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -209,6 +216,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip_all)]
     async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Into::<Error>::into)?;
@@ -223,6 +231,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(None)
     }
 
+    #[instrument(skip_all)]
     async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -239,6 +248,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip_all)]
     async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -260,6 +270,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip_all)]
     async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
@@ -274,6 +285,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(None)
     }
 
+    #[instrument(skip_all)]
     async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -290,6 +302,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip_all)]
     async fn add_keys(&self, keys: Keys) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -309,6 +322,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self))]
     async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
@@ -321,6 +335,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(None)
     }
 
+    #[instrument(skip(self))]
     async fn remove_keys(&self, id: &Id) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
         let write_txn = db.begin_write().map_err(Error::from)?;
@@ -336,6 +351,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self, proofs))]
     async fn add_proofs(&self, mint_url: UncheckedUrl, proofs: Proofs) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
 
@@ -360,6 +376,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self))]
     async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
@@ -377,6 +394,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(proofs)
     }
 
+    #[instrument(skip(self, proofs))]
     async fn remove_proofs(
         &self,
         mint_url: UncheckedUrl,
@@ -405,6 +423,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self, proofs))]
     async fn add_pending_proofs(
         &self,
         mint_url: UncheckedUrl,
@@ -433,6 +452,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
+    #[instrument(skip(self))]
     async fn get_pending_proofs(
         &self,
         mint_url: UncheckedUrl,
@@ -453,6 +473,7 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(proofs)
     }
 
+    #[instrument(skip(self, proofs))]
     async fn remove_pending_proofs(
         &self,
         mint_url: UncheckedUrl,
@@ -481,7 +502,8 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
-    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Self::Err> {
+    #[instrument(skip(self))]
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err> {
         let db = self.db.lock().await;
 
         let current_counter;
@@ -512,7 +534,8 @@ impl WalletDatabase for RedbWalletDatabase {
         Ok(())
     }
 
-    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Self::Err> {
+    #[instrument(skip(self))]
+    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> {
         let db = self.db.lock().await;
         let read_txn = db.begin_read().map_err(Error::from)?;
         let table = read_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;

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

@@ -592,7 +592,7 @@ impl WalletDatabase for RexieWalletDatabase {
         Ok(())
     }
 
-    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Self::Err> {
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err> {
         let rexie = self.db.lock().await;
 
         let transaction = rexie
@@ -604,7 +604,7 @@ impl WalletDatabase for RexieWalletDatabase {
         let keyset_id = serde_wasm_bindgen::to_value(keyset_id).map_err(Error::from)?;
 
         let current_count = counter_store.get(&keyset_id).await.map_err(Error::from)?;
-        let current_count: Option<u64> =
+        let current_count: Option<u32> =
             serde_wasm_bindgen::from_value(current_count).map_err(Error::from)?;
 
         let new_count = current_count.unwrap_or_default() + count;
@@ -621,7 +621,7 @@ impl WalletDatabase for RexieWalletDatabase {
         Ok(())
     }
 
-    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Self::Err> {
+    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> {
         let rexie = self.db.lock().await;
 
         let transaction = rexie
@@ -633,7 +633,7 @@ impl WalletDatabase for RexieWalletDatabase {
         let keyset_id = serde_wasm_bindgen::to_value(keyset_id).map_err(Error::from)?;
 
         let current_count = counter_store.get(&keyset_id).await.map_err(Error::from)?;
-        let current_count: Option<u64> =
+        let current_count: Option<u32> =
             serde_wasm_bindgen::from_value(current_count).map_err(Error::from)?;
 
         Ok(current_count)

+ 22 - 8
crates/cdk/Cargo.toml

@@ -21,25 +21,39 @@ nut13 = ["dep:bip39"]
 async-trait = "0.1"
 base64 = "0.22" # bitcoin uses v0.13 (optional dep)
 bip39 = { version = "2.0", optional = true }
-bitcoin = { version = "0.30", features = ["serde", "rand", "rand-std"] } # lightning-invoice uses v0.30
+bitcoin = { version = "0.30", features = [
+    "serde",
+    "rand",
+    "rand-std",
+] } # lightning-invoice uses v0.30
 http = "1.0"
 lightning-invoice = { version = "0.30", features = ["serde"] }
 once_cell = "1.19"
-reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "socks"], optional = true }
-serde = { version = "1.0", default-features = false, features = ["derive"]}
+reqwest = { version = "0.12", default-features = false, features = [
+    "json",
+    "rustls-tls",
+    "socks",
+], optional = true }
+serde = { version = "1.0", default-features = false, features = ["derive"] }
 serde_json = "1.0"
 serde_with = "3.4"
-tracing = { version = "0.1", default-features = false }
+tracing = { version = "0.1", default-features = false, features = [
+    "attributes",
+    "log",
+] }
 thiserror = "1.0"
 url = "2.3"
 uuid = { version = "1.6", features = ["v4"] }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync"] }
+tokio = { workspace = true, features = [
+    "rt-multi-thread",
+    "time",
+    "macros",
+    "sync",
+] }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 tokio = { workspace = true, features = ["rt", "macros", "sync", "time"] }
 getrandom = { version = "0.2", features = ["js"] }
-instant = { version = "0.1", features = [ "wasm-bindgen", "inaccurate" ] }
-
-
+instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] }

+ 2 - 2
crates/cdk/src/cdk_database/mod.rs

@@ -88,8 +88,8 @@ pub trait WalletDatabase {
         proofs: &Proofs,
     ) -> Result<(), Self::Err>;
 
-    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Self::Err>;
-    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Self::Err>;
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>;
+    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err>;
 }
 
 #[cfg(feature = "mint")]

+ 4 - 4
crates/cdk/src/cdk_database/wallet_memory.rs

@@ -21,7 +21,7 @@ pub struct WalletMemoryDatabase {
     mint_keys: Arc<Mutex<HashMap<Id, Keys>>>,
     proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
     pending_proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
-    keyset_counter: Arc<Mutex<HashMap<Id, u64>>>,
+    keyset_counter: Arc<Mutex<HashMap<Id, u32>>>,
 }
 
 impl WalletMemoryDatabase {
@@ -29,7 +29,7 @@ impl WalletMemoryDatabase {
         mint_quotes: Vec<MintQuote>,
         melt_quotes: Vec<MeltQuote>,
         mint_keys: Vec<Keys>,
-        keyset_counter: HashMap<Id, u64>,
+        keyset_counter: HashMap<Id, u32>,
     ) -> Self {
         Self {
             mints: Arc::new(Mutex::new(HashMap::new())),
@@ -215,7 +215,7 @@ impl WalletDatabase for WalletMemoryDatabase {
         Ok(())
     }
 
-    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Error> {
         let keyset_counter = self.keyset_counter.lock().await;
         let current_counter = keyset_counter.get(keyset_id).unwrap_or(&0);
         self.keyset_counter
@@ -225,7 +225,7 @@ impl WalletDatabase for WalletMemoryDatabase {
         Ok(())
     }
 
-    async fn get_keyset_counter(&self, id: &Id) -> Result<Option<u64>, Error> {
+    async fn get_keyset_counter(&self, id: &Id) -> Result<Option<u32>, Error> {
         Ok(self.keyset_counter.lock().await.get(id).cloned())
     }
 }

+ 56 - 0
crates/cdk/src/client.rs

@@ -3,6 +3,7 @@
 use reqwest::Client;
 use serde_json::Value;
 use thiserror::Error;
+use tracing::instrument;
 use url::Url;
 
 use crate::error::ErrorResponse;
@@ -74,6 +75,7 @@ impl HttpClient {
     }
 
     /// Get Active Mint Keys [NUT-01]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_mint_keys(&self, mint_url: Url) -> Result<Vec<KeySet>, Error> {
         let url = join_url(mint_url, &["v1", "keys"])?;
         let keys = self.inner.get(url).send().await?.json::<Value>().await?;
@@ -83,6 +85,7 @@ impl HttpClient {
     }
 
     /// Get Keyset Keys [NUT-01]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_mint_keyset(&self, mint_url: Url, keyset_id: Id) -> Result<KeySet, Error> {
         let url = join_url(mint_url, &["v1", "keys", &keyset_id.to_string()])?;
         let keys = self
@@ -99,6 +102,7 @@ impl HttpClient {
     }
 
     /// Get Keysets [NUT-02]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error> {
         let url = join_url(mint_url, &["v1", "keysets"])?;
         let res = self.inner.get(url).send().await?.json::<Value>().await?;
@@ -113,6 +117,7 @@ impl HttpClient {
     }
 
     /// Mint Quote [NUT-04]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn post_mint_quote(
         &self,
         mint_url: Url,
@@ -136,7 +141,30 @@ impl HttpClient {
         }
     }
 
+    /// Mint Quote status
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
+    pub async fn get_mint_quote_status(
+        &self,
+        mint_url: Url,
+        quote_id: &str,
+    ) -> Result<MintQuoteBolt11Response, Error> {
+        let url = join_url(mint_url, &["v1", "mint", "quote", "bolt11", quote_id])?;
+
+        let res = self.inner.get(url).send().await?;
+
+        let status = res.status();
+
+        let response: Result<MintQuoteBolt11Response, serde_json::Error> =
+            serde_json::from_value(res.json().await?);
+
+        match response {
+            Ok(res) => Ok(res),
+            Err(_) => Err(ErrorResponse::from_json(&status.to_string())?.into()),
+        }
+    }
+
     /// Mint Tokens [NUT-04]
+    #[instrument(skip(self, quote, premint_secrets), fields(mint_url = %mint_url))]
     pub async fn post_mint(
         &self,
         mint_url: Url,
@@ -169,6 +197,7 @@ impl HttpClient {
     }
 
     /// Melt Quote [NUT-05]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn post_melt_quote(
         &self,
         mint_url: Url,
@@ -192,8 +221,31 @@ impl HttpClient {
         }
     }
 
+    /// Melt Quote Status
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
+    pub async fn get_melt_quote_status(
+        &self,
+        mint_url: Url,
+        quote_id: &str,
+    ) -> Result<MeltQuoteBolt11Response, Error> {
+        let url = join_url(mint_url, &["v1", "melt", "quote", "bolt11", quote_id])?;
+
+        let res = self.inner.get(url).send().await?;
+
+        let status = res.status();
+
+        let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
+            serde_json::from_value(res.json().await?);
+
+        match response {
+            Ok(res) => Ok(res),
+            Err(_) => Err(ErrorResponse::from_json(&status.to_string())?.into()),
+        }
+    }
+
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
+    #[instrument(skip(self, quote, inputs, outputs), fields(mint_url = %mint_url))]
     pub async fn post_melt(
         &self,
         mint_url: Url,
@@ -222,6 +274,7 @@ impl HttpClient {
     }
 
     /// Split Token [NUT-06]
+    #[instrument(skip(self, swap_request), fields(mint_url = %mint_url))]
     pub async fn post_swap(
         &self,
         mint_url: Url,
@@ -241,6 +294,7 @@ impl HttpClient {
     }
 
     /// Get Mint Info [NUT-06]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error> {
         let url = join_url(mint_url, &["v1", "info"])?;
 
@@ -255,6 +309,7 @@ impl HttpClient {
     }
 
     /// Spendable check [NUT-07]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn post_check_state(
         &self,
         mint_url: Url,
@@ -281,6 +336,7 @@ impl HttpClient {
         }
     }
 
+    #[instrument(skip(self, request), fields(mint_url = %mint_url))]
     pub async fn post_restore(
         &self,
         mint_url: Url,

+ 44 - 47
crates/cdk/src/nuts/nut13.rs

@@ -2,11 +2,7 @@
 //!
 //! <https://github.com/cashubtc/nuts/blob/main/13.md>
 
-use core::str::FromStr;
-
-use bip39::Mnemonic;
-use bitcoin::bip32::{DerivationPath, ExtendedPrivKey};
-use bitcoin::Network;
+use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey};
 
 use super::nut00::{BlindedMessage, PreMint, PreMintSecrets};
 use super::nut01::SecretKey;
@@ -18,21 +14,12 @@ use crate::util::hex;
 use crate::{Amount, SECP256K1};
 
 impl Secret {
-    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
-        tracing::debug!(
-            "Deriving secret for {} with count {}",
-            keyset_id.to_string(),
-            counter.to_string()
-        );
-        let path: DerivationPath = DerivationPath::from_str(&format!(
-            "m/129372'/0'/{}'/{}'/0",
-            u64::try_from(keyset_id)?,
-            counter
-        ))?;
-
-        let seed: [u8; 64] = mnemonic.to_seed("");
-        let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
-        let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
+    pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> {
+        tracing::debug!("Deriving secret for {} with count {}", keyset_id, counter);
+        let path = derive_path_from_keyset_id(keyset_id)?
+            .child(ChildNumber::from_hardened_idx(counter)?)
+            .child(ChildNumber::from_normal_idx(0)?);
+        let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
 
         Ok(Self::new(hex::encode(
             derived_xpriv.private_key.secret_bytes(),
@@ -41,21 +28,12 @@ impl Secret {
 }
 
 impl SecretKey {
-    pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
-        tracing::debug!(
-            "Deriving key for {} with count {}",
-            keyset_id.to_string(),
-            counter.to_string()
-        );
-        let path = DerivationPath::from_str(&format!(
-            "m/129372'/0'/{}'/{}'/1",
-            u64::try_from(keyset_id)?,
-            counter
-        ))?;
-
-        let seed: [u8; 64] = mnemonic.to_seed("");
-        let bip32_root_key = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?;
-        let derived_xpriv = bip32_root_key.derive_priv(&SECP256K1, &path)?;
+    pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> {
+        tracing::debug!("Deriving key for {} with count {}", keyset_id, counter);
+        let path = derive_path_from_keyset_id(keyset_id)?
+            .child(ChildNumber::from_hardened_idx(counter)?)
+            .child(ChildNumber::from_normal_idx(1)?);
+        let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
 
         Ok(Self::from(derived_xpriv.private_key))
     }
@@ -64,10 +42,10 @@ impl SecretKey {
 impl PreMintSecrets {
     /// Generate blinded messages from predetermined secrets and blindings
     /// factor
-    pub fn from_seed(
+    pub fn from_xpriv(
         keyset_id: Id,
-        counter: u64,
-        mnemonic: &Mnemonic,
+        counter: u32,
+        xpriv: ExtendedPrivKey,
         amount: Amount,
         zero_amount: bool,
     ) -> Result<Self, Error> {
@@ -76,8 +54,8 @@ impl PreMintSecrets {
         let mut counter = counter;
 
         for amount in amount.split() {
-            let secret = Secret::from_seed(mnemonic, keyset_id, counter)?;
-            let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter)?;
+            let secret = Secret::from_xpriv(xpriv, keyset_id, counter)?;
+            let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, counter)?;
 
             let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
 
@@ -103,15 +81,15 @@ impl PreMintSecrets {
     /// factor
     pub fn restore_batch(
         keyset_id: Id,
-        mnemonic: &Mnemonic,
-        start_count: u64,
-        end_count: u64,
+        xpriv: ExtendedPrivKey,
+        start_count: u32,
+        end_count: u32,
     ) -> Result<Self, Error> {
         let mut pre_mint_secrets = PreMintSecrets::default();
 
         for i in start_count..=end_count {
-            let secret = Secret::from_seed(mnemonic, keyset_id, i)?;
-            let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, i)?;
+            let secret = Secret::from_xpriv(xpriv, keyset_id, i)?;
+            let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, i)?;
 
             let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
 
@@ -131,8 +109,23 @@ impl PreMintSecrets {
     }
 }
 
+fn derive_path_from_keyset_id(id: Id) -> Result<DerivationPath, Error> {
+    let index = (u64::try_from(id)? % (2u64.pow(31) - 1)) as u32;
+    let keyset_child_number = ChildNumber::from_hardened_idx(index)?;
+    Ok(DerivationPath::from(vec![
+        ChildNumber::from_hardened_idx(129372)?,
+        ChildNumber::from_hardened_idx(0)?,
+        keyset_child_number,
+    ]))
+}
+
 #[cfg(test)]
 mod tests {
+    use std::str::FromStr;
+
+    use bip39::Mnemonic;
+    use bitcoin::Network;
+
     use super::*;
 
     #[test]
@@ -140,6 +133,8 @@ mod tests {
         let seed =
             "half depart obvious quality work element tank gorilla view sugar picture humble";
         let mnemonic = Mnemonic::from_str(seed).unwrap();
+        let seed: [u8; 64] = mnemonic.to_seed("");
+        let xpriv = ExtendedPrivKey::new_master(Network::Bitcoin, &seed).unwrap();
         let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
 
         let test_secrets = [
@@ -151,7 +146,7 @@ mod tests {
         ];
 
         for (i, test_secret) in test_secrets.iter().enumerate() {
-            let secret = Secret::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
+            let secret = Secret::from_xpriv(xpriv, keyset_id, i.try_into().unwrap()).unwrap();
             assert_eq!(secret, Secret::from_str(test_secret).unwrap())
         }
     }
@@ -160,6 +155,8 @@ mod tests {
         let seed =
             "half depart obvious quality work element tank gorilla view sugar picture humble";
         let mnemonic = Mnemonic::from_str(seed).unwrap();
+        let seed: [u8; 64] = mnemonic.to_seed("");
+        let xpriv = ExtendedPrivKey::new_master(Network::Bitcoin, &seed).unwrap();
         let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
 
         let test_rs = [
@@ -171,7 +168,7 @@ mod tests {
         ];
 
         for (i, test_r) in test_rs.iter().enumerate() {
-            let r = SecretKey::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
+            let r = SecretKey::from_xpriv(xpriv, keyset_id, i.try_into().unwrap()).unwrap();
             assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
         }
     }

+ 208 - 226
crates/cdk/src/wallet.rs

@@ -5,19 +5,21 @@ use std::num::ParseIntError;
 use std::str::FromStr;
 use std::sync::Arc;
 
-use bip39::Mnemonic;
+use bitcoin::bip32::ExtendedPrivKey;
 use bitcoin::hashes::sha256::Hash as Sha256Hash;
 use bitcoin::hashes::Hash;
+use bitcoin::Network;
 use thiserror::Error;
+use tracing::instrument;
 
-use crate::cdk_database::wallet_memory::WalletMemoryDatabase;
 use crate::cdk_database::{self, WalletDatabase};
 use crate::client::HttpClient;
 use crate::dhke::{construct_proofs, hash_to_curve};
 use crate::nuts::{
-    nut10, nut12, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind, MintInfo,
-    PreMintSecrets, PreSwap, Proof, ProofState, Proofs, PublicKey, RestoreRequest, SigFlag,
-    SigningKey, SpendingConditions, State, SwapRequest, Token, VerifyingKey,
+    nut10, nut12, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind,
+    MeltQuoteBolt11Response, MintInfo, MintQuoteBolt11Response, PreMintSecrets, PreSwap, Proof,
+    ProofState, Proofs, PublicKey, RestoreRequest, SigFlag, SigningKey, SpendingConditions, State,
+    SwapRequest, Token, VerifyingKey,
 };
 use crate::types::{MeltQuote, Melted, MintQuote};
 use crate::url::UncheckedUrl;
@@ -47,9 +49,6 @@ pub enum Error {
     PreimageNotProvided,
     #[error("Unknown Key")]
     UnknownKey,
-    /// Mnemonic Required
-    #[error("Mnemonic Required")]
-    MnemonicRequired,
     /// Spending Locktime not provided
     #[error("Spending condition locktime not provided")]
     LocktimeNotProvided,
@@ -93,38 +92,25 @@ impl From<Error> for cdk_database::Error {
 pub struct Wallet {
     pub client: HttpClient,
     pub localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync>,
-    mnemonic: Option<Mnemonic>,
-}
-
-impl Default for Wallet {
-    fn default() -> Self {
-        Self {
-            localstore: Arc::new(WalletMemoryDatabase::default()),
-            client: HttpClient::default(),
-            mnemonic: None,
-        }
-    }
+    xpriv: ExtendedPrivKey,
 }
 
 impl Wallet {
-    pub async fn new(
-        client: HttpClient,
+    pub fn new(
         localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync>,
-        mnemonic: Option<Mnemonic>,
+        seed: &[u8],
     ) -> Self {
+        let xpriv = ExtendedPrivKey::new_master(Network::Bitcoin, seed)
+            .expect("Could not create master key");
         Self {
-            mnemonic,
-            client,
+            client: HttpClient::new(),
             localstore,
+            xpriv,
         }
     }
 
-    /// Back up seed
-    pub fn mnemonic(&self) -> Option<Mnemonic> {
-        self.mnemonic.clone()
-    }
-
     /// Total Balance of wallet
+    #[instrument(skip(self))]
     pub async fn total_balance(&self) -> Result<Amount, Error> {
         let mints = self.localstore.get_mints().await?;
         let mut balance = Amount::ZERO;
@@ -140,6 +126,7 @@ impl Wallet {
         Ok(balance)
     }
 
+    #[instrument(skip(self))]
     pub async fn mint_balances(&self) -> Result<HashMap<UncheckedUrl, Amount>, Error> {
         let mints = self.localstore.get_mints().await?;
 
@@ -158,10 +145,12 @@ impl Wallet {
         Ok(balances)
     }
 
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
         Ok(self.localstore.get_proofs(mint_url).await?)
     }
 
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn add_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error> {
         let mint_info = match self
             .client
@@ -182,6 +171,7 @@ impl Wallet {
         Ok(mint_info)
     }
 
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_keyset_keys(
         &self,
         mint_url: &UncheckedUrl,
@@ -203,6 +193,7 @@ impl Wallet {
         Ok(keys)
     }
 
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_mint_keysets(
         &self,
         mint_url: &UncheckedUrl,
@@ -217,6 +208,7 @@ impl Wallet {
     }
 
     /// Get active mint keyset
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn get_active_mint_keys(
         &self,
         mint_url: &UncheckedUrl,
@@ -237,6 +229,7 @@ impl Wallet {
     }
 
     /// Refresh Mint keys
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn refresh_mint_keys(&self, mint_url: &UncheckedUrl) -> Result<(), Error> {
         let current_mint_keysets_info = self
             .client
@@ -275,6 +268,7 @@ impl Wallet {
     }
 
     /// Check if a proof is spent
+    #[instrument(skip(self, proofs), fields(mint_url = %mint_url))]
     pub async fn check_proofs_spent(
         &self,
         mint_url: UncheckedUrl,
@@ -296,6 +290,7 @@ impl Wallet {
     }
 
     /// Mint Quote
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn mint_quote(
         &mut self,
         mint_url: UncheckedUrl,
@@ -321,6 +316,34 @@ impl Wallet {
         Ok(quote)
     }
 
+    /// Mint quote status
+    #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))]
+    pub async fn mint_quote_status(
+        &self,
+        mint_url: UncheckedUrl,
+        quote_id: &str,
+    ) -> Result<MintQuoteBolt11Response, Error> {
+        let response = self
+            .client
+            .get_mint_quote_status(mint_url.try_into()?, quote_id)
+            .await?;
+
+        match self.localstore.get_mint_quote(quote_id).await? {
+            Some(quote) => {
+                let mut quote = quote;
+
+                quote.paid = response.paid;
+                self.localstore.add_mint_quote(quote).await?;
+            }
+            None => {
+                tracing::info!("Quote mint {} unknown", quote_id);
+            }
+        }
+
+        Ok(response)
+    }
+
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     async fn active_mint_keyset(
         &mut self,
         mint_url: &UncheckedUrl,
@@ -351,6 +374,7 @@ impl Wallet {
         Err(Error::NoActiveKeyset)
     }
 
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     async fn active_keys(
         &mut self,
         mint_url: &UncheckedUrl,
@@ -376,6 +400,7 @@ impl Wallet {
     }
 
     /// Mint
+    #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))]
     pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Amount, Error> {
         // Check that mint is in store of mints
         if self.localstore.get_mint(mint_url.clone()).await?.is_none() {
@@ -396,42 +421,24 @@ impl Wallet {
 
         let active_keyset_id = self.active_mint_keyset(&mint_url, &quote_info.unit).await?;
 
-        let mut counter: Option<u64> = None;
-
-        let premint_secrets;
-
-        #[cfg(not(feature = "nut13"))]
-        {
-            premint_secrets = PreMintSecrets::random(active_keyset_id, quote_info.amount)?;
-        }
+        let count = self
+            .localstore
+            .get_keyset_counter(&active_keyset_id)
+            .await?;
 
-        #[cfg(feature = "nut13")]
-        {
-            premint_secrets = match &self.mnemonic {
-                Some(mnemonic) => {
-                    let count = self
-                        .localstore
-                        .get_keyset_counter(&active_keyset_id)
-                        .await?;
+        let count = if let Some(count) = count {
+            count + 1
+        } else {
+            0
+        };
 
-                    let count = if let Some(count) = count {
-                        count + 1
-                    } else {
-                        0
-                    };
-
-                    counter = Some(count);
-                    PreMintSecrets::from_seed(
-                        active_keyset_id,
-                        count,
-                        mnemonic,
-                        quote_info.amount,
-                        false,
-                    )?
-                }
-                None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?,
-            };
-        }
+        let premint_secrets = PreMintSecrets::from_xpriv(
+            active_keyset_id,
+            count,
+            self.xpriv,
+            quote_info.amount,
+            false,
+        )?;
 
         let mint_res = self
             .client
@@ -469,12 +476,9 @@ impl Wallet {
         self.localstore.remove_mint_quote(&quote_info.id).await?;
 
         // Update counter for keyset
-        #[cfg(feature = "nut13")]
-        if counter.is_some() {
-            self.localstore
-                .increment_keyset_counter(&active_keyset_id, proofs.len() as u64)
-                .await?;
-        }
+        self.localstore
+            .increment_keyset_counter(&active_keyset_id, proofs.len() as u32)
+            .await?;
 
         // Add new proofs to store
         self.localstore.add_proofs(mint_url, proofs).await?;
@@ -483,6 +487,7 @@ impl Wallet {
     }
 
     /// Swap
+    #[instrument(skip(self, input_proofs), fields(mint_url = %mint_url))]
     pub async fn swap(
         &mut self,
         mint_url: &UncheckedUrl,
@@ -516,14 +521,11 @@ impl Wallet {
                 .ok_or(Error::UnknownKey)?,
         )?;
 
-        #[cfg(feature = "nut13")]
-        if self.mnemonic.is_some() {
-            let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?;
+        let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?;
 
-            self.localstore
-                .increment_keyset_counter(&active_keyset_id, post_swap_proofs.len() as u64)
-                .await?;
-        }
+        self.localstore
+            .increment_keyset_counter(&active_keyset_id, post_swap_proofs.len() as u32)
+            .await?;
 
         let mut keep_proofs = Proofs::new();
         let proofs_to_send;
@@ -590,6 +592,7 @@ impl Wallet {
     }
 
     /// Create Swap Payload
+    #[instrument(skip(self, proofs), fields(mint_url = %mint_url))]
     async fn create_swap(
         &mut self,
         mint_url: &UncheckedUrl,
@@ -606,105 +609,65 @@ impl Wallet {
         let desired_amount = amount.unwrap_or(proofs_total);
         let change_amount = proofs_total - desired_amount;
 
-        let mut desired_messages;
-        let change_messages;
+        let (mut desired_messages, change_messages) = match spending_conditions {
+            Some(conditions) => {
+                let count = self
+                    .localstore
+                    .get_keyset_counter(&active_keyset_id)
+                    .await?;
 
-        #[cfg(not(feature = "nut13"))]
-        {
-            (desired_messages, change_messages) = match spendig_conditions {
-                Some(conditions) => (
+                let count = if let Some(count) = count {
+                    count + 1
+                } else {
+                    0
+                };
+
+                let change_premint_secrets = PreMintSecrets::from_xpriv(
+                    active_keyset_id,
+                    count,
+                    self.xpriv,
+                    change_amount,
+                    false,
+                )?;
+
+                (
                     PreMintSecrets::with_conditions(active_keyset_id, desired_amount, conditions)?,
-                    PreMintSecrets::random(active_keyset_id, change_amount),
-                ),
-                None => (
-                    PreMintSecrets::random(active_keyset_id, proofs_total)?,
-                    PreMintSecrets::default(),
-                ),
-            };
-        }
+                    change_premint_secrets,
+                )
+            }
+            None => {
+                let count = self
+                    .localstore
+                    .get_keyset_counter(&active_keyset_id)
+                    .await?;
 
-        #[cfg(feature = "nut13")]
-        {
-            (desired_messages, change_messages) = match &self.mnemonic {
-                Some(mnemonic) => match spending_conditions {
-                    Some(conditions) => {
-                        let count = self
-                            .localstore
-                            .get_keyset_counter(&active_keyset_id)
-                            .await?;
+                let count = if let Some(count) = count {
+                    count + 1
+                } else {
+                    0
+                };
 
-                        let count = if let Some(count) = count {
-                            count + 1
-                        } else {
-                            0
-                        };
-
-                        let change_premint_secrets = PreMintSecrets::from_seed(
-                            active_keyset_id,
-                            count,
-                            mnemonic,
-                            change_amount,
-                            false,
-                        )?;
-
-                        (
-                            PreMintSecrets::with_conditions(
-                                active_keyset_id,
-                                desired_amount,
-                                conditions,
-                            )?,
-                            change_premint_secrets,
-                        )
-                    }
-                    None => {
-                        let count = self
-                            .localstore
-                            .get_keyset_counter(&active_keyset_id)
-                            .await?;
+                let premint_secrets = PreMintSecrets::from_xpriv(
+                    active_keyset_id,
+                    count,
+                    self.xpriv,
+                    desired_amount,
+                    false,
+                )?;
 
-                        let count = if let Some(count) = count {
-                            count + 1
-                        } else {
-                            0
-                        };
-
-                        let premint_secrets = PreMintSecrets::from_seed(
-                            active_keyset_id,
-                            count,
-                            mnemonic,
-                            desired_amount,
-                            false,
-                        )?;
-
-                        let count = count + premint_secrets.len() as u64;
-
-                        let change_premint_secrets = PreMintSecrets::from_seed(
-                            active_keyset_id,
-                            count,
-                            mnemonic,
-                            change_amount,
-                            false,
-                        )?;
-
-                        (premint_secrets, change_premint_secrets)
-                    }
-                },
-                None => match spending_conditions {
-                    Some(conditions) => (
-                        PreMintSecrets::with_conditions(
-                            active_keyset_id,
-                            desired_amount,
-                            conditions,
-                        )?,
-                        PreMintSecrets::random(active_keyset_id, change_amount)?,
-                    ),
-                    None => (
-                        PreMintSecrets::random(active_keyset_id, desired_amount)?,
-                        PreMintSecrets::random(active_keyset_id, change_amount)?,
-                    ),
-                },
-            };
-        }
+                let count = count + premint_secrets.len() as u32;
+
+                let change_premint_secrets = PreMintSecrets::from_xpriv(
+                    active_keyset_id,
+                    count,
+                    self.xpriv,
+                    change_amount,
+                    false,
+                )?;
+
+                (premint_secrets, change_premint_secrets)
+            }
+        };
 
         // Combine the BlindedMessages totoalling the desired amount with change
         desired_messages.combine(change_messages);
@@ -720,6 +683,7 @@ impl Wallet {
     }
 
     /// Send
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn send(
         &mut self,
         mint_url: &UncheckedUrl,
@@ -730,14 +694,16 @@ impl Wallet {
     ) -> Result<String, Error> {
         let input_proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
 
-        let send_proofs = match input_proofs
-            .iter()
-            .map(|p| p.amount)
-            .sum::<Amount>()
-            .eq(&amount)
-        {
-            true => Some(input_proofs),
-            false => {
+        let send_proofs = match (
+            input_proofs
+                .iter()
+                .map(|p| p.amount)
+                .sum::<Amount>()
+                .eq(&amount),
+            &conditions,
+        ) {
+            (true, None) => Some(input_proofs),
+            _ => {
                 self.swap(mint_url, unit, Some(amount), input_proofs, conditions)
                     .await?
             }
@@ -754,6 +720,7 @@ impl Wallet {
     }
 
     /// Melt Quote
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn melt_quote(
         &mut self,
         mint_url: UncheckedUrl,
@@ -784,7 +751,35 @@ impl Wallet {
         Ok(quote)
     }
 
+    /// Melt quote status
+    #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))]
+    pub async fn melt_quote_status(
+        &self,
+        mint_url: UncheckedUrl,
+        quote_id: &str,
+    ) -> Result<MeltQuoteBolt11Response, Error> {
+        let response = self
+            .client
+            .get_melt_quote_status(mint_url.try_into()?, quote_id)
+            .await?;
+
+        match self.localstore.get_melt_quote(quote_id).await? {
+            Some(quote) => {
+                let mut quote = quote;
+
+                quote.paid = response.paid;
+                self.localstore.add_melt_quote(quote).await?;
+            }
+            None => {
+                tracing::info!("Quote melt {} unknown", quote_id);
+            }
+        }
+
+        Ok(response)
+    }
+
     // Select proofs
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn select_proofs(
         &self,
         mint_url: UncheckedUrl,
@@ -845,6 +840,7 @@ impl Wallet {
     }
 
     /// Melt
+    #[instrument(skip(self, quote_id), fields(mint_url = %mint_url))]
     pub async fn melt(&mut self, mint_url: &UncheckedUrl, quote_id: &str) -> Result<Melted, Error> {
         let quote_info = self.localstore.get_melt_quote(quote_id).await?;
 
@@ -864,44 +860,21 @@ impl Wallet {
 
         let proofs_amount = proofs.iter().map(|p| p.amount).sum();
 
-        let mut counter: Option<u64> = None;
-
         let active_keyset_id = self.active_mint_keyset(mint_url, &quote_info.unit).await?;
 
-        let premint_secrets;
-
-        #[cfg(not(feature = "nut13"))]
-        {
-            premint_secrets = PreMintSecrets::blank(active_keyset_id, proofs_amount)?;
-        }
+        let count = self
+            .localstore
+            .get_keyset_counter(&active_keyset_id)
+            .await?;
 
-        #[cfg(feature = "nut13")]
-        {
-            premint_secrets = match &self.mnemonic {
-                Some(mnemonic) => {
-                    let count = self
-                        .localstore
-                        .get_keyset_counter(&active_keyset_id)
-                        .await?;
+        let count = if let Some(count) = count {
+            count + 1
+        } else {
+            0
+        };
 
-                    let count = if let Some(count) = count {
-                        count + 1
-                    } else {
-                        0
-                    };
-
-                    counter = Some(count);
-                    PreMintSecrets::from_seed(
-                        active_keyset_id,
-                        count,
-                        mnemonic,
-                        proofs_amount,
-                        true,
-                    )?
-                }
-                None => PreMintSecrets::blank(active_keyset_id, proofs_amount)?,
-            };
-        }
+        let premint_secrets =
+            PreMintSecrets::from_xpriv(active_keyset_id, count, self.xpriv, proofs_amount, true)?;
 
         let melt_response = self
             .client
@@ -939,12 +912,9 @@ impl Wallet {
             );
 
             // Update counter for keyset
-            #[cfg(feature = "nut13")]
-            if counter.is_some() {
-                self.localstore
-                    .increment_keyset_counter(&active_keyset_id, change_proofs.len() as u64)
-                    .await?;
-            }
+            self.localstore
+                .increment_keyset_counter(&active_keyset_id, change_proofs.len() as u32)
+                .await?;
 
             self.localstore
                 .add_proofs(mint_url.clone(), change_proofs)
@@ -961,6 +931,7 @@ impl Wallet {
     }
 
     /// Receive
+    #[instrument(skip_all)]
     pub async fn receive(
         &mut self,
         encoded_token: &str,
@@ -977,6 +948,16 @@ impl Wallet {
                 continue;
             }
 
+            // Add mint if it does not exist in the store
+            if self
+                .localstore
+                .get_mint(token.mint.clone())
+                .await?
+                .is_none()
+            {
+                self.add_mint(token.mint.clone()).await?;
+            }
+
             let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
 
             let keys = self.get_keyset_keys(&token.mint, active_keyset_id).await?;
@@ -1078,12 +1059,9 @@ impl Wallet {
             )?;
             let mint_proofs = received_proofs.entry(token.mint).or_default();
 
-            #[cfg(feature = "nut13")]
-            if self.mnemonic.is_some() {
-                self.localstore
-                    .increment_keyset_counter(&active_keyset_id, p.len() as u64)
-                    .await?;
-            }
+            self.localstore
+                .increment_keyset_counter(&active_keyset_id, p.len() as u32)
+                .await?;
 
             mint_proofs.extend(p);
         }
@@ -1095,6 +1073,7 @@ impl Wallet {
         Ok(())
     }
 
+    #[instrument(skip(self, proofs), fields(mint_url = %mint_url))]
     pub fn proofs_to_token(
         &self,
         mint_url: UncheckedUrl,
@@ -1106,6 +1085,7 @@ impl Wallet {
     }
 
     #[cfg(feature = "nut13")]
+    #[instrument(skip(self), fields(mint_url = %mint_url))]
     pub async fn restore(&mut self, mint_url: UncheckedUrl) -> Result<Amount, Error> {
         // Check that mint is in store of mints
         if self.localstore.get_mint(mint_url.clone()).await?.is_none() {
@@ -1124,7 +1104,7 @@ impl Wallet {
             while empty_batch.lt(&3) {
                 let premint_secrets = PreMintSecrets::restore_batch(
                     keyset.id,
-                    &self.mnemonic.clone().ok_or(Error::MnemonicRequired)?,
+                    self.xpriv,
                     start_counter,
                     start_counter + 100,
                 )?;
@@ -1178,7 +1158,7 @@ impl Wallet {
 
                 #[cfg(feature = "nut13")]
                 self.localstore
-                    .increment_keyset_counter(&keyset.id, proofs.len() as u64)
+                    .increment_keyset_counter(&keyset.id, proofs.len() as u32)
                     .await?;
 
                 let states = self
@@ -1209,6 +1189,7 @@ impl Wallet {
     /// Verify all proofs in token have meet the required spend
     /// Can be used to allow a wallet to accept payments offline while reducing
     /// the risk of claiming back to the limits let by the spending_conditions
+    #[instrument(skip(self, token))]
     pub fn verify_token_p2pk(
         &self,
         token: &Token,
@@ -1326,6 +1307,7 @@ impl Wallet {
     }
 
     /// Verify all proofs in token have a valid DLEQ proof
+    #[instrument(skip(self, token))]
     pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
         let mut keys_cache: HashMap<Id, Keys> = HashMap::new();