# Migration Guide: v0.14.x to v0.15.0

## Summary

**Two major breaking changes:**

1. **`MultiMintWallet` removed** → Use `WalletRepository` to get individual `Wallet` instances
2. **Melt saga pattern** → `confirm()` returns `MeltOutcome` (Paid or Pending), with async support

**Key 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(_) => ... }`

## Quick Reference

| 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>` |

## Detailed Changes

### 1. Construction

```rust
// Before
let wallet = MultiMintWallet::new(localstore, seed, CurrencyUnit::Sat).await?;

// After
let wallet = WalletRepositoryBuilder::new()
    .localstore(localstore)
    .seed(seed)
    .build()
    .await?;
```

### 2. Adding Mints

```rust
// 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?;
```

### 3. Method Calls (Get Wallet First)

All methods that previously took `mint_url` now require getting the wallet first:

```rust
// 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)

### 4. wait_and_mint_quote Signature Change

Note: `wait_and_mint_quote` now takes the full `MintQuote` instead of just the quote ID:

```rust
// Before
let proofs = wallet
    .wait_for_mint_quote(&mint_url, &quote_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?;
```

### 5. Balance Handling

```rust
// 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);
```

```rust
// 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);
}
```

### 5. Receiving Tokens

```rust
// 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?;
```

## API Changes

### Removed from MultiMintWallet

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(...)` |

### New WalletRepository Methods

```rust
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>;
}
```

### Type Changes

| Old | New |
|-----|-----|
| `MultiMintWallet` | `WalletRepository` |
| `MultiMintReceiveOptions` | `ReceiveOptions` |
| `HashMap<MintUrl, Amount>` (balances) | `BTreeMap<WalletKey, Amount>` |
| `Amount` (total_balance) | `BTreeMap<CurrencyUnit, Amount>` |

## Melt Saga Changes

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.

### Basic Melt Flow

```rust
// Before: prepare_melt returned a result that completed immediately
let prepared = wallet.prepare_melt(&quote_id, HashMap::new()).await?;
let result = prepared.confirm().await?;
```

```rust
// 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
```

### New PreparedMelt Methods

```rust
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>;
}
```

### MeltConfirmOptions

```rust
let options = MeltConfirmOptions::default()
    .skip_swap(true); // Skip swap before melt if needed

let outcome = prepared.confirm_with_options(options).await?;
```

### Handling Pending Melts

```rust
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?;
```

### Recovery

```rust
// Recover incomplete sagas on startup
wallet.recover_incomplete_sagas().await?;
```

## FFI Changes

FFI bindings follow the same pattern:

```swift
// 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.

## Troubleshooting

### "no method named `mint_quote` found for struct `WalletRepository`"

**Cause:** Calling wallet method on repository instead of getting wallet first.

**Fix:**
```rust
let mint_wallet = wallet.get_wallet(&mint_url, &CurrencyUnit::Sat).await?;
let quote = mint_wallet.mint_quote(...).await?;
```

### "expected struct `Amount`, found struct `BTreeMap`"

**Cause:** `total_balance()` now returns map.

**Fix:**
```rust
let balances = wallet.total_balance().await?;
let total = balances.get(&CurrencyUnit::Sat).copied().unwrap_or(Amount::ZERO);
```

### "cannot find type `MultiMintWallet`"

**Fix:** Update imports:
```rust
use cdk::wallet::{WalletRepository, WalletRepositoryBuilder};
```

## References

- [Changelog](../../CHANGELOG.md#0150)
- [Issue #1577](https://github.com/cashubtc/cdk/issues/1577)
