Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: check pending proofs #139

Merged
merged 2 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/cdk-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
39 changes: 32 additions & 7 deletions bindings/cdk-js/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down Expand Up @@ -40,17 +40,41 @@ impl From<Wallet> for JsWallet {
impl JsWallet {
#[wasm_bindgen(constructor)]
pub async fn new(seed: Vec<u8>) -> 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)]
pub async fn total_balance(&self) -> Result<JsAmount> {
Ok(self.inner.total_balance().await.map_err(into_err)?.into())
}

#[wasm_bindgen(js_name = totalPendingBalance)]
pub async fn total_pending_balance(&self) -> Result<JsAmount> {
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<String>) -> Result<JsAmount> {
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<JsValue> {
let mint_balances = self.inner.mint_balances().await.map_err(into_err)?;
Expand Down Expand Up @@ -180,14 +204,15 @@ impl JsWallet {
signing_keys: JsValue,
preimages: JsValue,
) -> Result<JsAmount> {
let signing_keys: Option<Vec<SigningKey>> = serde_wasm_bindgen::from_value(signing_keys)?;
let signing_keys: Option<Vec<SecretKey>> = serde_wasm_bindgen::from_value(signing_keys)?;
let preimages: Option<Vec<String>> = 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)]
Expand Down
6 changes: 5 additions & 1 deletion crates/cdk/src/nuts/nut00.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -200,6 +200,10 @@ impl Proof {
dleq: None,
}
}

pub fn y(&self) -> Result<PublicKey, Error> {
Ok(hash_to_curve(self.secret.as_bytes())?)
}
}

impl Hash for Proof {
Expand Down
62 changes: 62 additions & 0 deletions crates/cdk/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ impl Wallet {
Ok(balance)
}

/// Total Balance of wallet
#[instrument(skip(self))]
pub async fn total_pending_balance(&self) -> Result<Amount, Error> {
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<HashMap<UncheckedUrl, Amount>, Error> {
let mints = self.localstore.get_mints().await?;
Expand Down Expand Up @@ -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<UncheckedUrl>,
) -> Result<Amount, Error> {
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<PublicKey> = 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(
Expand Down
Loading