diff --git a/bindings/cdk-js/src/lib.rs b/bindings/cdk-js/src/lib.rs index beb70f8c7..60c66d60a 100644 --- a/bindings/cdk-js/src/lib.rs +++ b/bindings/cdk-js/src/lib.rs @@ -3,7 +3,7 @@ use wasm_bindgen::prelude::*; pub mod error; pub mod nuts; pub mod types; -#[cfg(all(feature = "wallet", target_arch = "wasm32"))] +#[cfg(target_arch = "wasm32")] pub mod wallet; #[wasm_bindgen(start)] diff --git a/bindings/cdk-js/src/wallet.rs b/bindings/cdk-js/src/wallet.rs index f10e4a8c8..72b6868b3 100644 --- a/bindings/cdk-js/src/wallet.rs +++ b/bindings/cdk-js/src/wallet.rs @@ -2,10 +2,10 @@ use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; -use cdk::nuts::{Proofs, SigningKey}; +use cdk::nuts::{Proofs, SecretKey}; use cdk::url::UncheckedUrl; use cdk::wallet::Wallet; -use cdk::{Amount, HttpClient}; +use cdk::Amount; use cdk_rexie::RexieWalletDatabase; use wasm_bindgen::prelude::*; @@ -40,10 +40,9 @@ impl From for JsWallet { impl JsWallet { #[wasm_bindgen(constructor)] pub async fn new(seed: Vec) -> Self { - let client = HttpClient::new(); let db = RexieWalletDatabase::new().await.unwrap(); - Wallet::new(client, Arc::new(db), &seed).await.into() + Wallet::new(Arc::new(db), &seed).into() } #[wasm_bindgen(js_name = totalBalance)] @@ -51,6 +50,31 @@ impl JsWallet { Ok(self.inner.total_balance().await.map_err(into_err)?.into()) } + #[wasm_bindgen(js_name = totalPendingBalance)] + pub async fn total_pending_balance(&self) -> Result { + Ok(self + .inner + .total_pending_balance() + .await + .map_err(into_err)? + .into()) + } + + #[wasm_bindgen(js_name = checkAllPendingProofs)] + pub async fn check_all_pending_proofs(&self, mint_url: Option) -> Result { + let mint_url = match mint_url { + Some(url) => Some(UncheckedUrl::from_str(&url).map_err(into_err)?), + None => None, + }; + + Ok(self + .inner + .check_all_pending_proofs(mint_url) + .await + .map_err(into_err)? + .into()) + } + #[wasm_bindgen(js_name = mintBalances)] pub async fn mint_balances(&self) -> Result { let mint_balances = self.inner.mint_balances().await.map_err(into_err)?; @@ -180,14 +204,15 @@ impl JsWallet { signing_keys: JsValue, preimages: JsValue, ) -> Result { - let signing_keys: Option> = serde_wasm_bindgen::from_value(signing_keys)?; + let signing_keys: Option> = serde_wasm_bindgen::from_value(signing_keys)?; let preimages: Option> = serde_wasm_bindgen::from_value(preimages)?; - self.inner + Ok(self + .inner .receive(&encoded_token, signing_keys, preimages) .await .map_err(into_err)? - .into() + .into()) } #[wasm_bindgen(js_name = send)] diff --git a/crates/cdk/src/nuts/nut00.rs b/crates/cdk/src/nuts/nut00.rs index 8a4bf7199..acd602e14 100644 --- a/crates/cdk/src/nuts/nut00.rs +++ b/crates/cdk/src/nuts/nut00.rs @@ -16,7 +16,7 @@ use url::Url; use super::nut10; use super::nut11::SpendingConditions; -use crate::dhke::blind_message; +use crate::dhke::{blind_message, hash_to_curve}; use crate::nuts::nut01::{PublicKey, SecretKey}; use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness}; use crate::nuts::nut12::BlindSignatureDleq; @@ -200,6 +200,10 @@ impl Proof { dleq: None, } } + + pub fn y(&self) -> Result { + Ok(hash_to_curve(self.secret.as_bytes())?) + } } impl Hash for Proof { diff --git a/crates/cdk/src/wallet.rs b/crates/cdk/src/wallet.rs index 5357ec55c..6ea05ae0e 100644 --- a/crates/cdk/src/wallet.rs +++ b/crates/cdk/src/wallet.rs @@ -129,6 +129,23 @@ impl Wallet { Ok(balance) } + /// Total Balance of wallet + #[instrument(skip(self))] + pub async fn total_pending_balance(&self) -> Result { + let mints = self.localstore.get_mints().await?; + let mut balance = Amount::ZERO; + + for (mint, _) in mints { + if let Some(proofs) = self.localstore.get_pending_proofs(mint.clone()).await? { + let amount = proofs.iter().map(|p| p.amount).sum(); + + balance += amount; + } + } + + Ok(balance) + } + #[instrument(skip(self))] pub async fn mint_balances(&self) -> Result, Error> { let mints = self.localstore.get_mints().await?; @@ -292,6 +309,51 @@ impl Wallet { Ok(spendable.states) } + /// Checks pending proofs for spent status + #[instrument(skip(self))] + pub async fn check_all_pending_proofs( + &self, + mint_url: Option, + ) -> Result { + let mints = match mint_url { + Some(mint_url) => HashMap::from_iter(vec![(mint_url, None)]), + None => self.localstore.get_mints().await?, + }; + + let mut balance = Amount::ZERO; + + for (mint, _) in mints { + if let Some(proofs) = self.localstore.get_pending_proofs(mint.clone()).await? { + let states = self + .check_proofs_spent(mint.clone(), proofs.clone()) + .await?; + + // Both `State::Pending` and `State::Unspent` should be included in the pending table. + // This is because a proof that has been crated to send will be stored in the pending table + // in order to avoid accidentally double spending but to allow it to be explicitly reclaimed + let pending_states: HashSet = states + .into_iter() + .filter(|s| s.state.ne(&State::Spent)) + .map(|s| s.y) + .collect(); + + let (pending_proofs, non_pending_proofs): (Proofs, Proofs) = proofs + .into_iter() + .partition(|p| p.y().map(|y| pending_states.contains(&y)).unwrap_or(false)); + + let amount = pending_proofs.iter().map(|p| p.amount).sum(); + + self.localstore + .remove_pending_proofs(mint, &non_pending_proofs) + .await?; + + balance += amount; + } + } + + Ok(balance) + } + /// Mint Quote #[instrument(skip(self), fields(mint_url = %mint_url))] pub async fn mint_quote(