Two major breaking changes:
MultiMintWallet removed → Use WalletRepository to get individual Wallet instancesconfirm() returns MeltOutcome (Paid or Pending), with async supportKey Changes:
wallet.mint_quote(&mint_url, ...) → wallet.get_wallet(&mint_url, &unit)?.mint_quote(...)prepared_melt.confirm().await? → match prepared_melt.confirm().await? { MeltOutcome::Paid(_) => ..., MeltOutcome::Pending(_) => ... }| Operation | Before | After |
|---|---|---|
| Create | MultiMintWallet::new(localstore, seed, unit) |
WalletRepositoryBuilder::new().localstore(localstore).seed(seed).build() |
| Add Mint | wallet.add_mint(mint_url) |
wallet.add_wallet(mint_url) |
| Get Wallet | N/A (methods took mint_url) | wallet.get_wallet(&mint_url, &unit) |
| Mint Quote | wallet.mint_quote(&mint_url, method, amount, ...) |
wallet.get_wallet(&mint_url, &unit)?.mint_quote(method, amount, ...) |
| Balances | HashMap<MintUrl, Amount> |
BTreeMap<WalletKey, Amount> |
| Total | Amount |
BTreeMap<CurrencyUnit, Amount> |
// Before
let wallet = MultiMintWallet::new(localstore, seed, CurrencyUnit::Sat).await?;
// After
let wallet = WalletRepositoryBuilder::new()
.localstore(localstore)
.seed(seed)
.build()
.await?;
// Before
wallet.add_mint(mint_url.clone()).await?;
// After - creates wallets for ALL supported units
wallet.add_wallet(mint_url.clone()).await?;
// Or create specific wallet with config
let config = WalletConfig::new()
.with_target_proof_count(5)
.with_metadata_cache_ttl(Some(Duration::from_secs(3600)));
let mint_wallet = wallet
.create_wallet(mint_url.clone(), CurrencyUnit::Sat, Some(config))
.await?;
All methods that previously took mint_url now require getting the wallet first:
// Before
let quote = wallet
.mint_quote(&mint_url, PaymentMethod::BOLT11, Some(amount), None, None)
.await?;
// After
let mint_wallet = wallet.get_wallet(&mint_url, &CurrencyUnit::Sat).await?;
let quote = mint_wallet
.mint_quote(PaymentMethod::BOLT11, Some(amount), None, None)
.await?;
This pattern applies to all wallet operations:
mint_quote() → get wallet → mint_wallet.mint_quote()wait_for_mint_quote() → get wallet → mint_wallet.wait_and_mint_quote()melt_quote() → get wallet → mint_wallet.melt_quote()prepare_melt() → get wallet → mint_wallet.prepare_melt()prepare_send() → get wallet → mint_wallet.prepare_send()receive() → get wallet → mint_wallet.receive()swap() → get wallet → mint_wallet.swap()restore() → get wallet → mint_wallet.restore()mint_tokens() → get wallet → mint_wallet.mint() (renamed)Note: wait_and_mint_quote now takes the full MintQuote instead of just the quote ID:
// Before
let proofs = wallet
.wait_for_mint_quote(&mint_url, "e_id, split_target, conditions, timeout)
.await?;
// After - pass the full MintQuote
let proofs = mint_wallet
.wait_and_mint_quote(quote, split_target, conditions, timeout)
.await?;
// Before - single amount
let total = wallet.total_balance().await?;
// After - by currency unit
let balances = wallet.total_balance().await?;
let sat_total = balances
.get(&CurrencyUnit::Sat)
.copied()
.unwrap_or(Amount::ZERO);
// Before - HashMap<MintUrl, Amount>
let balances: HashMap<MintUrl, Amount> = wallet.get_balances().await?;
for (mint_url, balance) in balances {
println!("{}: {}", mint_url, balance);
}
// After - BTreeMap<WalletKey, Amount> (includes unit)
let balances: BTreeMap<WalletKey, Amount> = wallet.get_balances().await?;
for (key, balance) in balances {
println!("{} ({}): {}", key.mint_url, key.unit, balance);
}
// Before
let received = wallet.receive(&token_str, MultiMintReceiveOptions::default()).await?;
// After
let token = Token::from_str(&token_str)?;
let token_data = wallet.get_token_data(&token).await?;
let mint_wallet = wallet
.get_wallet(&token_data.mint_url, &token_data.unit)
.await?;
let received = mint_wallet.receive(&token_str, ReceiveOptions::default()).await?;
All methods moved to Wallet (get wallet first, then call):
| Method | Change |
|---|---|
mint_quote(mint_url, ...) |
→ Wallet::mint_quote(...) |
wait_for_mint_quote(mint_url, ...) |
→ Wallet::wait_and_mint_quote(...) |
melt_quote(mint_url, ...) |
→ Wallet::melt_quote(...) |
prepare_melt(mint_url, ...) |
→ Wallet::prepare_melt(...) |
prepare_send(mint_url, ...) |
→ Wallet::prepare_send(...) |
receive(...) |
→ Wallet::receive(...) |
restore(mint_url) |
→ Wallet::restore() |
swap(mint_url, ...) |
→ Wallet::swap(...) |
mint_tokens(mint_url, ...) |
→ Wallet::mint(...) |
melt_quote(mint_url, ...) |
→ Wallet::melt_quote(PaymentMethod, bolt11_invoice, ...) |
add_mint(mint_url) |
→ WalletRepository::add_wallet(mint_url) |
add_mint_with_config(...) |
→ WalletRepository::add_wallet_with_config(...) |
impl WalletRepository {
// Get specific wallet
async fn get_wallet(&self, mint_url: &MintUrl, unit: &CurrencyUnit) -> Result<Wallet>;
// Get all wallets for a mint
async fn get_wallets_for_mint(&self, mint_url: &MintUrl) -> Vec<Wallet>;
// Check existence
async fn has_wallet(&self, mint_url: &MintUrl, unit: &CurrencyUnit) -> bool;
async fn has_mint(&self, mint_url: &MintUrl) -> bool;
// Create with config
async fn create_wallet(&self, mint_url: MintUrl, unit: CurrencyUnit, config: Option<WalletConfig>) -> Result<Wallet>;
async fn set_mint_config(&self, mint_url: MintUrl, unit: CurrencyUnit, config: WalletConfig) -> Result<Wallet>;
// Remove
async fn remove_wallet(&self, mint_url: MintUrl, unit: CurrencyUnit) -> Result<()>;
// Helpers
async fn get_token_data(&self, token: &Token) -> Result<TokenData>;
async fn check_all_mint_quotes(&self, mint_url: Option<MintUrl>) -> Result<Amount>;
}
| Old | New |
|---|---|
MultiMintWallet |
WalletRepository |
MultiMintReceiveOptions |
ReceiveOptions |
HashMap<MintUrl, Amount> (balances) |
BTreeMap<WalletKey, Amount> |
Amount (total_balance) |
BTreeMap<CurrencyUnit, Amount> |
Melt operations now use the saga pattern with type-state safety and support for async completion. The API requires calling melt_quote first to create a quote, then prepare_melt with the quote ID.
// Before: prepare_melt returned a result that completed immediately
let prepared = wallet.prepare_melt("e_id, HashMap::new()).await?;
let result = prepared.confirm().await?;
// After: Two-step process - melt_quote then prepare_melt
use cdk::nuts::PaymentMethod;
use cdk::wallet::MeltOutcome;
// Step 1: Create a melt quote
let melt_quote = wallet
.melt_quote(PaymentMethod::BOLT11, invoice, None, None)
.await?;
// Step 2: Prepare the melt with the quote ID
let prepared = wallet
.prepare_melt(&melt_quote.id, std::collections::HashMap::new())
.await?;
// Step 3: Confirm and handle the outcome
match prepared.confirm().await? {
MeltOutcome::Paid(melted) => {
println!("Paid! Amount: {}, Fee: {}", melted.amount(), melted.fee_paid());
}
MeltOutcome::Pending(pending) => {
// Melt is processing asynchronously
// Can await immediately or drop for background completion
let melted = pending.await?;
println!("Completed! Amount: {}, Fee: {}", melted.amount(), melted.fee_paid());
}
}
// Option: Prefer async (returns Pending immediately if supported)
let outcome = prepared.confirm_prefer_async().await?;
// Handle same as above
impl PreparedMelt {
/// Confirm the melt (waits for immediate completion or returns Pending)
async fn confirm(self) -> Result<MeltOutcome, Error>;
/// Confirm with options (e.g., skip_swap)
async fn confirm_with_options(self, options: MeltConfirmOptions) -> Result<MeltOutcome, Error>;
/// Prefer async completion
async fn confirm_prefer_async(self) -> Result<MeltOutcome, Error>;
/// Cancel the prepared melt (releases reserved proofs)
async fn cancel(self) -> Result<(), Error>;
}
let options = MeltConfirmOptions::default()
.skip_swap(true); // Skip swap before melt if needed
let outcome = prepared.confirm_with_options(options).await?;
use cdk::wallet::MeltOutcome;
// Immediate await
if let MeltOutcome::Pending(pending) = outcome {
let melted = pending.await?;
}
// Or drop for background completion via WebSocket or finalize_pending_melts()
drop(pending); // Will complete via notifications
// Later: recover pending melts after restart
wallet.finalize_pending_melts().await?;
// Recover incomplete sagas on startup
wallet.recover_incomplete_sagas().await?;
FFI bindings follow the same pattern:
// Before
let wallet = MultiMintWallet(mnemonic: mnemonic, database: db)
let quote = try await wallet.mintQuote(mintUrl: mintUrl, ...)
// After
let wallet = WalletRepository(mnemonic: mnemonic, database: db)
try await wallet.addMint(mintUrl: mintUrl, unit: .sat, targetProofCount: 3)
let mintWallet = try await wallet.getWallet(mintUrl: mintUrl, unit: .sat)
let quote = try await mintWallet.mintQuote(...)
Same pattern applies to Kotlin and Python - MultiMintWallet → WalletRepository, get Wallet first, then call methods.
mint_quote found for struct WalletRepository"Cause: Calling wallet method on repository instead of getting wallet first.
Fix:
let mint_wallet = wallet.get_wallet(&mint_url, &CurrencyUnit::Sat).await?;
let quote = mint_wallet.mint_quote(...).await?;
Amount, found struct BTreeMap"Cause: total_balance() now returns map.
Fix:
let balances = wallet.total_balance().await?;
let total = balances.get(&CurrencyUnit::Sat).copied().unwrap_or(Amount::ZERO);
MultiMintWallet"Fix: Update imports:
use cdk::wallet::{WalletRepository, WalletRepositoryBuilder};