mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-21 21:15:50 +01:00
Refactor wallet to accept seed for internal Xpriv
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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, "e_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, "e_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)?;
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, "e_info.unit).await?;
|
||||
|
||||
let mut counter: Option<u64> = None;
|
||||
let count = self
|
||||
.localstore
|
||||
.get_keyset_counter(&active_keyset_id)
|
||||
.await?;
|
||||
|
||||
let premint_secrets;
|
||||
let count = if let Some(count) = count {
|
||||
count + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "nut13"))]
|
||||
{
|
||||
premint_secrets = PreMintSecrets::random(active_keyset_id, quote_info.amount)?;
|
||||
}
|
||||
|
||||
#[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
|
||||
};
|
||||
|
||||
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("e_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 premint_secrets = PreMintSecrets::from_xpriv(
|
||||
active_keyset_id,
|
||||
count,
|
||||
self.xpriv,
|
||||
desired_amount,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let change_premint_secrets = PreMintSecrets::from_seed(
|
||||
active_keyset_id,
|
||||
count,
|
||||
mnemonic,
|
||||
change_amount,
|
||||
false,
|
||||
)?;
|
||||
let count = count + premint_secrets.len() as u32;
|
||||
|
||||
(
|
||||
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 change_premint_secrets = PreMintSecrets::from_xpriv(
|
||||
active_keyset_id,
|
||||
count,
|
||||
self.xpriv,
|
||||
change_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)?,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
(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, "e_info.unit).await?;
|
||||
|
||||
let premint_secrets;
|
||||
let count = self
|
||||
.localstore
|
||||
.get_keyset_counter(&active_keyset_id)
|
||||
.await?;
|
||||
|
||||
#[cfg(not(feature = "nut13"))]
|
||||
{
|
||||
premint_secrets = PreMintSecrets::blank(active_keyset_id, proofs_amount)?;
|
||||
}
|
||||
let count = if let Some(count) = count {
|
||||
count + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
#[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
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user