Pārlūkot izejas kodu

Update FFI Database Objects to Records (#1149)

David Caseria 1 nedēļu atpakaļ
vecāks
revīzija
344b81a694

+ 3 - 1
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())
                     })?,

+ 6 - 9
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<Arc<Proof>> = mint_proofs
-                .into_iter()
-                .map(|p| Arc::new(p.into()))
-                .collect();
+            let ffi_proofs: Vec<Proof> = 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, &quote_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<MultiMintSendOptions> for CdkMultiMintSendOptions {
 pub type BalanceMap = HashMap<String, Amount>;
 
 /// Type alias for proofs by mint URL
-pub type ProofsByMint = HashMap<String, Vec<Arc<Proof>>>;
+pub type ProofsByMint = HashMap<String, Vec<Proof>>;

+ 1 - 1
crates/cdk-ffi/src/postgres.rs

@@ -237,7 +237,7 @@ impl WalletDatabase for WalletPostgresDatabase {
             .into_iter()
             .map(|info| {
                 Ok::<cdk::types::ProofInfo, FfiError>(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(),

+ 1 - 1
crates/cdk-ffi/src/sqlite.rs

@@ -272,7 +272,7 @@ impl WalletDatabase for WalletSqliteDatabase {
             .into_iter()
             .map(|info| {
                 Ok::<cdk::types::ProofInfo, FfiError>(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(),

+ 1 - 4
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

+ 106 - 55
crates/cdk-ffi/src/types/proof.rs

@@ -44,78 +44,126 @@ impl From<ProofState> 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<Witness>,
+    /// Optional DLEQ proof
+    pub dleq: Option<ProofDleq>,
 }
 
 impl From<cdk::nuts::Proof> 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<Proof> for cdk::nuts::Proof {
-    fn from(proof: Proof) -> Self {
-        proof.inner
-    }
-}
+impl TryFrom<Proof> 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<Self, Self::Error> {
+        use std::str::FromStr;
 
-    /// Get the secret as string
-    pub fn secret(&self) -> String {
-        self.inner.secret.to_string()
-    }
+        use cdk::nuts::Id;
 
-    /// Get the unblinded signature (C) as string
-    pub fn c(&self) -> String {
-        self.inner.c.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 keyset ID as string
-    pub fn keyset_id(&self) -> String {
-        self.inner.keyset_id.to_string()
-    }
+/// Get the Y value (hash_to_curve of secret) for a proof
+#[uniffi::export]
+pub fn proof_y(proof: &Proof) -> Result<String, FfiError> {
+    // Convert to CDK proof to calculate Y
+    let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
+    Ok(cdk_proof.y()?.to_string())
+}
 
-    /// Get the witness
-    pub fn witness(&self) -> Option<Witness> {
-        self.inner.witness.as_ref().map(|w| w.clone().into())
+/// Check if proof is active with given keyset IDs
+#[uniffi::export]
+pub fn proof_is_active(proof: &Proof, active_keyset_ids: Vec<String>) -> bool {
+    use cdk::nuts::Id;
+    let ids: Vec<Id> = 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 is active with given keyset IDs
-    pub fn is_active(&self, active_keyset_ids: Vec<String>) -> bool {
-        use cdk::nuts::Id;
-        let ids: Vec<Id> = active_keyset_ids
-            .into_iter()
-            .filter_map(|id| Id::from_str(&id).ok())
-            .collect();
-        self.inner.is_active(&ids)
-    }
+/// Check if proof has DLEQ proof
+#[uniffi::export]
+pub fn proof_has_dleq(proof: &Proof) -> bool {
+    proof.dleq.is_some()
+}
 
-    /// Get the Y value (hash_to_curve of secret)
-    pub fn y(&self) -> Result<String, FfiError> {
-        Ok(self.inner.y()?.to_string())
-    }
+/// 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() })
+}
 
-    /// Get the DLEQ proof if present
-    pub fn dleq(&self) -> Option<ProofDleq> {
-        self.inner.dleq.as_ref().map(|d| d.clone().into())
-    }
+/// 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<Proof, FfiError> {
+    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() })?;
 
-    /// Check if proof has DLEQ proof
-    pub fn has_dleq(&self) -> bool {
-        self.inner.dleq.is_some()
-    }
+    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<std::sync::Arc<Proof>>;
+pub type Proofs = Vec<Proof>;
 
 /// FFI-compatible DLEQ proof for proofs
 #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
@@ -175,9 +223,12 @@ impl From<BlindSignatureDleq> 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<Amount, FfiError> {
-    let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
+    let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+        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<SpendingConditions> for cdk::nuts::SpendingConditions {
 #[derive(Debug, Clone, uniffi::Record)]
 pub struct ProofInfo {
     /// Proof
-    pub proof: std::sync::Arc<Proof>,
+    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<cdk::types::ProofInfo> 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<ProofInfo, FfiError> {
 pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
     // 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(),

+ 19 - 106
crates/cdk-ffi/src/types/quote.rs

@@ -77,30 +77,25 @@ impl TryFrom<MintQuote> 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()
-        }
-    }
-
-    /// Check if quote is expired
-    pub fn is_expired(&self, current_time: u64) -> bool {
-        current_time > self.expiry
-    }
+/// Get total amount for a mint quote (amount paid)
+#[uniffi::export]
+pub fn mint_quote_total_amount(quote: &MintQuote) -> Result<Amount, FfiError> {
+    let cdk_quote: cdk::wallet::MintQuote = quote.clone().try_into()?;
+    Ok(cdk_quote.total_amount().into())
+}
 
-    /// Get amount that can be minted
-    pub fn amount_mintable(&self) -> Amount {
-        Amount::new(self.amount_paid.value - self.amount_issued.value)
-    }
+/// Check if mint quote is expired
+#[uniffi::export]
+pub fn mint_quote_is_expired(quote: &MintQuote, current_time: u64) -> Result<bool, FfiError> {
+    let cdk_quote: cdk::wallet::MintQuote = quote.clone().try_into()?;
+    Ok(cdk_quote.is_expired(current_time))
+}
 
-    /// Convert MintQuote to JSON string
-    pub fn to_json(&self) -> Result<String, FfiError> {
-        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<Amount, FfiError> {
+    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<String, FfiError> {
 }
 
 /// 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<cdk::nuts::MintQuoteBolt11Response<String>> 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<u64> {
-        self.expiry
-    }
-
-    /// Get amount
-    pub fn amount(&self) -> Option<Amount> {
-        self.amount
-    }
-
-    /// Get unit
-    pub fn unit(&self) -> Option<CurrencyUnit> {
-        self.unit.clone()
-    }
-
-    /// Get pubkey
-    pub fn pubkey(&self) -> Option<String> {
-        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<cdk::nuts::MeltQuoteBolt11Response<String>> 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<String> {
-        self.payment_preimage.clone()
-    }
-
-    /// Get request
-    pub fn request(&self) -> Option<String> {
-        self.request.clone()
-    }
-
-    /// Get unit
-    pub fn unit(&self) -> Option<CurrencyUnit> {
-        self.unit.clone()
-    }
-}
-
 /// FFI-compatible PaymentMethod
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
 pub enum PaymentMethod {

+ 4 - 8
crates/cdk-ffi/src/types/subscription.rs

@@ -139,13 +139,9 @@ pub enum NotificationPayload {
     /// Proof state update
     ProofState { proof_states: Vec<ProofStateUpdate> },
     /// Mint quote update
-    MintQuoteUpdate {
-        quote: std::sync::Arc<MintQuoteBolt11Response>,
-    },
+    MintQuoteUpdate { quote: MintQuoteBolt11Response },
     /// Melt quote update
-    MeltQuoteUpdate {
-        quote: std::sync::Arc<MeltQuoteBolt11Response>,
-    },
+    MeltQuoteUpdate { quote: MeltQuoteBolt11Response },
 }
 
 impl From<MintEvent<String>> for NotificationPayload {
@@ -156,12 +152,12 @@ impl From<MintEvent<String>> 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(),
                 }
             }
             _ => {

+ 18 - 1
crates/cdk-ffi/src/types/transaction.rs

@@ -106,6 +106,21 @@ pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError>
     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<MintUrl>,
+    direction: Option<TransactionDirection>,
+    unit: Option<CurrencyUnit>,
+) -> Result<bool, FfiError> {
+    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<Self, FfiError> {
-        let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            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(),

+ 4 - 7
crates/cdk-ffi/src/types/wallet.rs

@@ -314,7 +314,7 @@ impl From<cdk::wallet::PreparedSend> 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<cdk::types::Melted> 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(),
         }

+ 22 - 29
crates/cdk-ffi/src/wallet.rs

@@ -119,8 +119,9 @@ impl Wallet {
         options: ReceiveOptions,
         memo: Option<String>,
     ) -> Result<Amount, FfiError> {
-        let cdk_proofs: Vec<cdk::nuts::Proof> =
-            proofs.into_iter().map(|p| p.inner.clone()).collect();
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            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(&quote_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<SpendingConditions>,
         include_fees: bool,
     ) -> Result<Option<Proofs>, FfiError> {
-        let cdk_proofs: Vec<cdk::nuts::Proof> =
-            input_proofs.into_iter().map(|p| p.inner.clone()).collect();
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            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<Vec<bool>, FfiError> {
-        let cdk_proofs: Vec<cdk::nuts::Proof> =
-            proofs.into_iter().map(|p| p.inner.clone()).collect();
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            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<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            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<Amount, FfiError> {
         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<Proofs, FfiError> {
         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

+ 6 - 9
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!(