From 344b81a69407c4e6f6ec466be7b3badcb39cf321 Mon Sep 17 00:00:00 2001 From: David Caseria Date: Fri, 24 Oct 2025 12:24:06 -0500 Subject: [PATCH] Update FFI Database Objects to Records (#1149) --- crates/cdk-ffi/src/database.rs | 4 +- crates/cdk-ffi/src/multi_mint_wallet.rs | 15 +- crates/cdk-ffi/src/postgres.rs | 2 +- crates/cdk-ffi/src/sqlite.rs | 2 +- crates/cdk-ffi/src/token.rs | 5 +- crates/cdk-ffi/src/types/proof.rs | 167 ++++++++++++------ crates/cdk-ffi/src/types/quote.rs | 125 ++----------- crates/cdk-ffi/src/types/subscription.rs | 12 +- crates/cdk-ffi/src/types/transaction.rs | 19 +- crates/cdk-ffi/src/types/wallet.rs | 11 +- crates/cdk-ffi/src/wallet.rs | 51 +++--- .../tests/ffi_minting_integration.rs | 15 +- 12 files changed, 194 insertions(+), 234 deletions(-) diff --git a/crates/cdk-ffi/src/database.rs b/crates/cdk-ffi/src/database.rs index cdf6eb57..c87b1c55 100644 --- a/crates/cdk-ffi/src/database.rs +++ b/crates/cdk-ffi/src/database.rs @@ -450,7 +450,9 @@ impl CdkWalletDatabase for WalletDatabaseBridge { .into_iter() .map(|info| { Ok(cdk::types::ProofInfo { - proof: info.proof.inner.clone(), + proof: info.proof.try_into().map_err(|e: FfiError| { + cdk::cdk_database::Error::Database(e.to_string().into()) + })?, y: info.y.try_into().map_err(|e: FfiError| { cdk::cdk_database::Error::Database(e.to_string().into()) })?, diff --git a/crates/cdk-ffi/src/multi_mint_wallet.rs b/crates/cdk-ffi/src/multi_mint_wallet.rs index 06bf2974..0ca4eda5 100644 --- a/crates/cdk-ffi/src/multi_mint_wallet.rs +++ b/crates/cdk-ffi/src/multi_mint_wallet.rs @@ -175,10 +175,7 @@ impl MultiMintWallet { let proofs = self.inner.list_proofs().await?; let mut proofs_by_mint = HashMap::new(); for (mint_url, mint_proofs) in proofs { - let ffi_proofs: Vec> = mint_proofs - .into_iter() - .map(|p| Arc::new(p.into())) - .collect(); + let ffi_proofs: Vec = mint_proofs.into_iter().map(|p| p.into()).collect(); proofs_by_mint.insert(mint_url.to_string(), ffi_proofs); } Ok(proofs_by_mint) @@ -262,7 +259,7 @@ impl MultiMintWallet { .inner .mint(&cdk_mint_url, "e_id, conditions) .await?; - Ok(proofs.into_iter().map(|p| Arc::new(p.into())).collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Wait for a mint quote to be paid and automatically mint the proofs @@ -288,7 +285,7 @@ impl MultiMintWallet { timeout_secs, ) .await?; - Ok(proofs.into_iter().map(|p| Arc::new(p.into())).collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Get a melt quote from a specific mint @@ -346,7 +343,7 @@ impl MultiMintWallet { let result = self.inner.swap(amount.map(Into::into), conditions).await?; - Ok(result.map(|proofs| proofs.into_iter().map(|p| Arc::new(p.into())).collect())) + Ok(result.map(|proofs| proofs.into_iter().map(|p| p.into()).collect())) } /// List transactions from all mints @@ -437,7 +434,7 @@ impl MultiMintWallet { .inner .mint_blind_auth(&cdk_mint_url, amount.into()) .await?; - Ok(proofs.into_iter().map(|p| Arc::new(p.into())).collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Get unspent auth proofs for a specific mint @@ -556,4 +553,4 @@ impl From for CdkMultiMintSendOptions { pub type BalanceMap = HashMap; /// Type alias for proofs by mint URL -pub type ProofsByMint = HashMap>>; +pub type ProofsByMint = HashMap>; diff --git a/crates/cdk-ffi/src/postgres.rs b/crates/cdk-ffi/src/postgres.rs index 4c46a69b..975bd011 100644 --- a/crates/cdk-ffi/src/postgres.rs +++ b/crates/cdk-ffi/src/postgres.rs @@ -237,7 +237,7 @@ impl WalletDatabase for WalletPostgresDatabase { .into_iter() .map(|info| { Ok::(cdk::types::ProofInfo { - proof: info.proof.inner.clone(), + proof: info.proof.try_into()?, y: info.y.try_into()?, mint_url: info.mint_url.try_into()?, state: info.state.into(), diff --git a/crates/cdk-ffi/src/sqlite.rs b/crates/cdk-ffi/src/sqlite.rs index b48bb36a..945d0df3 100644 --- a/crates/cdk-ffi/src/sqlite.rs +++ b/crates/cdk-ffi/src/sqlite.rs @@ -272,7 +272,7 @@ impl WalletDatabase for WalletSqliteDatabase { .into_iter() .map(|info| { Ok::(cdk::types::ProofInfo { - proof: info.proof.inner.clone(), + proof: info.proof.try_into()?, y: info.y.try_into()?, mint_url: info.mint_url.try_into()?, state: info.state.into(), diff --git a/crates/cdk-ffi/src/token.rs b/crates/cdk-ffi/src/token.rs index 31a22e42..cce6ac7b 100644 --- a/crates/cdk-ffi/src/token.rs +++ b/crates/cdk-ffi/src/token.rs @@ -75,10 +75,7 @@ impl Token { // For now, return empty keysets to get all proofs let empty_keysets = vec![]; let proofs = self.inner.proofs(&empty_keysets)?; - Ok(proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Convert token to raw bytes diff --git a/crates/cdk-ffi/src/types/proof.rs b/crates/cdk-ffi/src/types/proof.rs index e75e6c0e..b5db3f97 100644 --- a/crates/cdk-ffi/src/types/proof.rs +++ b/crates/cdk-ffi/src/types/proof.rs @@ -44,78 +44,126 @@ impl From for CdkState { } /// FFI-compatible Proof -#[derive(Debug, uniffi::Object)] +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] pub struct Proof { - pub(crate) inner: cdk::nuts::Proof, + /// Proof amount + pub amount: Amount, + /// Secret (as string) + pub secret: String, + /// Unblinded signature C (as hex string) + pub c: String, + /// Keyset ID (as hex string) + pub keyset_id: String, + /// Optional witness + pub witness: Option, + /// Optional DLEQ proof + pub dleq: Option, } impl From for Proof { fn from(proof: cdk::nuts::Proof) -> Self { - Self { inner: proof } + Self { + amount: proof.amount.into(), + secret: proof.secret.to_string(), + c: proof.c.to_string(), + keyset_id: proof.keyset_id.to_string(), + witness: proof.witness.map(|w| w.into()), + dleq: proof.dleq.map(|d| d.into()), + } } } -impl From for cdk::nuts::Proof { - fn from(proof: Proof) -> Self { - proof.inner - } -} +impl TryFrom for cdk::nuts::Proof { + type Error = FfiError; -#[uniffi::export] -impl Proof { - /// Get the amount - pub fn amount(&self) -> Amount { - self.inner.amount.into() - } + fn try_from(proof: Proof) -> Result { + use std::str::FromStr; - /// Get the secret as string - pub fn secret(&self) -> String { - self.inner.secret.to_string() - } - - /// Get the unblinded signature (C) as string - pub fn c(&self) -> String { - self.inner.c.to_string() - } - - /// Get the keyset ID as string - pub fn keyset_id(&self) -> String { - self.inner.keyset_id.to_string() - } - - /// Get the witness - pub fn witness(&self) -> Option { - self.inner.witness.as_ref().map(|w| w.clone().into()) - } - - /// Check if proof is active with given keyset IDs - pub fn is_active(&self, active_keyset_ids: Vec) -> bool { use cdk::nuts::Id; - let ids: Vec = active_keyset_ids - .into_iter() - .filter_map(|id| Id::from_str(&id).ok()) - .collect(); - self.inner.is_active(&ids) - } - /// Get the Y value (hash_to_curve of secret) - pub fn y(&self) -> Result { - Ok(self.inner.y()?.to_string()) + Ok(Self { + amount: proof.amount.into(), + secret: cdk::secret::Secret::from_str(&proof.secret) + .map_err(|e| FfiError::Serialization { msg: e.to_string() })?, + c: cdk::nuts::PublicKey::from_str(&proof.c) + .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?, + keyset_id: Id::from_str(&proof.keyset_id) + .map_err(|e| FfiError::Serialization { msg: e.to_string() })?, + witness: proof.witness.map(|w| w.into()), + dleq: proof.dleq.map(|d| d.into()), + }) } +} - /// Get the DLEQ proof if present - pub fn dleq(&self) -> Option { - self.inner.dleq.as_ref().map(|d| d.clone().into()) - } +/// Get the Y value (hash_to_curve of secret) for a proof +#[uniffi::export] +pub fn proof_y(proof: &Proof) -> Result { + // Convert to CDK proof to calculate Y + let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?; + Ok(cdk_proof.y()?.to_string()) +} - /// Check if proof has DLEQ proof - pub fn has_dleq(&self) -> bool { - self.inner.dleq.is_some() +/// Check if proof is active with given keyset IDs +#[uniffi::export] +pub fn proof_is_active(proof: &Proof, active_keyset_ids: Vec) -> bool { + use cdk::nuts::Id; + let ids: Vec = active_keyset_ids + .into_iter() + .filter_map(|id| Id::from_str(&id).ok()) + .collect(); + + // A proof is active if its keyset_id is in the active list + if let Ok(keyset_id) = Id::from_str(&proof.keyset_id) { + ids.contains(&keyset_id) + } else { + false } } +/// Check if proof has DLEQ proof +#[uniffi::export] +pub fn proof_has_dleq(proof: &Proof) -> bool { + proof.dleq.is_some() +} + +/// Verify HTLC witness on a proof +#[uniffi::export] +pub fn proof_verify_htlc(proof: &Proof) -> Result<(), FfiError> { + let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?; + cdk_proof + .verify_htlc() + .map_err(|e| FfiError::Generic { msg: e.to_string() }) +} + +/// Verify DLEQ proof on a proof +#[uniffi::export] +pub fn proof_verify_dleq( + proof: &Proof, + mint_pubkey: super::keys::PublicKey, +) -> Result<(), FfiError> { + let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?; + let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?; + cdk_proof + .verify_dleq(cdk_pubkey) + .map_err(|e| FfiError::Generic { msg: e.to_string() }) +} + +/// Sign a P2PK proof with a secret key, returning a new signed proof +#[uniffi::export] +pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result { + let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?; + let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex) + .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?; + + cdk_proof + .sign_p2pk(secret_key) + .map_err(|e| FfiError::Generic { msg: e.to_string() })?; + + Ok(cdk_proof.into()) +} + /// FFI-compatible Proofs (vector of Proof) -pub type Proofs = Vec>; +pub type Proofs = Vec; /// FFI-compatible DLEQ proof for proofs #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] @@ -175,9 +223,12 @@ impl From for cdk::nuts::BlindSignatureDleq { } } -/// Helper functions for Proofs +/// Helper function to calculate total amount of proofs +#[uniffi::export] pub fn proofs_total_amount(proofs: &Proofs) -> Result { - let cdk_proofs: Vec = proofs.iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + proofs.iter().map(|p| p.clone().try_into()).collect(); + let cdk_proofs = cdk_proofs?; use cdk::nuts::ProofsMethods; Ok(cdk_proofs.total_amount()?.into()) } @@ -420,7 +471,7 @@ impl TryFrom for cdk::nuts::SpendingConditions { #[derive(Debug, Clone, uniffi::Record)] pub struct ProofInfo { /// Proof - pub proof: std::sync::Arc, + pub proof: Proof, /// Y value (hash_to_curve of secret) pub y: super::keys::PublicKey, /// Mint URL @@ -436,7 +487,7 @@ pub struct ProofInfo { impl From for ProofInfo { fn from(info: cdk::types::ProofInfo) -> Self { Self { - proof: std::sync::Arc::new(info.proof.into()), + proof: info.proof.into(), y: info.y.into(), mint_url: info.mint_url.into(), state: info.state.into(), @@ -458,7 +509,7 @@ pub fn decode_proof_info(json: String) -> Result { pub fn encode_proof_info(info: ProofInfo) -> Result { // Convert to cdk::types::ProofInfo for serialization let cdk_info = cdk::types::ProofInfo { - proof: info.proof.inner.clone(), + proof: info.proof.try_into()?, y: info.y.try_into()?, mint_url: info.mint_url.try_into()?, state: info.state.into(), diff --git a/crates/cdk-ffi/src/types/quote.rs b/crates/cdk-ffi/src/types/quote.rs index e931008a..7ee46709 100644 --- a/crates/cdk-ffi/src/types/quote.rs +++ b/crates/cdk-ffi/src/types/quote.rs @@ -77,30 +77,25 @@ impl TryFrom for cdk::wallet::MintQuote { } } -impl MintQuote { - /// Get total amount (amount + fees) - pub fn total_amount(&self) -> Amount { - if let Some(amount) = self.amount { - Amount::new(amount.value + self.amount_paid.value - self.amount_issued.value) - } else { - Amount::zero() - } - } +/// Get total amount for a mint quote (amount paid) +#[uniffi::export] +pub fn mint_quote_total_amount(quote: &MintQuote) -> Result { + let cdk_quote: cdk::wallet::MintQuote = quote.clone().try_into()?; + Ok(cdk_quote.total_amount().into()) +} - /// Check if quote is expired - pub fn is_expired(&self, current_time: u64) -> bool { - current_time > self.expiry - } +/// Check if mint quote is expired +#[uniffi::export] +pub fn mint_quote_is_expired(quote: &MintQuote, current_time: u64) -> Result { + let cdk_quote: cdk::wallet::MintQuote = quote.clone().try_into()?; + Ok(cdk_quote.is_expired(current_time)) +} - /// Get amount that can be minted - pub fn amount_mintable(&self) -> Amount { - Amount::new(self.amount_paid.value - self.amount_issued.value) - } - - /// Convert MintQuote to JSON string - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } +/// Get amount that can be minted from a mint quote +#[uniffi::export] +pub fn mint_quote_amount_mintable(quote: &MintQuote) -> Result { + let cdk_quote: cdk::wallet::MintQuote = quote.clone().try_into()?; + Ok(cdk_quote.amount_mintable().into()) } /// Decode MintQuote from JSON string @@ -117,7 +112,7 @@ pub fn encode_mint_quote(quote: MintQuote) -> Result { } /// FFI-compatible MintQuoteBolt11Response -#[derive(Debug, uniffi::Object)] +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] pub struct MintQuoteBolt11Response { /// Quote ID pub quote: String, @@ -149,46 +144,8 @@ impl From> for MintQuoteBolt11Respons } } -#[uniffi::export] -impl MintQuoteBolt11Response { - /// Get quote ID - pub fn quote(&self) -> String { - self.quote.clone() - } - - /// Get request string - pub fn request(&self) -> String { - self.request.clone() - } - - /// Get state - pub fn state(&self) -> QuoteState { - self.state.clone() - } - - /// Get expiry - pub fn expiry(&self) -> Option { - self.expiry - } - - /// Get amount - pub fn amount(&self) -> Option { - self.amount - } - - /// Get unit - pub fn unit(&self) -> Option { - self.unit.clone() - } - - /// Get pubkey - pub fn pubkey(&self) -> Option { - self.pubkey.clone() - } -} - /// FFI-compatible MeltQuoteBolt11Response -#[derive(Debug, uniffi::Object)] +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] pub struct MeltQuoteBolt11Response { /// Quote ID pub quote: String, @@ -222,50 +179,6 @@ impl From> for MeltQuoteBolt11Respons } } } - -#[uniffi::export] -impl MeltQuoteBolt11Response { - /// Get quote ID - pub fn quote(&self) -> String { - self.quote.clone() - } - - /// Get amount - pub fn amount(&self) -> Amount { - self.amount - } - - /// Get fee reserve - pub fn fee_reserve(&self) -> Amount { - self.fee_reserve - } - - /// Get state - pub fn state(&self) -> QuoteState { - self.state.clone() - } - - /// Get expiry - pub fn expiry(&self) -> u64 { - self.expiry - } - - /// Get payment preimage - pub fn payment_preimage(&self) -> Option { - self.payment_preimage.clone() - } - - /// Get request - pub fn request(&self) -> Option { - self.request.clone() - } - - /// Get unit - pub fn unit(&self) -> Option { - self.unit.clone() - } -} - /// FFI-compatible PaymentMethod #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] pub enum PaymentMethod { diff --git a/crates/cdk-ffi/src/types/subscription.rs b/crates/cdk-ffi/src/types/subscription.rs index ccd036bf..b4a0aed9 100644 --- a/crates/cdk-ffi/src/types/subscription.rs +++ b/crates/cdk-ffi/src/types/subscription.rs @@ -139,13 +139,9 @@ pub enum NotificationPayload { /// Proof state update ProofState { proof_states: Vec }, /// Mint quote update - MintQuoteUpdate { - quote: std::sync::Arc, - }, + MintQuoteUpdate { quote: MintQuoteBolt11Response }, /// Melt quote update - MeltQuoteUpdate { - quote: std::sync::Arc, - }, + MeltQuoteUpdate { quote: MeltQuoteBolt11Response }, } impl From> for NotificationPayload { @@ -156,12 +152,12 @@ impl From> for NotificationPayload { }, cdk::nuts::NotificationPayload::MintQuoteBolt11Response(quote_resp) => { NotificationPayload::MintQuoteUpdate { - quote: std::sync::Arc::new(quote_resp.into()), + quote: quote_resp.into(), } } cdk::nuts::NotificationPayload::MeltQuoteBolt11Response(quote_resp) => { NotificationPayload::MeltQuoteUpdate { - quote: std::sync::Arc::new(quote_resp.into()), + quote: quote_resp.into(), } } _ => { diff --git a/crates/cdk-ffi/src/types/transaction.rs b/crates/cdk-ffi/src/types/transaction.rs index 9c70c35d..39c0aa21 100644 --- a/crates/cdk-ffi/src/types/transaction.rs +++ b/crates/cdk-ffi/src/types/transaction.rs @@ -106,6 +106,21 @@ pub fn encode_transaction(transaction: Transaction) -> Result Ok(serde_json::to_string(&transaction)?) } +/// Check if a transaction matches the given filter conditions +#[uniffi::export] +pub fn transaction_matches_conditions( + transaction: &Transaction, + mint_url: Option, + direction: Option, + unit: Option, +) -> Result { + let cdk_transaction: cdk::wallet::types::Transaction = transaction.clone().try_into()?; + let cdk_mint_url = mint_url.map(|url| url.try_into()).transpose()?; + let cdk_direction = direction.map(Into::into); + let cdk_unit = unit.map(Into::into); + Ok(cdk_transaction.matches_conditions(&cdk_mint_url, &cdk_direction, &cdk_unit)) +} + /// FFI-compatible TransactionDirection #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] pub enum TransactionDirection { @@ -163,7 +178,9 @@ impl TransactionId { /// Create from proofs pub fn from_proofs(proofs: &Proofs) -> Result { - let cdk_proofs: Vec = proofs.iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + proofs.iter().map(|p| p.clone().try_into()).collect(); + let cdk_proofs = cdk_proofs?; let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?; Ok(Self { hex: id.to_string(), diff --git a/crates/cdk-ffi/src/types/wallet.rs b/crates/cdk-ffi/src/types/wallet.rs index 336ca7f5..d9b6928a 100644 --- a/crates/cdk-ffi/src/types/wallet.rs +++ b/crates/cdk-ffi/src/types/wallet.rs @@ -314,7 +314,7 @@ impl From for PreparedSend { .proofs() .iter() .cloned() - .map(|p| std::sync::Arc::new(p.into())) + .map(|p| p.into()) .collect(); Self { inner: Mutex::new(Some(prepared)), @@ -421,12 +421,9 @@ impl From for Melted { Self { state: melted.state.into(), preimage: melted.preimage, - change: melted.change.map(|proofs| { - proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect() - }), + change: melted + .change + .map(|proofs| proofs.into_iter().map(|p| p.into()).collect()), amount: melted.amount.into(), fee_paid: melted.fee_paid.into(), } diff --git a/crates/cdk-ffi/src/wallet.rs b/crates/cdk-ffi/src/wallet.rs index c144450f..449aa950 100644 --- a/crates/cdk-ffi/src/wallet.rs +++ b/crates/cdk-ffi/src/wallet.rs @@ -119,8 +119,9 @@ impl Wallet { options: ReceiveOptions, memo: Option, ) -> Result { - let cdk_proofs: Vec = - proofs.into_iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + proofs.into_iter().map(|p| p.try_into()).collect(); + let cdk_proofs = cdk_proofs?; let amount = self .inner @@ -166,10 +167,7 @@ impl Wallet { .inner .mint("e_id, amount_split_target.into(), conditions) .await?; - Ok(proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Get a melt quote @@ -222,10 +220,7 @@ impl Wallet { ) .await?; - Ok(proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Get a quote for a bolt12 melt @@ -248,8 +243,9 @@ impl Wallet { spending_conditions: Option, include_fees: bool, ) -> Result, FfiError> { - let cdk_proofs: Vec = - input_proofs.into_iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + input_proofs.into_iter().map(|p| p.try_into()).collect(); + let cdk_proofs = cdk_proofs?; // Convert spending conditions if provided let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?; @@ -265,12 +261,7 @@ impl Wallet { ) .await?; - Ok(result.map(|proofs| { - proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect() - })) + Ok(result.map(|proofs| proofs.into_iter().map(|p| p.into()).collect())) } /// Get proofs by states @@ -291,7 +282,7 @@ impl Wallet { }; for proof in proofs { - all_proofs.push(std::sync::Arc::new(proof.into())); + all_proofs.push(proof.into()); } } @@ -300,8 +291,9 @@ impl Wallet { /// Check if proofs are spent pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result, FfiError> { - let cdk_proofs: Vec = - proofs.into_iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + proofs.into_iter().map(|p| p.try_into()).collect(); + let cdk_proofs = cdk_proofs?; let proof_states = self.inner.check_proofs_spent(cdk_proofs).await?; // Convert ProofState to bool (spent = true, unspent = false) @@ -382,7 +374,9 @@ impl Wallet { /// Reclaim unspent proofs (mark them as unspent in the database) pub async fn reclaim_unspent(&self, proofs: Proofs) -> Result<(), FfiError> { - let cdk_proofs: Vec = proofs.iter().map(|p| p.inner.clone()).collect(); + let cdk_proofs: Result, _> = + proofs.iter().map(|p| p.clone().try_into()).collect(); + let cdk_proofs = cdk_proofs?; self.inner.reclaim_unspent(cdk_proofs).await?; Ok(()) } @@ -401,9 +395,11 @@ impl Wallet { ) -> Result { let id = cdk::nuts::Id::from_str(&keyset_id) .map_err(|e| FfiError::Generic { msg: e.to_string() })?; - let fee_and_amounts = self.inner.get_keyset_fees_and_amounts_by_id(id).await?; - let total_fee = (proof_count as u64 * fee_and_amounts.fee()) / 1000; // fee is per thousand - Ok(Amount::new(total_fee)) + let fee = self + .inner + .get_keyset_count_fee(&id, proof_count as u64) + .await?; + Ok(fee.into()) } } @@ -453,10 +449,7 @@ impl Wallet { /// Mint blind auth tokens pub async fn mint_blind_auth(&self, amount: Amount) -> Result { let proofs = self.inner.mint_blind_auth(amount.into()).await?; - Ok(proofs - .into_iter() - .map(|p| std::sync::Arc::new(p.into())) - .collect()) + Ok(proofs.into_iter().map(|p| p.into()).collect()) } /// Get unspent auth proofs diff --git a/crates/cdk-integration-tests/tests/ffi_minting_integration.rs b/crates/cdk-integration-tests/tests/ffi_minting_integration.rs index 71ade057..81f4ee0b 100644 --- a/crates/cdk-integration-tests/tests/ffi_minting_integration.rs +++ b/crates/cdk-integration-tests/tests/ffi_minting_integration.rs @@ -18,7 +18,7 @@ use std::time::Duration; use bip39::Mnemonic; use cdk_ffi::sqlite::WalletSqliteDatabase; -use cdk_ffi::types::{Amount, CurrencyUnit, QuoteState, SplitTarget}; +use cdk_ffi::types::{encode_mint_quote, Amount, CurrencyUnit, QuoteState, SplitTarget}; use cdk_ffi::wallet::Wallet as FfiWallet; use cdk_ffi::WalletConfig; use cdk_integration_tests::{get_mint_url_from_env, pay_if_regtest}; @@ -157,7 +157,7 @@ async fn test_ffi_full_minting_flow() { ); // Calculate total amount of minted proofs - let total_minted: u64 = mint_result.iter().map(|proof| proof.amount().value).sum(); + let total_minted: u64 = mint_result.iter().map(|proof| proof.amount.value).sum(); assert_eq!( total_minted, mint_amount.value, "Total minted amount should equal requested amount" @@ -166,14 +166,11 @@ async fn test_ffi_full_minting_flow() { // Verify each proof has valid properties for proof in &mint_result { assert!( - proof.amount().value > 0, + proof.amount.value > 0, "Each proof should have positive amount" ); - assert!( - !proof.secret().is_empty(), - "Each proof should have a secret" - ); - assert!(!proof.c().is_empty(), "Each proof should have a C value"); + assert!(!proof.secret.is_empty(), "Each proof should have a secret"); + assert!(!proof.c.is_empty(), "Each proof should have a C value"); } // Step 4: Verify wallet balance after minting @@ -235,7 +232,7 @@ async fn test_ffi_mint_quote_creation() { ); // Test quote JSON serialization (useful for bindings that need JSON) - let quote_json = quote.to_json().expect("Quote should serialize to JSON"); + let quote_json = encode_mint_quote(quote.clone()).expect("Quote should serialize to JSON"); assert!(!quote_json.is_empty(), "Quote JSON should not be empty"); println!(