Cesar Rodas 2 viikkoa sitten
vanhempi
säilyke
6fd8072178

+ 11 - 20
crates/cdk-cli/src/sub_commands/mint.rs

@@ -5,7 +5,7 @@ use cdk::amount::SplitTarget;
 use cdk::mint_url::MintUrl;
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::PaymentMethod;
-use cdk::wallet::MultiMintWallet;
+use cdk::wallet::{MultiMintWallet, WalletTrait};
 use cdk::{Amount, StreamExt};
 use cdk_common::nut00::KnownMethod;
 use clap::Args;
@@ -56,14 +56,9 @@ pub async fn mint(
                 let amount = sub_command_args
                     .amount
                     .ok_or(anyhow!("Amount must be defined"))?;
-                let quote = wallet
-                    .mint_quote(
-                        PaymentMethod::BOLT11,
-                        Some(Amount::from(amount)),
-                        description,
-                        None,
-                    )
-                    .await?;
+                let quote =
+                    WalletTrait::mint_quote(wallet.as_ref(), Amount::from(amount), description)
+                        .await?;
 
                 println!(
                     "Quote: id={}, state={}, amount={}, expiry={}",
@@ -85,14 +80,10 @@ pub async fn mint(
                         .single_use
                         .map_or("none".to_string(), |b| b.to_string())
                 );
-                let quote = wallet
-                    .mint_quote(
-                        payment_method.clone(),
-                        amount.map(|a| a.into()),
-                        description,
-                        None,
-                    )
-                    .await?;
+                let amount = amount.ok_or(anyhow!("Amount must be defined for bolt12"))?;
+                let quote =
+                    WalletTrait::mint_quote(wallet.as_ref(), Amount::from(amount), description)
+                        .await?;
 
                 println!(
                     "Quote: id={}, state={}, amount={}, expiry={}",
@@ -114,9 +105,9 @@ pub async fn mint(
                         .single_use
                         .map_or("none".to_string(), |b| b.to_string())
                 );
-                let quote = wallet
-                    .mint_quote(payment_method.clone(), amount.map(|a| a.into()), None, None)
-                    .await?;
+                let amount = amount.ok_or(anyhow!("Amount must be defined"))?;
+                let quote =
+                    WalletTrait::mint_quote(wallet.as_ref(), Amount::from(amount), None).await?;
 
                 println!(
                     "Quote: id={}, state={}, amount={}, expiry={}",

+ 1 - 1
crates/cdk-cli/src/sub_commands/npubcash.rs

@@ -6,7 +6,7 @@ use anyhow::{bail, Result};
 use cdk::amount::SplitTarget;
 use cdk::mint_url::MintUrl;
 use cdk::nuts::nut00::ProofsMethods;
-use cdk::wallet::{MultiMintWallet, Wallet};
+use cdk::wallet::{MultiMintWallet, Wallet, WalletTrait};
 use cdk::StreamExt;
 use clap::Subcommand;
 use nostr_sdk::ToBech32;

+ 1 - 1
crates/cdk-cli/src/sub_commands/pay_request.rs

@@ -2,7 +2,7 @@ use std::io::{self, Write};
 
 use anyhow::{anyhow, Result};
 use cdk::nuts::PaymentRequest;
-use cdk::wallet::MultiMintWallet;
+use cdk::wallet::{MultiMintWallet, WalletTrait};
 use cdk::Amount;
 use clap::Args;
 

+ 314 - 0
crates/cdk-common/src/wallet/mod.rs

@@ -671,3 +671,317 @@ mod tests {
         assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
     }
 }
+
+/// Unified wallet trait covering all wallet operations.
+#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
+pub trait Wallet: Send + Sync {
+    // --- Associated types ---
+
+    /// Amount type used for balances and values
+    type Amount: Clone + Send + Sync;
+    /// Collection of proofs
+    type Proofs: Clone + Send + Sync;
+    /// Individual proof
+    type Proof: Clone + Send + Sync;
+    /// Mint quote information
+    type MintQuote: Clone + Send + Sync;
+    /// Melt quote information
+    type MeltQuote: Clone + Send + Sync;
+    /// Result type for melt operations
+    type MeltResult: Clone + Send + Sync;
+    /// Token type for receiving
+    type Token: Clone + Send + Sync;
+    /// Currency unit (e.g., sat, msat)
+    type CurrencyUnit: Clone + Send + Sync;
+    /// Mint URL
+    type MintUrl: Clone + Send + Sync;
+    /// Mint information
+    type MintInfo: Clone + Send + Sync;
+    /// Keyset information
+    type KeySetInfo: Clone + Send + Sync;
+    /// Error type
+    type Error: Send + Sync + 'static;
+    /// Send options
+    type SendOptions: Clone + Send + Sync;
+    /// Receive options
+    type ReceiveOptions: Clone + Send + Sync;
+    /// Spending conditions
+    type SpendingConditions: Clone + Send + Sync;
+    /// Split target for proof amounts
+    type SplitTarget: Clone + Send + Sync + Default;
+    /// Payment method (e.g., Bolt11, Bolt12)
+    type PaymentMethod: Clone + Send + Sync;
+    /// Melt options (e.g., MPP, amountless)
+    type MeltOptions: Clone + Send + Sync;
+    /// Wallet restore result
+    type Restored: Clone + Send + Sync;
+    /// Transaction record
+    type Transaction: Clone + Send + Sync;
+    /// Transaction identifier
+    type TransactionId: Clone + Send + Sync;
+    /// Transaction direction (incoming/outgoing)
+    type TransactionDirection: Clone + Send + Sync;
+    /// Payment request (NUT-18)
+    type PaymentRequest: Clone + Send + Sync;
+    /// Active subscription handle
+    type Subscription: Send + Sync;
+    /// Subscription parameters
+    type SubscribeParams: Clone + Send + Sync;
+
+    // --- Identity ---
+
+    /// Get the mint URL for this wallet
+    fn mint_url(&self) -> Self::MintUrl;
+
+    /// Get the currency unit for this wallet
+    fn unit(&self) -> Self::CurrencyUnit;
+
+    // --- Balance ---
+
+    /// Get total unspent balance
+    async fn total_balance(&self) -> Result<Self::Amount, Self::Error>;
+
+    /// Get total pending balance
+    async fn total_pending_balance(&self) -> Result<Self::Amount, Self::Error>;
+
+    /// Get total reserved balance
+    async fn total_reserved_balance(&self) -> Result<Self::Amount, Self::Error>;
+
+    // --- Mint info ---
+
+    /// Fetch mint info from the mint (always makes a network call)
+    async fn fetch_mint_info(&self) -> Result<Option<Self::MintInfo>, Self::Error>;
+
+    /// Load mint info from cache (may fetch if cache is stale)
+    async fn load_mint_info(&self) -> Result<Self::MintInfo, Self::Error>;
+
+    /// Get the active keyset with the lowest fees
+    async fn get_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
+
+    /// Refresh keysets by fetching latest from the mint
+    async fn refresh_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
+
+    // --- Minting ---
+
+    /// Create a mint quote (Bolt11-only simplified interface)
+    async fn mint_quote(
+        &self,
+        amount: Self::Amount,
+        description: Option<String>,
+    ) -> Result<Self::MintQuote, Self::Error>;
+
+    /// Refresh a specific mint quote status from the mint
+    async fn refresh_mint_quote(
+        &self,
+        quote_id: &str,
+    ) -> Result<Self::MintQuote, Self::Error>;
+
+    // --- Melting ---
+
+    /// Create a melt quote for a payment request
+    async fn melt_quote(
+        &self,
+        method: Self::PaymentMethod,
+        request: String,
+        options: Option<Self::MeltOptions>,
+        extra: Option<String>,
+    ) -> Result<Self::MeltQuote, Self::Error>;
+
+    // --- Sending ---
+
+    /// Send ecash: select proofs, optionally swap, and produce a token
+    async fn send(
+        &self,
+        amount: Self::Amount,
+        options: Self::SendOptions,
+    ) -> Result<Self::Token, Self::Error>;
+
+    /// Get all pending send operation IDs
+    async fn get_pending_sends(&self) -> Result<Vec<String>, Self::Error>;
+
+    /// Revoke a pending send, returning proofs to the wallet
+    async fn revoke_send(
+        &self,
+        operation_id: &str,
+    ) -> Result<Self::Amount, Self::Error>;
+
+    /// Check if a pending send has been claimed by the receiver
+    async fn check_send_status(
+        &self,
+        operation_id: &str,
+    ) -> Result<bool, Self::Error>;
+
+    // --- Receiving ---
+
+    /// Receive tokens from an encoded token string
+    async fn receive(
+        &self,
+        encoded_token: &str,
+        options: Self::ReceiveOptions,
+    ) -> Result<Self::Amount, Self::Error>;
+
+    /// Receive proofs directly (not from a token string)
+    async fn receive_proofs(
+        &self,
+        proofs: Self::Proofs,
+        options: Self::ReceiveOptions,
+        memo: Option<String>,
+        token: Option<String>,
+    ) -> Result<Self::Amount, Self::Error>;
+
+    // --- Swapping ---
+
+    /// Swap proofs (e.g. to change denominations or add spending conditions)
+    async fn swap(
+        &self,
+        amount: Option<Self::Amount>,
+        amount_split_target: Self::SplitTarget,
+        input_proofs: Self::Proofs,
+        spending_conditions: Option<Self::SpendingConditions>,
+        include_fees: bool,
+    ) -> Result<Option<Self::Proofs>, Self::Error>;
+
+    // --- Proofs ---
+
+    /// Get all unspent proofs
+    async fn get_unspent_proofs(&self) -> Result<Self::Proofs, Self::Error>;
+
+    /// Get all pending proofs
+    async fn get_pending_proofs(&self) -> Result<Self::Proofs, Self::Error>;
+
+    /// Get all reserved proofs (used in pending sends)
+    async fn get_reserved_proofs(&self) -> Result<Self::Proofs, Self::Error>;
+
+    /// Get all pending-spent proofs
+    async fn get_pending_spent_proofs(&self) -> Result<Self::Proofs, Self::Error>;
+
+    /// Check all pending proofs against the mint and reclaim unspent
+    async fn check_all_pending_proofs(&self) -> Result<Self::Amount, Self::Error>;
+
+    /// Check which proofs are spent
+    async fn check_proofs_spent(&self, proofs: Self::Proofs) -> Result<Vec<bool>, Self::Error>;
+
+    /// Reclaim unspent proofs by swapping them back into the wallet
+    async fn reclaim_unspent(&self, proofs: Self::Proofs) -> Result<(), Self::Error>;
+
+    // --- Transactions ---
+
+    /// List transactions, optionally filtered by direction
+    async fn list_transactions(
+        &self,
+        direction: Option<Self::TransactionDirection>,
+    ) -> Result<Vec<Self::Transaction>, Self::Error>;
+
+    /// Get a single transaction by ID
+    async fn get_transaction(
+        &self,
+        id: Self::TransactionId,
+    ) -> Result<Option<Self::Transaction>, Self::Error>;
+
+    /// Get proofs associated with a transaction
+    async fn get_proofs_for_transaction(
+        &self,
+        id: Self::TransactionId,
+    ) -> Result<Self::Proofs, Self::Error>;
+
+    /// Revert a transaction (return proofs to unspent state)
+    async fn revert_transaction(
+        &self,
+        id: Self::TransactionId,
+    ) -> Result<(), Self::Error>;
+
+    // --- Token verification ---
+
+    /// Verify DLEQ proofs on a token
+    async fn verify_token_dleq(
+        &self,
+        token: &Self::Token,
+    ) -> Result<(), Self::Error>;
+
+    // --- Wallet recovery ---
+
+    /// Restore wallet from seed, recovering all proofs
+    async fn restore(&self) -> Result<Self::Restored, Self::Error>;
+
+    // --- Keysets & fees ---
+
+    /// Get fee rate for a specific keyset
+    async fn get_keyset_fees(
+        &self,
+        keyset_id: &str,
+    ) -> Result<u64, Self::Error>;
+
+    /// Calculate total fee for a given proof count and keyset
+    async fn calculate_fee(
+        &self,
+        proof_count: u64,
+        keyset_id: &str,
+    ) -> Result<Self::Amount, Self::Error>;
+
+    // --- Subscriptions ---
+
+    /// Subscribe to mint events
+    async fn subscribe(
+        &self,
+        params: Self::SubscribeParams,
+    ) -> Result<Self::Subscription, Self::Error>;
+
+    // --- Payment requests ---
+
+    /// Pay a NUT-18 payment request
+    async fn pay_request(
+        &self,
+        request: Self::PaymentRequest,
+        custom_amount: Option<Self::Amount>,
+    ) -> Result<(), Self::Error>;
+
+    // --- BIP353 / Lightning address ---
+
+    /// Create a melt quote from a BIP353 address
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_bip353_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error>;
+
+    /// Create a melt quote from a Lightning address
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_lightning_address_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error>;
+
+    /// Create a melt quote from a human-readable address (BIP353 or Lightning)
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_human_readable_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error>;
+
+    // --- Auth ---
+
+    /// Set Clear Auth Token (CAT)
+    async fn set_cat(&self, cat: String) -> Result<(), Self::Error>;
+
+    /// Set refresh token for authentication
+    async fn set_refresh_token(
+        &self,
+        refresh_token: String,
+    ) -> Result<(), Self::Error>;
+
+    /// Refresh the access token using the stored refresh token
+    async fn refresh_access_token(&self) -> Result<(), Self::Error>;
+
+    /// Mint blind auth proofs
+    async fn mint_blind_auth(
+        &self,
+        amount: Self::Amount,
+    ) -> Result<Self::Proofs, Self::Error>;
+
+    /// Get all unspent auth proofs
+    async fn get_unspent_auth_proofs(&self) -> Result<Self::Proofs, Self::Error>;
+}

+ 2 - 0
crates/cdk-ffi/src/lib.rs

@@ -18,7 +18,9 @@ pub mod sqlite;
 pub mod token;
 pub mod types;
 pub mod wallet;
+mod wallet_trait;
 
+pub use cdk_common::wallet::Wallet as WalletTrait;
 pub use database::*;
 pub use error::*;
 pub use logging::*;

+ 2 - 10
crates/cdk-ffi/src/multi_mint_wallet.rs

@@ -352,21 +352,13 @@ impl MultiMintWallet {
     pub async fn mint_quote(
         &self,
         mint_url: MintUrl,
-        payment_method: PaymentMethod,
-        amount: Option<Amount>,
+        amount: Amount,
         description: Option<String>,
-        extra: Option<String>,
     ) -> Result<MintQuote, FfiError> {
         let cdk_mint_url: cdk::mint_url::MintUrl = mint_url.try_into()?;
         let quote = self
             .inner
-            .mint_quote(
-                &cdk_mint_url,
-                payment_method,
-                amount.map(Into::into),
-                description,
-                extra,
-            )
+            .mint_quote(&cdk_mint_url, amount.into(), description)
             .await?;
         Ok(quote.into())
     }

+ 2 - 1
crates/cdk-ffi/src/types/amount.rs

@@ -129,9 +129,10 @@ impl From<CurrencyUnit> for CdkCurrencyUnit {
 }
 
 /// FFI-compatible SplitTarget
-#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, uniffi::Enum)]
 pub enum SplitTarget {
     /// Default target; least amount of proofs
+    #[default]
     None,
     /// Target amount for wallet to have most proofs that add up to value
     Value { amount: Amount },

+ 21 - 40
crates/cdk-ffi/src/wallet.rs

@@ -4,7 +4,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use bip39::Mnemonic;
-use cdk::wallet::{Wallet as CdkWallet, WalletBuilder as CdkWalletBuilder};
+use cdk::wallet::{Wallet as CdkWallet, WalletBuilder as CdkWalletBuilder, WalletTrait};
 
 use crate::error::FfiError;
 use crate::token::Token;
@@ -22,6 +22,11 @@ impl Wallet {
     pub(crate) fn from_inner(inner: Arc<CdkWallet>) -> Self {
         Self { inner }
     }
+
+    /// Get reference to the inner CDK wallet
+    pub(crate) fn inner(&self) -> &Arc<CdkWallet> {
+        &self.inner
+    }
 }
 
 #[uniffi::export(async_runtime = "tokio")]
@@ -92,37 +97,9 @@ impl Wallet {
         self.inner.set_metadata_cache_ttl(ttl);
     }
 
-    /// Get total balance
-    pub async fn total_balance(&self) -> Result<Amount, FfiError> {
-        let balance = self.inner.total_balance().await?;
-        Ok(balance.into())
-    }
-
-    /// Get total pending balance
-    pub async fn total_pending_balance(&self) -> Result<Amount, FfiError> {
-        let balance = self.inner.total_pending_balance().await?;
-        Ok(balance.into())
-    }
-
-    /// Get total reserved balance
-    pub async fn total_reserved_balance(&self) -> Result<Amount, FfiError> {
-        let balance = self.inner.total_reserved_balance().await?;
-        Ok(balance.into())
-    }
-
-    /// Get mint info from mint
-    pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, FfiError> {
-        let info = self.inner.fetch_mint_info().await?;
-        Ok(info.map(Into::into))
-    }
-
-    /// Load mint info
-    ///
-    /// This will get mint info from cache if it is fresh
-    pub async fn load_mint_info(&self) -> Result<MintInfo, FfiError> {
-        let info = self.inner.load_mint_info().await?;
-        Ok(info.into())
-    }
+    // total_balance(), total_pending_balance(), total_reserved_balance(),
+    // fetch_mint_info(), load_mint_info() are now available via wallet traits
+    // (WalletBalance, WalletMintInfo) in wallet_traits.rs
 
     /// Receive tokens
     pub async fn receive(
@@ -210,15 +187,19 @@ impl Wallet {
     /// Get a mint quote
     pub async fn mint_quote(
         &self,
-        payment_method: PaymentMethod,
+        _payment_method: PaymentMethod,
         amount: Option<Amount>,
         description: Option<String>,
-        extra: Option<String>,
+        _extra: Option<String>,
     ) -> Result<MintQuote, FfiError> {
-        let quote = self
-            .inner
-            .mint_quote(payment_method, amount.map(Into::into), description, extra)
-            .await?;
+        let quote = <CdkWallet as WalletTrait>::mint_quote(
+            self.inner.as_ref(),
+            amount
+                .ok_or(FfiError::internal("amount required for mint quote"))?
+                .into(),
+            description,
+        )
+        .await?;
         Ok(quote.into())
     }
 
@@ -461,13 +442,13 @@ impl Wallet {
 
     /// Refresh keysets from the mint
     pub async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, FfiError> {
-        let keysets = self.inner.refresh_keysets().await?;
+        let keysets = <CdkWallet as WalletTrait>::refresh_keysets(self.inner.as_ref()).await?;
         Ok(keysets.into_iter().map(Into::into).collect())
     }
 
     /// Get the active keyset for the wallet's unit
     pub async fn get_active_keyset(&self) -> Result<KeySetInfo, FfiError> {
-        let keyset = self.inner.get_active_keyset().await?;
+        let keyset = <CdkWallet as WalletTrait>::get_active_keyset(self.inner.as_ref()).await?;
         Ok(keyset.into())
     }
 

+ 535 - 0
crates/cdk-ffi/src/wallet_trait.rs

@@ -0,0 +1,535 @@
+//! Wallet trait implementations for the FFI [`Wallet`].
+//!
+//! Each method delegates to the CDK wallet's trait implementation,
+//! converting types between FFI and CDK representations.
+
+use std::sync::Arc;
+
+use cdk_common::wallet::Wallet as CdkWalletTrait;
+
+use crate::error::FfiError;
+use crate::types::*;
+use crate::wallet::Wallet;
+
+#[async_trait::async_trait]
+impl CdkWalletTrait for Wallet {
+    type Amount = Amount;
+    type Proofs = Proofs;
+    type Proof = Proof;
+    type MintQuote = MintQuote;
+    type MeltQuote = MeltQuote;
+    type MeltResult = FinalizedMelt;
+    type Token = String;
+    type CurrencyUnit = CurrencyUnit;
+    type MintUrl = MintUrl;
+    type MintInfo = MintInfo;
+    type KeySetInfo = KeySetInfo;
+    type Error = FfiError;
+    type SendOptions = SendOptions;
+    type ReceiveOptions = ReceiveOptions;
+    type SpendingConditions = SpendingConditions;
+    type SplitTarget = SplitTarget;
+    type PaymentMethod = PaymentMethod;
+    type MeltOptions = MeltOptions;
+    type Restored = Restored;
+    type Transaction = Transaction;
+    type TransactionId = TransactionId;
+    type TransactionDirection = TransactionDirection;
+    type PaymentRequest = Arc<crate::types::payment_request::PaymentRequest>;
+    type Subscription = Arc<ActiveSubscription>;
+    type SubscribeParams = SubscribeParams;
+
+    fn mint_url(&self) -> Self::MintUrl {
+        <cdk::wallet::Wallet as CdkWalletTrait>::mint_url(self.inner().as_ref()).into()
+    }
+
+    fn unit(&self) -> Self::CurrencyUnit {
+        <cdk::wallet::Wallet as CdkWalletTrait>::unit(self.inner().as_ref()).into()
+    }
+
+    async fn total_balance(&self) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::total_balance(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn total_pending_balance(&self) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::total_pending_balance(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn total_reserved_balance(&self) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::total_reserved_balance(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn fetch_mint_info(&self) -> Result<Option<Self::MintInfo>, Self::Error> {
+        let info =
+            <cdk::wallet::Wallet as CdkWalletTrait>::fetch_mint_info(self.inner().as_ref())
+                .await?;
+        Ok(info.map(Into::into))
+    }
+
+    async fn load_mint_info(&self) -> Result<Self::MintInfo, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::load_mint_info(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn get_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_active_keyset(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn refresh_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error> {
+        let keysets =
+            <cdk::wallet::Wallet as CdkWalletTrait>::refresh_keysets(self.inner().as_ref())
+                .await?;
+        Ok(keysets.into_iter().map(Into::into).collect())
+    }
+
+    async fn mint_quote(
+        &self,
+        amount: Self::Amount,
+        description: Option<String>,
+    ) -> Result<Self::MintQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::mint_quote(
+                self.inner().as_ref(),
+                amount.into(),
+                description,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn refresh_mint_quote(&self, quote_id: &str) -> Result<Self::MintQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::refresh_mint_quote(
+                self.inner().as_ref(),
+                quote_id,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn melt_quote(
+        &self,
+        method: Self::PaymentMethod,
+        request: String,
+        options: Option<Self::MeltOptions>,
+        extra: Option<String>,
+    ) -> Result<Self::MeltQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::melt_quote(
+                self.inner().as_ref(),
+                method.into(),
+                request,
+                options.map(Into::into),
+                extra,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn send(
+        &self,
+        amount: Self::Amount,
+        options: Self::SendOptions,
+    ) -> Result<Self::Token, Self::Error> {
+        let token = <cdk::wallet::Wallet as CdkWalletTrait>::send(
+            self.inner().as_ref(),
+            amount.into(),
+            options.into(),
+        )
+        .await?;
+        Ok(token.to_string())
+    }
+
+    async fn get_pending_sends(&self) -> Result<Vec<String>, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_pending_sends(self.inner().as_ref())
+                .await?,
+        )
+    }
+
+    async fn revoke_send(&self, operation_id: &str) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::revoke_send(
+                self.inner().as_ref(),
+                operation_id,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn check_send_status(&self, operation_id: &str) -> Result<bool, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::check_send_status(
+                self.inner().as_ref(),
+                operation_id,
+            )
+            .await?,
+        )
+    }
+
+    async fn receive(
+        &self,
+        encoded_token: &str,
+        options: Self::ReceiveOptions,
+    ) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::receive(
+                self.inner().as_ref(),
+                encoded_token,
+                options.into(),
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn receive_proofs(
+        &self,
+        proofs: Self::Proofs,
+        options: Self::ReceiveOptions,
+        memo: Option<String>,
+        token: Option<String>,
+    ) -> Result<Self::Amount, Self::Error> {
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            proofs.into_iter().map(|p| p.try_into()).collect();
+        let cdk_proofs = cdk_proofs?;
+
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::receive_proofs(
+                self.inner().as_ref(),
+                cdk_proofs,
+                options.into(),
+                memo,
+                token,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn swap(
+        &self,
+        amount: Option<Self::Amount>,
+        amount_split_target: Self::SplitTarget,
+        input_proofs: Self::Proofs,
+        spending_conditions: Option<Self::SpendingConditions>,
+        include_fees: bool,
+    ) -> Result<Option<Self::Proofs>, Self::Error> {
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            input_proofs.into_iter().map(|p| p.try_into()).collect();
+        let cdk_proofs = cdk_proofs?;
+
+        let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
+
+        let result = <cdk::wallet::Wallet as CdkWalletTrait>::swap(
+            self.inner().as_ref(),
+            amount.map(Into::into),
+            amount_split_target.into(),
+            cdk_proofs,
+            conditions,
+            include_fees,
+        )
+        .await?;
+
+        Ok(result.map(|proofs| proofs.into_iter().map(|p| p.into()).collect()))
+    }
+
+    async fn get_unspent_proofs(&self) -> Result<Self::Proofs, Self::Error> {
+        let proofs =
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_unspent_proofs(self.inner().as_ref())
+                .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn get_pending_proofs(&self) -> Result<Self::Proofs, Self::Error> {
+        let proofs =
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_pending_proofs(self.inner().as_ref())
+                .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn get_reserved_proofs(&self) -> Result<Self::Proofs, Self::Error> {
+        let proofs =
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_reserved_proofs(self.inner().as_ref())
+                .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn get_pending_spent_proofs(&self) -> Result<Self::Proofs, Self::Error> {
+        let proofs = <cdk::wallet::Wallet as CdkWalletTrait>::get_pending_spent_proofs(
+            self.inner().as_ref(),
+        )
+        .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn check_all_pending_proofs(&self) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::check_all_pending_proofs(
+                self.inner().as_ref(),
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn check_proofs_spent(&self, proofs: Self::Proofs) -> Result<Vec<bool>, Self::Error> {
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            proofs.into_iter().map(|p| p.try_into()).collect();
+        let cdk_proofs = cdk_proofs?;
+
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::check_proofs_spent(
+                self.inner().as_ref(),
+                cdk_proofs,
+            )
+            .await?,
+        )
+    }
+
+    async fn reclaim_unspent(&self, proofs: Self::Proofs) -> Result<(), Self::Error> {
+        let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
+            proofs.into_iter().map(|p| p.try_into()).collect();
+        let cdk_proofs = cdk_proofs?;
+
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::reclaim_unspent(
+                self.inner().as_ref(),
+                cdk_proofs,
+            )
+            .await?,
+        )
+    }
+
+    async fn list_transactions(
+        &self,
+        direction: Option<Self::TransactionDirection>,
+    ) -> Result<Vec<Self::Transaction>, Self::Error> {
+        let cdk_direction = direction.map(Into::into);
+        let transactions = <cdk::wallet::Wallet as CdkWalletTrait>::list_transactions(
+            self.inner().as_ref(),
+            cdk_direction,
+        )
+        .await?;
+        Ok(transactions.into_iter().map(Into::into).collect())
+    }
+
+    async fn get_transaction(
+        &self,
+        id: Self::TransactionId,
+    ) -> Result<Option<Self::Transaction>, Self::Error> {
+        let cdk_id = id.try_into()?;
+        let transaction = <cdk::wallet::Wallet as CdkWalletTrait>::get_transaction(
+            self.inner().as_ref(),
+            cdk_id,
+        )
+        .await?;
+        Ok(transaction.map(Into::into))
+    }
+
+    async fn get_proofs_for_transaction(
+        &self,
+        id: Self::TransactionId,
+    ) -> Result<Self::Proofs, Self::Error> {
+        let cdk_id = id.try_into()?;
+        let proofs = <cdk::wallet::Wallet as CdkWalletTrait>::get_proofs_for_transaction(
+            self.inner().as_ref(),
+            cdk_id,
+        )
+        .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn revert_transaction(&self, id: Self::TransactionId) -> Result<(), Self::Error> {
+        let cdk_id = id.try_into()?;
+        <cdk::wallet::Wallet as CdkWalletTrait>::revert_transaction(
+            self.inner().as_ref(),
+            cdk_id,
+        )
+        .await?;
+        Ok(())
+    }
+
+    async fn verify_token_dleq(&self, token: &String) -> Result<(), Self::Error> {
+        let cdk_token: cdk::nuts::Token = token.parse().map_err(FfiError::internal)?;
+        <cdk::wallet::Wallet as CdkWalletTrait>::verify_token_dleq(
+            self.inner().as_ref(),
+            &cdk_token,
+        )
+        .await?;
+        Ok(())
+    }
+
+    async fn restore(&self) -> Result<Self::Restored, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::restore(self.inner().as_ref())
+                .await?
+                .into(),
+        )
+    }
+
+    async fn get_keyset_fees(&self, keyset_id: &str) -> Result<u64, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::get_keyset_fees(
+                self.inner().as_ref(),
+                keyset_id,
+            )
+            .await?,
+        )
+    }
+
+    async fn calculate_fee(
+        &self,
+        proof_count: u64,
+        keyset_id: &str,
+    ) -> Result<Self::Amount, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::calculate_fee(
+                self.inner().as_ref(),
+                proof_count,
+                keyset_id,
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn subscribe(
+        &self,
+        params: Self::SubscribeParams,
+    ) -> Result<Self::Subscription, Self::Error> {
+        let cdk_params: cdk::nuts::nut17::Params<Arc<String>> = params.clone().into();
+        let sub_id = cdk_params.id.to_string();
+        let active_sub = <cdk::wallet::Wallet as CdkWalletTrait>::subscribe(
+            self.inner().as_ref(),
+            cdk_params,
+        )
+        .await?;
+        Ok(Arc::new(ActiveSubscription::new(active_sub, sub_id)))
+    }
+
+    async fn pay_request(
+        &self,
+        request: Self::PaymentRequest,
+        custom_amount: Option<Self::Amount>,
+    ) -> Result<(), Self::Error> {
+        <cdk::wallet::Wallet as CdkWalletTrait>::pay_request(
+            self.inner().as_ref(),
+            request.inner().clone(),
+            custom_amount.map(Into::into),
+        )
+        .await?;
+        Ok(())
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_bip353_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::melt_bip353_quote(
+                self.inner().as_ref(),
+                address,
+                amount.into(),
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_lightning_address_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::melt_lightning_address_quote(
+                self.inner().as_ref(),
+                address,
+                amount.into(),
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_human_readable_quote(
+        &self,
+        address: &str,
+        amount: Self::Amount,
+    ) -> Result<Self::MeltQuote, Self::Error> {
+        Ok(
+            <cdk::wallet::Wallet as CdkWalletTrait>::melt_human_readable_quote(
+                self.inner().as_ref(),
+                address,
+                amount.into(),
+            )
+            .await?
+            .into(),
+        )
+    }
+
+    async fn set_cat(&self, cat: String) -> Result<(), Self::Error> {
+        <cdk::wallet::Wallet as CdkWalletTrait>::set_cat(self.inner().as_ref(), cat).await?;
+        Ok(())
+    }
+
+    async fn set_refresh_token(&self, refresh_token: String) -> Result<(), Self::Error> {
+        <cdk::wallet::Wallet as CdkWalletTrait>::set_refresh_token(
+            self.inner().as_ref(),
+            refresh_token,
+        )
+        .await?;
+        Ok(())
+    }
+
+    async fn refresh_access_token(&self) -> Result<(), Self::Error> {
+        <cdk::wallet::Wallet as CdkWalletTrait>::refresh_access_token(self.inner().as_ref())
+            .await?;
+        Ok(())
+    }
+
+    async fn mint_blind_auth(&self, amount: Self::Amount) -> Result<Self::Proofs, Self::Error> {
+        let proofs = <cdk::wallet::Wallet as CdkWalletTrait>::mint_blind_auth(
+            self.inner().as_ref(),
+            amount.into(),
+        )
+        .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+
+    async fn get_unspent_auth_proofs(&self) -> Result<Self::Proofs, Self::Error> {
+        let proofs = <cdk::wallet::Wallet as CdkWalletTrait>::get_unspent_auth_proofs(
+            self.inner().as_ref(),
+        )
+        .await?;
+        Ok(proofs.into_iter().map(Into::into).collect())
+    }
+}

+ 2 - 4
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -27,7 +27,7 @@ use cdk::nuts::{
 };
 use cdk::types::{FeeReserve, QuoteTTL};
 use cdk::util::unix_time;
-use cdk::wallet::{AuthWallet, MintConnector, Wallet, WalletBuilder};
+use cdk::wallet::{AuthWallet, MintConnector, Wallet, WalletBuilder, WalletTrait};
 use cdk::{Amount, Error, Mint, StreamExt};
 use cdk_fake_wallet::FakeWallet;
 use tokio::sync::RwLock;
@@ -425,9 +425,7 @@ pub async fn fund_wallet(
     split_target: Option<SplitTarget>,
 ) -> Result<Amount> {
     let desired_amount = Amount::from(amount);
-    let quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(desired_amount), None, None)
-        .await?;
+    let quote = wallet.mint_quote(desired_amount, None).await?;
 
     Ok(wallet
         .proof_stream(quote, split_target.unwrap_or_default(), None)

+ 2 - 2
crates/cdk-integration-tests/src/lib.rs

@@ -26,7 +26,7 @@ use cdk::amount::{Amount, SplitTarget};
 use cdk::nuts::{
     MeltQuoteBolt11Response, MeltRequest, MintRequest, MintResponse, PreMintSecrets, Proofs,
 };
-use cdk::wallet::{HttpClient, MintConnector, MintQuote};
+use cdk::wallet::{HttpClient, MintConnector, MintQuote, WalletTrait};
 use cdk::{StreamExt, Wallet};
 use cdk_fake_wallet::create_fake_invoice;
 use init_regtest::{get_lnd_dir, LND_RPC_ADDR};
@@ -53,7 +53,7 @@ pub fn standard_keyset_amounts(max_order: u32) -> Vec<u64> {
 
 pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
     let quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(amount), None, None)
+        .mint_quote(amount, None)
         .await
         .expect("Could not get mint quote");
 

+ 3 - 9
crates/cdk-integration-tests/tests/async_melt.rs

@@ -14,7 +14,7 @@ use bip39::Mnemonic;
 use cashu::PaymentMethod;
 use cdk::amount::SplitTarget;
 use cdk::nuts::{CurrencyUnit, MeltQuoteState};
-use cdk::wallet::Wallet;
+use cdk::wallet::{Wallet, WalletTrait};
 use cdk::StreamExt;
 use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
 use cdk_sqlite::wallet::memory;
@@ -37,10 +37,7 @@ async fn test_async_melt_returns_pending() {
     .expect("failed to create new wallet");
 
     // Step 1: Mint some tokens
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
     let _proofs = proof_streams
@@ -109,10 +106,7 @@ async fn test_sync_melt_completes_fully() {
     .expect("failed to create new wallet");
 
     // Step 1: Mint some tokens
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
     let _proofs = proof_streams

+ 8 - 11
crates/cdk-integration-tests/tests/fake_auth.rs

@@ -12,7 +12,9 @@ use cdk::nuts::{
     MeltQuoteState, MeltRequest, MintQuoteBolt11Request, MintRequest, PaymentMethod,
     RestoreRequest, State, SwapRequest,
 };
-use cdk::wallet::{AuthHttpClient, AuthMintConnector, HttpClient, MintConnector, WalletBuilder};
+use cdk::wallet::{
+    AuthHttpClient, AuthMintConnector, HttpClient, MintConnector, WalletBuilder, WalletTrait,
+};
 use cdk::{Error, OidcClient};
 use cdk_fake_wallet::create_fake_invoice;
 use cdk_http_client::HttpClient as CommonHttpClient;
@@ -334,10 +336,7 @@ async fn test_mint_with_auth() {
 
     let mint_amount: Amount = 100.into();
 
-    let quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
-        .await
-        .unwrap();
+    let quote = wallet.mint_quote(mint_amount, None).await.unwrap();
 
     let proofs = wallet
         .wait_and_mint_quote(
@@ -520,7 +519,7 @@ async fn test_reuse_auth_proof() {
 
     {
         let quote = wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
+            .mint_quote(10.into(), None)
             .await
             .expect("Quote should be allowed");
 
@@ -534,9 +533,7 @@ async fn test_reuse_auth_proof() {
         .unwrap();
 
     {
-        let quote_res = wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
-            .await;
+        let quote_res = wallet.mint_quote(10.into(), None).await;
         assert!(
             matches!(quote_res, Err(Error::TokenAlreadySpent)),
             "Expected AuthRequired error, got {:?}",
@@ -653,7 +650,7 @@ async fn test_refresh_access_token() {
 
     // Try to get a mint quote with the refreshed token
     let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
+        .mint_quote(mint_amount, None)
         .await
         .expect("failed to get mint quote with refreshed token");
 
@@ -739,7 +736,7 @@ async fn test_auth_token_spending_order() {
     // Use tokens and verify they're used in the expected order (FIFO)
     for i in 0..3 {
         let mint_quote = wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
+            .mint_quote(10.into(), None)
             .await
             .expect("failed to get mint quote");
 

+ 35 - 137
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -26,7 +26,7 @@ use cdk::nuts::{
     SecretKey, State, SwapRequest,
 };
 use cdk::wallet::types::TransactionDirection;
-use cdk::wallet::{HttpClient, MintConnector, Wallet};
+use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletTrait};
 use cdk::StreamExt;
 use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
 use cdk_sqlite::wallet::memory;
@@ -45,10 +45,7 @@ async fn test_fake_tokens_pending() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -104,10 +101,7 @@ async fn test_fake_melt_payment_fail() {
     )
     .expect("Failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -182,10 +176,7 @@ async fn test_fake_melt_payment_fail_and_check() {
     )
     .expect("Failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -240,10 +231,7 @@ async fn test_fake_melt_payment_return_fail_status() {
     )
     .expect("Failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -334,10 +322,7 @@ async fn test_fake_melt_payment_error_unknown() {
     )
     .unwrap();
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -416,10 +401,7 @@ async fn test_fake_melt_payment_err_paid() {
     )
     .expect("Failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -481,10 +463,7 @@ async fn test_fake_melt_change_in_quote() {
     )
     .expect("Failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -564,10 +543,7 @@ async fn test_fake_mint_with_witness() {
         None,
     )
     .expect("failed to create new wallet");
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -594,10 +570,7 @@ async fn test_fake_mint_without_witness() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut payment_streams = wallet.payment_stream(&mint_quote);
 
@@ -649,10 +622,7 @@ async fn test_fake_mint_with_wrong_witness() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut payment_streams = wallet.payment_stream(&mint_quote);
 
@@ -710,10 +680,7 @@ async fn test_fake_mint_inflated() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut payment_streams = wallet.payment_stream(&mint_quote);
 
@@ -786,10 +753,7 @@ async fn test_fake_mint_multiple_units() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut payment_streams = wallet.payment_stream(&mint_quote);
 
@@ -889,10 +853,7 @@ async fn test_fake_mint_multiple_unit_swap() {
 
     wallet.refresh_keysets().await.unwrap();
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -912,10 +873,7 @@ async fn test_fake_mint_multiple_unit_swap() {
     .expect("failed to create usd wallet");
     wallet_usd.refresh_keysets().await.unwrap();
 
-    let mint_quote = wallet_usd
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams =
         wallet_usd.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
@@ -1020,10 +978,7 @@ async fn test_fake_mint_multiple_unit_melt() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1044,10 +999,7 @@ async fn test_fake_mint_multiple_unit_melt() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet_usd
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
     println!("Minted quote usd");
 
     let mut proof_streams =
@@ -1169,10 +1121,7 @@ async fn test_fake_mint_input_output_mismatch() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1231,10 +1180,7 @@ async fn test_fake_mint_swap_inflated() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
     let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
@@ -1284,10 +1230,7 @@ async fn test_fake_mint_swap_spend_after_fail() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1374,10 +1317,7 @@ async fn test_fake_mint_melt_spend_after_fail() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1468,10 +1408,7 @@ async fn test_fake_mint_duplicate_proofs_swap() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1552,10 +1489,7 @@ async fn test_fake_mint_duplicate_proofs_melt() {
     )
     .expect("failed to create new wallet");
 
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -1611,10 +1545,7 @@ async fn test_wallet_proof_recovery_after_failed_melt() {
     .expect("failed to create new wallet");
 
     // Mint 100 sats
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
     let _roof_streams = wallet
         .wait_and_mint_quote(
             mint_quote.clone(),
@@ -1704,10 +1635,7 @@ async fn test_concurrent_melt_same_invoice() {
 
     // Mint proofs for all wallets
     for (i, wallet) in wallets.iter().enumerate() {
-        let mint_quote = wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-            .await
-            .unwrap();
+        let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
         let mut proof_streams =
             wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
         proof_streams
@@ -1804,10 +1732,7 @@ async fn test_wallet_proof_recovery_after_failed_swap() {
     .expect("failed to create new wallet");
 
     // Mint 100 sats
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
     let initial_proofs = proof_streams
         .next()
@@ -1893,10 +1818,7 @@ async fn test_melt_proofs_external() {
     )
     .expect("failed to create sender wallet");
 
-    let mint_quote = wallet_sender
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet_sender.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams =
         wallet_sender.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
@@ -1991,10 +1913,7 @@ async fn test_melt_with_swap_for_exact_amount() {
     .expect("failed to create new wallet");
 
     // Mint 100 sats - this will give us proofs in standard denominations
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -2077,10 +1996,7 @@ async fn test_melt_exact_proofs_no_swap_needed() {
     .expect("failed to create new wallet");
 
     // Mint a larger amount to have more denomination options
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(1000.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(1000.into(), None).await.unwrap();
 
     let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
 
@@ -2136,10 +2052,7 @@ async fn test_check_all_mint_quotes_bolt11() {
     .expect("failed to create new wallet");
 
     // Create first mint quote and pay it (using proof_stream triggers fake wallet payment)
-    let mint_quote_1 = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote_1 = wallet.mint_quote(100.into(), None).await.unwrap();
 
     // Wait for the payment to be registered (fake wallet auto-pays)
     let mut payment_stream_1 = wallet.payment_stream(&mint_quote_1);
@@ -2150,10 +2063,7 @@ async fn test_check_all_mint_quotes_bolt11() {
         .expect("no error");
 
     // Create second mint quote and pay it
-    let mint_quote_2 = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(50.into()), None, None)
-        .await
-        .unwrap();
+    let mint_quote_2 = wallet.mint_quote(50.into(), None).await.unwrap();
 
     let mut payment_stream_2 = wallet.payment_stream(&mint_quote_2);
     payment_stream_2
@@ -2198,16 +2108,10 @@ async fn test_get_unissued_mint_quotes_wallet() {
     .expect("failed to create new wallet");
 
     // Create a quote but don't pay it (stays unpaid)
-    let unpaid_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
-        .await
-        .unwrap();
+    let unpaid_quote = wallet.mint_quote(100.into(), None).await.unwrap();
 
     // Create another quote and pay it but don't mint
-    let paid_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(50.into()), None, None)
-        .await
-        .unwrap();
+    let paid_quote = wallet.mint_quote(50.into(), None).await.unwrap();
     let mut payment_stream = wallet.payment_stream(&paid_quote);
     payment_stream
         .next()
@@ -2216,10 +2120,7 @@ async fn test_get_unissued_mint_quotes_wallet() {
         .expect("no error");
 
     // Create a third quote and fully mint it
-    let minted_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(25.into()), None, None)
-        .await
-        .unwrap();
+    let minted_quote = wallet.mint_quote(25.into(), None).await.unwrap();
     let mut proof_stream = wallet.proof_stream(minted_quote.clone(), SplitTarget::default(), None);
     proof_stream
         .next()
@@ -2271,10 +2172,7 @@ async fn test_refresh_mint_quote_status_updates_after_minting() {
     .expect("failed to create new wallet");
 
     let mint_amount = Amount::from(100);
-    let mint_quote = wallet
-        .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
-        .await
-        .unwrap();
+    let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
 
     // Get the quote from localstore before minting
     let quote_before = wallet

+ 1 - 34
crates/cdk/src/wallet/balance.rs

@@ -1,34 +1 @@
-use tracing::instrument;
-
-use crate::nuts::nut00::ProofsMethods;
-use crate::nuts::State;
-use crate::{Amount, Error, Wallet};
-
-impl Wallet {
-    /// Total unspent balance of wallet
-    #[instrument(skip(self))]
-    pub async fn total_balance(&self) -> Result<Amount, Error> {
-        // Use the efficient balance query instead of fetching all proofs
-        let balance = self
-            .localstore
-            .get_balance(
-                Some(self.mint_url.clone()),
-                Some(self.unit.clone()),
-                Some(vec![State::Unspent]),
-            )
-            .await?;
-        Ok(Amount::from(balance))
-    }
-
-    /// Total pending balance
-    #[instrument(skip(self))]
-    pub async fn total_pending_balance(&self) -> Result<Amount, Error> {
-        Ok(self.get_pending_proofs().await?.total_amount()?)
-    }
-
-    /// Total reserved balance
-    #[instrument(skip(self))]
-    pub async fn total_reserved_balance(&self) -> Result<Amount, Error> {
-        Ok(self.get_reserved_proofs().await?.total_amount()?)
-    }
-}
+// Balance methods are now implemented via the WalletBalance trait in traits.rs

+ 6 - 95
crates/cdk/src/wallet/issue/mod.rs

@@ -5,112 +5,23 @@
 pub(crate) mod saga;
 
 use cdk_common::nut00::KnownMethod;
-use cdk_common::nut04::MintMethodOptions;
-use cdk_common::nut25::MintQuoteBolt12Request;
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::PaymentMethod;
 pub(crate) use saga::MintSaga;
 use tracing::instrument;
 
 use crate::amount::SplitTarget;
-use crate::nuts::{
-    MintQuoteBolt11Request, MintQuoteCustomRequest, Proofs, SecretKey, SpendingConditions,
-};
+use crate::nuts::{Proofs, SpendingConditions};
 use crate::util::unix_time;
 use crate::wallet::recovery::RecoveryAction;
 use crate::wallet::{MintQuote, MintQuoteState};
 use crate::{Amount, Error, Wallet};
 
 impl Wallet {
-    /// Mint Quote
-    #[instrument(skip(self, method))]
-    pub async fn mint_quote<T>(
-        &self,
-        method: T,
-        amount: Option<Amount>,
-        description: Option<String>,
-        extra: Option<String>,
-    ) -> Result<MintQuote, Error>
-    where
-        T: Into<PaymentMethod>,
-    {
-        let mint_info = self.load_mint_info().await?;
-        let mint_url = self.mint_url.clone();
-        let unit = self.unit.clone();
-
-        let method: PaymentMethod = method.into();
-
-        // Check settings and description support
-        if description.is_some() {
-            let settings = mint_info
-                .nuts
-                .nut04
-                .get_settings(&unit, &method)
-                .ok_or(Error::UnsupportedUnit)?;
-
-            match settings.options {
-                Some(MintMethodOptions::Bolt11 { description }) if description => (),
-                _ => return Err(Error::InvoiceDescriptionUnsupported),
-            }
-        }
-
-        self.refresh_keysets().await?;
-
-        let secret_key = SecretKey::generate();
-
-        let (quote_id, request_str, expiry) = match &method {
-            PaymentMethod::Known(KnownMethod::Bolt11) => {
-                let amount = amount.ok_or(Error::AmountUndefined)?;
-                let request = MintQuoteBolt11Request {
-                    amount,
-                    unit: unit.clone(),
-                    description,
-                    pubkey: Some(secret_key.public_key()),
-                };
-
-                let response = self.client.post_mint_quote(request).await?;
-                (response.quote, response.request, response.expiry)
-            }
-            PaymentMethod::Known(KnownMethod::Bolt12) => {
-                let request = MintQuoteBolt12Request {
-                    amount,
-                    unit: unit.clone(),
-                    description,
-                    pubkey: secret_key.public_key(),
-                };
-
-                let response = self.client.post_mint_bolt12_quote(request).await?;
-                (response.quote, response.request, response.expiry)
-            }
-            PaymentMethod::Custom(_) => {
-                let amount = amount.ok_or(Error::AmountUndefined)?;
-                let request = MintQuoteCustomRequest {
-                    amount,
-                    unit: unit.clone(),
-                    description,
-                    pubkey: Some(secret_key.public_key()),
-                    extra: serde_json::from_str(&extra.unwrap_or_default())?,
-                };
-
-                let response = self.client.post_mint_custom_quote(&method, request).await?;
-                (response.quote, response.request, response.expiry)
-            }
-        };
-
-        let quote = MintQuote::new(
-            quote_id,
-            mint_url,
-            method.clone(),
-            amount,
-            unit,
-            request_str,
-            expiry.unwrap_or(0),
-            Some(secret_key),
-        );
-
-        self.localstore.add_mint_quote(quote.clone()).await?;
-
-        Ok(quote)
-    }
+    // mint_quote() is now implemented via the WalletMint trait in traits.rs
+    // The multi-method version (Bolt11/Bolt12/Custom) has been replaced by the
+    // simplified trait version which is Bolt11-only.
+    // For Bolt12 and Custom methods, use the protocol-specific methods directly.
 
     /// Checks the state of a mint quote with the mint
     async fn check_state(&self, mint_quote: &mut MintQuote) -> Result<(), Error> {

+ 3 - 49
crates/cdk/src/wallet/keysets.rs

@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
 use cdk_common::amount::{FeeAndAmounts, KeysetFeeAndAmounts};
-use cdk_common::nut02::{KeySetInfos, KeySetInfosMethods};
+use cdk_common::nut02::KeySetInfosMethods;
 use tracing::instrument;
 
 use crate::nuts::{Id, KeySetInfo, Keys};
@@ -64,36 +64,7 @@ impl Wallet {
         }
     }
 
-    /// Refresh keysets by fetching the latest from mint - always fetches fresh data
-    ///
-    /// Forces a fresh fetch of keyset information from the mint server,
-    /// updating the metadata cache and database. Use this when you need
-    /// the most up-to-date keyset information.
-    #[instrument(skip(self))]
-    pub async fn refresh_keysets(&self) -> Result<KeySetInfos, Error> {
-        tracing::debug!("Refreshing keysets from mint");
-
-        let keysets = self
-            .metadata_cache
-            .load_from_mint(&self.localstore, &self.client)
-            .await?
-            .keysets
-            .iter()
-            .filter_map(|(_, keyset)| {
-                if keyset.unit == self.unit && keyset.active {
-                    Some((*keyset.clone()).clone())
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-
-        if !keysets.is_empty() {
-            Ok(keysets)
-        } else {
-            Err(Error::UnknownKeySet)
-        }
-    }
+    // refresh_keysets() is now implemented via the WalletMintInfo trait in traits.rs
 
     /// Get the active keyset with the lowest fees - fetches fresh data from mint
     ///
@@ -110,24 +81,7 @@ impl Wallet {
             .ok_or(Error::NoActiveKeyset)
     }
 
-    /// Get the active keyset with the lowest fees from cache
-    ///
-    /// Returns the active keyset with minimum input fees from the metadata cache.
-    /// Uses cached data if available, fetches from mint if cache not populated.
-    #[instrument(skip(self))]
-    pub async fn get_active_keyset(&self) -> Result<KeySetInfo, Error> {
-        self.metadata_cache
-            .load(&self.localstore, &self.client, {
-                let ttl = self.metadata_cache_ttl.read();
-                *ttl
-            })
-            .await?
-            .active_keysets
-            .iter()
-            .min_by_key(|k| k.input_fee_ppk)
-            .map(|ks| (**ks).clone())
-            .ok_or(Error::NoActiveKeyset)
-    }
+    // get_active_keyset() is now implemented via the WalletMintInfo trait in traits.rs
 
     /// Get keyset fees and amounts for all keysets from metadata cache
     ///

+ 1 - 0
crates/cdk/src/wallet/melt/custom.rs

@@ -1,3 +1,4 @@
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::wallet::MeltQuote;
 use cdk_common::PaymentMethod;
 use tracing::instrument;

+ 1 - 0
crates/cdk/src/wallet/melt/saga/mod.rs

@@ -36,6 +36,7 @@ use std::collections::HashMap;
 
 use cdk_common::amount::SplitTarget;
 use cdk_common::dhke::construct_proofs;
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::wallet::{
     MeltOperationData, MeltQuote, MeltSagaState, OperationData, ProofInfo, Transaction,
     TransactionDirection, WalletSaga, WalletSagaState,

+ 8 - 94
crates/cdk/src/wallet/mod.rs

@@ -25,12 +25,11 @@ use crate::mint_url::MintUrl;
 use crate::nuts::nut00::token::Token;
 use crate::nuts::nut17::Kind;
 use crate::nuts::{
-    nut10, CurrencyUnit, Id, Keys, MintInfo, MintQuoteState, PreMintSecrets, Proofs,
-    RestoreRequest, SpendingConditions, State,
+    nut10, CurrencyUnit, Id, Keys, MintQuoteState, PreMintSecrets, Proofs, RestoreRequest,
+    SpendingConditions, State,
 };
-use crate::util::unix_time;
 use crate::wallet::mint_metadata_cache::MintMetadataCache;
-use crate::{Amount, OidcClient};
+use crate::Amount;
 
 mod auth;
 #[cfg(feature = "nostr")]
@@ -59,12 +58,14 @@ mod streams;
 pub mod subscription;
 mod swap;
 pub mod test_utils;
+mod wallet_trait;
 mod transactions;
 pub mod util;
 
 pub use auth::{AuthMintConnector, AuthWallet};
 pub use builder::WalletBuilder;
 pub use cdk_common::wallet as types;
+pub use cdk_common::wallet::Wallet as WalletTrait;
 pub use melt::{MeltConfirmOptions, PreparedMelt};
 pub use mint_connector::http_client::{
     AuthHttpClient as BaseAuthHttpClient, HttpClient as BaseHttpClient,
@@ -169,7 +170,7 @@ impl From<WalletSubscription> for WalletParams {
 }
 
 /// Amount that are recovered during restore operation
-#[derive(Debug, Hash, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
 pub struct Restored {
     /// Amount in the restore that has already been spent
     pub spent: Amount,
@@ -304,95 +305,8 @@ impl Wallet {
         Ok(())
     }
 
-    /// Query mint for current mint information
-    #[instrument(skip(self))]
-    pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, Error> {
-        let mint_info = self
-            .metadata_cache
-            .load_from_mint(&self.localstore, &self.client)
-            .await?
-            .mint_info
-            .clone();
-
-        // If mint provides time make sure it is accurate
-        if let Some(mint_unix_time) = mint_info.time {
-            let current_unix_time = unix_time();
-            if current_unix_time.abs_diff(mint_unix_time) > 30 {
-                tracing::warn!(
-                    "Mint time does match wallet time. Mint: {}, Wallet: {}",
-                    mint_unix_time,
-                    current_unix_time
-                );
-                return Err(Error::MintTimeExceedsTolerance);
-            }
-        }
-
-        // Create or update auth wallet
-        {
-            let mut auth_wallet = self.auth_wallet.write().await;
-            match &*auth_wallet {
-                Some(auth_wallet) => {
-                    let mut protected_endpoints = auth_wallet.protected_endpoints.write().await;
-                    *protected_endpoints = mint_info.protected_endpoints();
-
-                    if let Some(oidc_client) = mint_info
-                        .openid_discovery()
-                        .map(|url| OidcClient::new(url, None))
-                    {
-                        auth_wallet.set_oidc_client(Some(oidc_client)).await;
-                    }
-                }
-                None => {
-                    tracing::info!("Mint has auth enabled creating auth wallet");
-
-                    let oidc_client = mint_info
-                        .openid_discovery()
-                        .map(|url| OidcClient::new(url, None));
-                    let new_auth_wallet = AuthWallet::new(
-                        self.mint_url.clone(),
-                        None,
-                        self.localstore.clone(),
-                        self.metadata_cache.clone(),
-                        mint_info.protected_endpoints(),
-                        oidc_client,
-                    );
-                    *auth_wallet = Some(new_auth_wallet.clone());
-
-                    self.client
-                        .set_auth_wallet(Some(new_auth_wallet.clone()))
-                        .await;
-
-                    if let Err(e) = new_auth_wallet.refresh_keysets().await {
-                        tracing::error!("Could not fetch auth keysets: {}", e);
-                    }
-                }
-            }
-        }
-
-        tracing::trace!("Mint info updated for {}", self.mint_url);
-
-        Ok(Some(mint_info))
-    }
-
-    /// Load mint info from cache
-    ///
-    /// This is a helper function that loads the mint info from the metadata cache
-    /// using the configured TTL. Unlike `fetch_mint_info()`, this does not make
-    /// a network call if the cache is fresh.
-    #[instrument(skip(self))]
-    pub async fn load_mint_info(&self) -> Result<MintInfo, Error> {
-        let mint_info = self
-            .metadata_cache
-            .load(&self.localstore, &self.client, {
-                let ttl = self.metadata_cache_ttl.read();
-                *ttl
-            })
-            .await?
-            .mint_info
-            .clone();
-
-        Ok(mint_info)
-    }
+    // fetch_mint_info() and load_mint_info() are now implemented via the
+    // WalletMintInfo trait in traits.rs
 
     /// Get amounts needed to refill proof state
     #[instrument(skip(self))]

+ 9 - 19
crates/cdk/src/wallet/multi_mint_wallet.rs

@@ -11,6 +11,7 @@ use std::sync::Arc;
 use anyhow::Result;
 use cdk_common::database::WalletDatabase;
 use cdk_common::task::spawn;
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::wallet::{MeltQuote, Transaction, TransactionDirection, TransactionId};
 use cdk_common::{database, KeySetInfo};
 use tokio::sync::RwLock;
@@ -1312,9 +1313,7 @@ impl MultiMintWallet {
         source_balance: Amount,
     ) -> Result<(MintQuote, crate::wallet::types::MeltQuote), Error> {
         // Step 1: Create mint quote at target mint for the exact amount we want to receive
-        let mint_quote = target_wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(amount), None, None)
-            .await?;
+        let mint_quote = target_wallet.mint_quote(amount, None).await?;
 
         // Step 2: Create melt quote at source mint for the invoice
         let melt_quote = source_wallet
@@ -1348,9 +1347,7 @@ impl MultiMintWallet {
 
         // Step 1: Create melt quote for full balance to discover fees
         // We need to create a dummy mint quote first to get an invoice
-        let dummy_mint_quote = target_wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(source_balance), None, None)
-            .await?;
+        let dummy_mint_quote = target_wallet.mint_quote(source_balance, None).await?;
         let probe_melt_quote = source_wallet
             .melt_quote(
                 PaymentMethod::BOLT11,
@@ -1370,9 +1367,7 @@ impl MultiMintWallet {
         }
 
         // Step 3: Create final mint quote for the net amount
-        let final_mint_quote = target_wallet
-            .mint_quote(PaymentMethod::BOLT11, Some(receive_amount), None, None)
-            .await?;
+        let final_mint_quote = target_wallet.mint_quote(receive_amount, None).await?;
 
         // Step 4: Create final melt quote with the new invoice
         let final_melt_quote = source_wallet
@@ -1594,24 +1589,19 @@ impl MultiMintWallet {
     }
 
     /// Mint quote for wallet
-    #[instrument(skip(self, method))]
-    pub async fn mint_quote<T>(
+    #[instrument(skip(self))]
+    pub async fn mint_quote(
         &self,
         mint_url: &MintUrl,
-        method: T,
-        amount: Option<Amount>,
+        amount: Amount,
         description: Option<String>,
-        extra: Option<String>,
-    ) -> Result<MintQuote, Error>
-    where
-        T: Into<PaymentMethod>,
-    {
+    ) -> Result<MintQuote, Error> {
         let wallets = self.wallets.read().await;
         let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
             mint_url: mint_url.to_string(),
         })?;
 
-        wallet.mint_quote(method, amount, description, extra).await
+        WalletTrait::mint_quote(wallet.as_ref(), amount, description).await
     }
 
     /// Refresh a specific mint quote status from the mint.

+ 1 - 0
crates/cdk/src/wallet/receive/saga/mod.rs

@@ -37,6 +37,7 @@ use bitcoin::hashes::sha256::Hash as Sha256Hash;
 use bitcoin::hashes::Hash;
 use bitcoin::XOnlyPublicKey;
 use cdk_common::util::unix_time;
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::wallet::{
     OperationData, ProofInfo, ReceiveOperationData, ReceiveSagaState, Transaction,
     TransactionDirection, WalletSaga, WalletSagaState,

+ 1 - 0
crates/cdk/src/wallet/send/saga/mod.rs

@@ -65,6 +65,7 @@ use std::collections::HashMap;
 
 use cdk_common::nut02::KeySetInfosMethods;
 use cdk_common::util::unix_time;
+use cdk_common::wallet::Wallet as WalletTrait;
 use cdk_common::wallet::{
     OperationData, SendOperationData, SendSagaState, Transaction, TransactionDirection, WalletSaga,
     WalletSagaState,

+ 482 - 0
crates/cdk/src/wallet/wallet_trait.rs

@@ -0,0 +1,482 @@
+//! Wallet trait implementations for [`Wallet`].
+//!
+//! Implements the unified wallet trait defined in `cdk_common::wallet`.
+
+use std::str::FromStr;
+
+use cdk_common::wallet::Wallet as WalletTrait;
+use cdk_common::wallet::{Transaction, TransactionDirection, TransactionId};
+use cdk_common::CurrencyUnit;
+
+use crate::amount::SplitTarget;
+use crate::mint_url::MintUrl;
+use crate::nuts::nut00::ProofsMethods;
+use crate::nuts::{
+    Id, KeySetInfo, MeltOptions, MintInfo, MintQuoteBolt11Request, PaymentMethod, PaymentRequest,
+    Proof, Proofs, SecretKey, SpendingConditions, State, Token,
+};
+use crate::types::FinalizedMelt;
+use crate::util::unix_time;
+use crate::wallet::receive::ReceiveOptions;
+use crate::wallet::send::SendOptions;
+use crate::wallet::subscription::ActiveSubscription;
+use crate::wallet::{MeltQuote, MintQuote, Restored, Wallet};
+use crate::OidcClient;
+use crate::{Amount, Error};
+
+#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
+impl WalletTrait for Wallet {
+    type Amount = Amount;
+    type Proofs = Proofs;
+    type Proof = Proof;
+    type MintQuote = MintQuote;
+    type MeltQuote = MeltQuote;
+    type MeltResult = FinalizedMelt;
+    type Token = Token;
+    type CurrencyUnit = CurrencyUnit;
+    type MintUrl = MintUrl;
+    type MintInfo = MintInfo;
+    type KeySetInfo = KeySetInfo;
+    type Error = Error;
+    type SendOptions = SendOptions;
+    type ReceiveOptions = ReceiveOptions;
+    type SpendingConditions = SpendingConditions;
+    type SplitTarget = SplitTarget;
+    type PaymentMethod = PaymentMethod;
+    type MeltOptions = MeltOptions;
+    type Restored = Restored;
+    type Transaction = Transaction;
+    type TransactionId = TransactionId;
+    type TransactionDirection = TransactionDirection;
+    type PaymentRequest = PaymentRequest;
+    type Subscription = ActiveSubscription;
+    type SubscribeParams = cdk_common::subscription::WalletParams;
+
+    fn mint_url(&self) -> Self::MintUrl {
+        self.mint_url.clone()
+    }
+
+    fn unit(&self) -> Self::CurrencyUnit {
+        self.unit.clone()
+    }
+
+    async fn total_balance(&self) -> Result<Amount, Error> {
+        let balance = self
+            .localstore
+            .get_balance(
+                Some(self.mint_url.clone()),
+                Some(self.unit.clone()),
+                Some(vec![State::Unspent]),
+            )
+            .await?;
+        Ok(Amount::from(balance))
+    }
+
+    async fn total_pending_balance(&self) -> Result<Amount, Error> {
+        Ok(self.get_pending_proofs().await?.total_amount()?)
+    }
+
+    async fn total_reserved_balance(&self) -> Result<Amount, Error> {
+        Ok(self.get_reserved_proofs().await?.total_amount()?)
+    }
+
+    async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, Error> {
+        let mint_info = self
+            .metadata_cache
+            .load_from_mint(&self.localstore, &self.client)
+            .await?
+            .mint_info
+            .clone();
+
+        // If mint provides time make sure it is accurate
+        if let Some(mint_unix_time) = mint_info.time {
+            let current_unix_time = unix_time();
+            if current_unix_time.abs_diff(mint_unix_time) > 30 {
+                tracing::warn!(
+                    "Mint time does match wallet time. Mint: {}, Wallet: {}",
+                    mint_unix_time,
+                    current_unix_time
+                );
+                return Err(Error::MintTimeExceedsTolerance);
+            }
+        }
+
+        // Create or update auth wallet
+        {
+            use crate::wallet::auth::AuthWallet;
+
+            let mut auth_wallet = self.auth_wallet.write().await;
+            match &*auth_wallet {
+                Some(auth_wallet) => {
+                    let mut protected_endpoints = auth_wallet.protected_endpoints.write().await;
+                    *protected_endpoints = mint_info.protected_endpoints();
+
+                    if let Some(oidc_client) = mint_info
+                        .openid_discovery()
+                        .map(|url| OidcClient::new(url, None))
+                    {
+                        auth_wallet.set_oidc_client(Some(oidc_client)).await;
+                    }
+                }
+                None => {
+                    tracing::info!("Mint has auth enabled creating auth wallet");
+
+                    let oidc_client = mint_info
+                        .openid_discovery()
+                        .map(|url| OidcClient::new(url, None));
+                    let new_auth_wallet = AuthWallet::new(
+                        self.mint_url.clone(),
+                        None,
+                        self.localstore.clone(),
+                        self.metadata_cache.clone(),
+                        mint_info.protected_endpoints(),
+                        oidc_client,
+                    );
+                    *auth_wallet = Some(new_auth_wallet.clone());
+
+                    self.client
+                        .set_auth_wallet(Some(new_auth_wallet.clone()))
+                        .await;
+
+                    if let Err(e) = new_auth_wallet.refresh_keysets().await {
+                        tracing::error!("Could not fetch auth keysets: {}", e);
+                    }
+                }
+            }
+        }
+
+        tracing::trace!("Mint info updated for {}", self.mint_url);
+
+        Ok(Some(mint_info))
+    }
+
+    async fn load_mint_info(&self) -> Result<MintInfo, Error> {
+        let mint_info = self
+            .metadata_cache
+            .load(&self.localstore, &self.client, {
+                let ttl = self.metadata_cache_ttl.read();
+                *ttl
+            })
+            .await?
+            .mint_info
+            .clone();
+
+        Ok(mint_info)
+    }
+
+    async fn get_active_keyset(&self) -> Result<KeySetInfo, Error> {
+        self.metadata_cache
+            .load(&self.localstore, &self.client, {
+                let ttl = self.metadata_cache_ttl.read();
+                *ttl
+            })
+            .await?
+            .active_keysets
+            .iter()
+            .min_by_key(|k| k.input_fee_ppk)
+            .map(|ks| (**ks).clone())
+            .ok_or(Error::NoActiveKeyset)
+    }
+
+    async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
+        tracing::debug!("Refreshing keysets from mint");
+
+        let keysets = self
+            .metadata_cache
+            .load_from_mint(&self.localstore, &self.client)
+            .await?
+            .keysets
+            .iter()
+            .filter_map(|(_, keyset)| {
+                if keyset.unit == self.unit && keyset.active {
+                    Some((*keyset.clone()).clone())
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>();
+
+        if !keysets.is_empty() {
+            Ok(keysets)
+        } else {
+            Err(Error::UnknownKeySet)
+        }
+    }
+
+    async fn mint_quote(
+        &self,
+        amount: Amount,
+        description: Option<String>,
+    ) -> Result<MintQuote, Error> {
+        let mint_info = WalletTrait::load_mint_info(self).await?;
+        let mint_url = self.mint_url.clone();
+        let unit = self.unit.clone();
+        let method = PaymentMethod::BOLT11;
+
+        // Check settings and description support
+        if description.is_some() {
+            let settings = mint_info
+                .nuts
+                .nut04
+                .get_settings(&unit, &method)
+                .ok_or(Error::UnsupportedUnit)?;
+
+            match settings.options {
+                Some(cdk_common::nut04::MintMethodOptions::Bolt11 {
+                    description: true, ..
+                }) => (),
+                _ => return Err(Error::InvoiceDescriptionUnsupported),
+            }
+        }
+
+        WalletTrait::refresh_keysets(self).await?;
+
+        let secret_key = SecretKey::generate();
+
+        let request = MintQuoteBolt11Request {
+            amount,
+            unit: unit.clone(),
+            description,
+            pubkey: Some(secret_key.public_key()),
+        };
+
+        let response = self.client.post_mint_quote(request).await?;
+
+        let quote = MintQuote::new(
+            response.quote,
+            mint_url,
+            method,
+            Some(amount),
+            unit,
+            response.request,
+            response.expiry.unwrap_or(0),
+            Some(secret_key),
+        );
+
+        self.localstore.add_mint_quote(quote.clone()).await?;
+
+        Ok(quote)
+    }
+
+    async fn refresh_mint_quote(&self, quote_id: &str) -> Result<MintQuote, Error> {
+        Wallet::refresh_mint_quote_status(self, quote_id).await
+    }
+
+    async fn melt_quote(
+        &self,
+        method: PaymentMethod,
+        request: String,
+        options: Option<MeltOptions>,
+        extra: Option<String>,
+    ) -> Result<MeltQuote, Error> {
+        Wallet::melt_quote::<PaymentMethod, _>(self, method, request, options, extra).await
+    }
+
+    async fn send(&self, amount: Amount, options: SendOptions) -> Result<Token, Error> {
+        let memo = options.memo.clone();
+        let prepared = self.prepare_send(amount, options).await?;
+        prepared.confirm(memo).await
+    }
+
+    async fn get_pending_sends(&self) -> Result<Vec<String>, Error> {
+        let uuids = Wallet::get_pending_sends(self).await?;
+        Ok(uuids.into_iter().map(|id| id.to_string()).collect())
+    }
+
+    async fn revoke_send(&self, operation_id: &str) -> Result<Amount, Error> {
+        let uuid = uuid::Uuid::parse_str(operation_id)
+            .map_err(|e| Error::Custom(format!("Invalid operation ID: {}", e)))?;
+        Wallet::revoke_send(self, uuid).await
+    }
+
+    async fn check_send_status(&self, operation_id: &str) -> Result<bool, Error> {
+        let uuid = uuid::Uuid::parse_str(operation_id)
+            .map_err(|e| Error::Custom(format!("Invalid operation ID: {}", e)))?;
+        Wallet::check_send_status(self, uuid).await
+    }
+
+    async fn receive(
+        &self,
+        encoded_token: &str,
+        options: ReceiveOptions,
+    ) -> Result<Amount, Error> {
+        Wallet::receive(self, encoded_token, options).await
+    }
+
+    async fn receive_proofs(
+        &self,
+        proofs: Proofs,
+        options: ReceiveOptions,
+        memo: Option<String>,
+        token: Option<String>,
+    ) -> Result<Amount, Error> {
+        Wallet::receive_proofs(self, proofs, options, memo, token).await
+    }
+
+    async fn swap(
+        &self,
+        amount: Option<Amount>,
+        amount_split_target: SplitTarget,
+        input_proofs: Proofs,
+        spending_conditions: Option<SpendingConditions>,
+        include_fees: bool,
+    ) -> Result<Option<Proofs>, Error> {
+        Wallet::swap(
+            self,
+            amount,
+            amount_split_target,
+            input_proofs,
+            spending_conditions,
+            include_fees,
+        )
+        .await
+    }
+
+    async fn get_unspent_proofs(&self) -> Result<Proofs, Error> {
+        Wallet::get_unspent_proofs(self).await
+    }
+
+    async fn get_pending_proofs(&self) -> Result<Proofs, Error> {
+        Wallet::get_pending_proofs(self).await
+    }
+
+    async fn get_reserved_proofs(&self) -> Result<Proofs, Error> {
+        Wallet::get_reserved_proofs(self).await
+    }
+
+    async fn get_pending_spent_proofs(&self) -> Result<Proofs, Error> {
+        Wallet::get_pending_spent_proofs(self).await
+    }
+
+    async fn check_all_pending_proofs(&self) -> Result<Amount, Error> {
+        Wallet::check_all_pending_proofs(self).await
+    }
+
+    async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<bool>, Error> {
+        let states = Wallet::check_proofs_spent(self, proofs).await?;
+        Ok(states
+            .into_iter()
+            .map(|ps| matches!(ps.state, State::Spent | State::PendingSpent))
+            .collect())
+    }
+
+    async fn reclaim_unspent(&self, proofs: Proofs) -> Result<(), Error> {
+        let states = Wallet::check_proofs_spent(self, proofs.clone()).await?;
+        let unspent_proofs: Proofs = proofs
+            .into_iter()
+            .zip(states.iter())
+            .filter(|(_, ps)| !matches!(ps.state, State::Spent | State::PendingSpent))
+            .map(|(p, _)| p)
+            .collect();
+
+        if !unspent_proofs.is_empty() {
+            self.swap(None, SplitTarget::default(), unspent_proofs, None, false)
+                .await?;
+        }
+        Ok(())
+    }
+
+    async fn list_transactions(
+        &self,
+        direction: Option<TransactionDirection>,
+    ) -> Result<Vec<Transaction>, Error> {
+        Wallet::list_transactions(self, direction).await
+    }
+
+    async fn get_transaction(
+        &self,
+        id: TransactionId,
+    ) -> Result<Option<Transaction>, Error> {
+        Wallet::get_transaction(self, id).await
+    }
+
+    async fn get_proofs_for_transaction(&self, id: TransactionId) -> Result<Proofs, Error> {
+        Wallet::get_proofs_for_transaction(self, id).await
+    }
+
+    async fn revert_transaction(&self, id: TransactionId) -> Result<(), Error> {
+        Wallet::revert_transaction(self, id).await
+    }
+
+    async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
+        Wallet::verify_token_dleq(self, token).await
+    }
+
+    async fn restore(&self) -> Result<Restored, Error> {
+        Wallet::restore(self).await
+    }
+
+    async fn get_keyset_fees(&self, keyset_id: &str) -> Result<u64, Error> {
+        let id = Id::from_str(keyset_id)?;
+        Ok(self.get_keyset_fees_and_amounts_by_id(id).await?.fee())
+    }
+
+    async fn calculate_fee(&self, proof_count: u64, keyset_id: &str) -> Result<Amount, Error> {
+        let id = Id::from_str(keyset_id)?;
+        Wallet::get_keyset_count_fee(self, &id, proof_count).await
+    }
+
+    async fn subscribe(
+        &self,
+        params: cdk_common::subscription::WalletParams,
+    ) -> Result<ActiveSubscription, Error> {
+        Wallet::subscribe(self, params).await
+    }
+
+    async fn pay_request(
+        &self,
+        request: PaymentRequest,
+        custom_amount: Option<Amount>,
+    ) -> Result<(), Error> {
+        Wallet::pay_request(self, request, custom_amount).await
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_bip353_quote(
+        &self,
+        address: &str,
+        amount: Amount,
+    ) -> Result<MeltQuote, Error> {
+        Wallet::melt_bip353_quote(self, address, amount).await
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_lightning_address_quote(
+        &self,
+        address: &str,
+        amount: Amount,
+    ) -> Result<MeltQuote, Error> {
+        Wallet::melt_lightning_address_quote(self, address, amount).await
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    async fn melt_human_readable_quote(
+        &self,
+        address: &str,
+        amount: Amount,
+    ) -> Result<MeltQuote, Error> {
+        Wallet::melt_human_readable_quote(self, address, amount).await
+    }
+
+    async fn set_cat(&self, cat: String) -> Result<(), Error> {
+        Wallet::set_cat(self, cat).await
+    }
+
+    async fn set_refresh_token(&self, refresh_token: String) -> Result<(), Error> {
+        Wallet::set_refresh_token(self, refresh_token).await
+    }
+
+    async fn refresh_access_token(&self) -> Result<(), Error> {
+        Wallet::refresh_access_token(self).await
+    }
+
+    async fn mint_blind_auth(&self, amount: Amount) -> Result<Proofs, Error> {
+        Wallet::mint_blind_auth(self, amount).await
+    }
+
+    async fn get_unspent_auth_proofs(&self) -> Result<Proofs, Error> {
+        let auth_proofs = Wallet::get_unspent_auth_proofs(self).await?;
+        Ok(auth_proofs.into_iter().map(|ap| ap.into()).collect())
+    }
+}