diff --git a/bindings/cdk-js/src/wallet.rs b/bindings/cdk-js/src/wallet.rs index f10e4a8c..097e899b 100644 --- a/bindings/cdk-js/src/wallet.rs +++ b/bindings/cdk-js/src/wallet.rs @@ -51,6 +51,26 @@ 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) -> Result { + Ok(self + .inner + .check_all_pending_proofs + .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)?; diff --git a/crates/cdk/src/nuts/nut00.rs b/crates/cdk/src/nuts/nut00.rs index 8a4bf719..acd602e1 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 5357ec55..dcc4ab13 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,44 @@ impl Wallet { Ok(spendable.states) } + /// Checks pending proofs for spent status + #[instrument(skip(self))] + pub async fn check_all_pending_proofs(&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 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(