| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- //! Example: MultiMint Wallet with NpubCash - Switching Active Mints
- //!
- //! This example demonstrates:
- //! 1. Creating a MultiMintWallet with multiple mints
- //! 2. Using NpubCash integration with the MultiMintWallet API
- //! 3. Switching the active mint for NpubCash deposits
- //! 4. Receiving payments to different mints and verifying balances
- //!
- //! Key concept: Since all wallets in a MultiMintWallet share the same seed, they all
- //! derive the same Nostr keypair. This means your npub.cash address stays the same,
- //! but you can change which mint receives the deposits.
- use std::sync::Arc;
- use std::time::Duration;
- use cdk::amount::SplitTarget;
- use cdk::mint_url::MintUrl;
- use cdk::nuts::nut00::ProofsMethods;
- use cdk::nuts::CurrencyUnit;
- use cdk::wallet::WalletRepositoryBuilder;
- use cdk::StreamExt;
- use cdk_sqlite::wallet::memory;
- use nostr_sdk::ToBech32;
- const NPUBCASH_URL: &str = "https://npubx.cash";
- const MINT_URL_1: &str = "https://fake.thesimplekid.dev";
- const MINT_URL_2: &str = "https://testnut.cashu.space";
- const PAYMENT_AMOUNT_MSATS: u64 = 10000; // 10 sats
- #[tokio::main]
- async fn main() -> Result<(), Box<dyn std::error::Error>> {
- println!("=== MultiMint Wallet with NpubCash Example ===\n");
- // -------------------------------------------------------------------------
- // Step 1: Create MultiMintWallet and add mints
- // -------------------------------------------------------------------------
- println!("Step 1: Setting up MultiMintWallet...\n");
- let seed: [u8; 64] = {
- let mut s = [0u8; 64];
- use std::time::{SystemTime, UNIX_EPOCH};
- let timestamp = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .unwrap()
- .as_nanos();
- for (i, byte) in s.iter_mut().enumerate() {
- *byte = ((timestamp >> (i % 16)) & 0xFF) as u8;
- }
- s
- };
- let localstore = memory::empty().await?;
- let wallet_repository = WalletRepositoryBuilder::new()
- .localstore(Arc::new(localstore))
- .seed(seed)
- .build()
- .await?;
- let mint_url_1: MintUrl = MINT_URL_1.parse()?;
- let mint_url_2: MintUrl = MINT_URL_2.parse()?;
- wallet_repository.add_wallet(mint_url_1.clone()).await?;
- wallet_repository.add_wallet(mint_url_2.clone()).await?;
- println!(" Added mints: {}, {}\n", mint_url_1, mint_url_2);
- // -------------------------------------------------------------------------
- // Step 2: Enable NpubCash on mint 1
- // -------------------------------------------------------------------------
- println!("Step 2: Enabling NpubCash on mint 1...\n");
- let wallet = wallet_repository
- .get_wallet(&mint_url_1.clone(), &CurrencyUnit::Sat)
- .await?;
- wallet.enable_npubcash(NPUBCASH_URL.to_string()).await?;
- let keys = wallet.get_npubcash_keys().unwrap();
- let npub = keys.public_key().to_bech32()?;
- let display_url = NPUBCASH_URL.trim_start_matches("https://");
- println!(" Your npub.cash address: {}@{}", npub, display_url);
- println!(" Active mint: {}\n", mint_url_1);
- // -------------------------------------------------------------------------
- // Step 3: Request and receive payment on mint 1
- // -------------------------------------------------------------------------
- println!("Step 3: Receiving payment on mint 1...\n");
- request_invoice(&npub, PAYMENT_AMOUNT_MSATS).await?;
- println!(" Waiting for payment...");
- let mut stream = wallet.npubcash_proof_stream(
- SplitTarget::default(),
- None, // no spending conditions
- Duration::from_secs(1),
- );
- let (_, proofs_1) = stream.next().await.ok_or("Stream ended unexpectedly")??;
- let amount_1: u64 = proofs_1.total_amount()?.into();
- println!(" Received {} sats on mint 1!\n", amount_1);
- // -------------------------------------------------------------------------
- // Step 4: Switch to mint 2 and receive payment
- // -------------------------------------------------------------------------
- println!("Step 4: Switching to mint 2 and receiving payment...\n");
- let wallet = wallet_repository
- .get_wallet(&mint_url_1.clone(), &CurrencyUnit::Sat)
- .await?;
- wallet.enable_npubcash(NPUBCASH_URL.to_string()).await?;
- println!(" Switched to mint: {}", mint_url_2);
- request_invoice(&npub, PAYMENT_AMOUNT_MSATS).await?;
- println!(" Waiting for payment...");
- // The stream is for the multimint wallet so it handles switching mints automatically
- let (_, proofs_2) = stream.next().await.ok_or("Stream ended unexpectedly")??;
- let amount_2: u64 = proofs_2.total_amount()?.into();
- println!(" Received {} sats on mint 2!\n", amount_2);
- // -------------------------------------------------------------------------
- // Step 5: Verify balances
- // -------------------------------------------------------------------------
- println!("Step 5: Verifying balances...\n");
- let balances = wallet_repository.get_balances().await?;
- for (mint, balance) in &balances {
- println!(" {}: {} sats", mint, balance);
- }
- let total = wallet.total_balance().await?;
- let expected_total = amount_1 + amount_2;
- println!(
- "\n Total: {} sats (expected: {} sats)",
- total, expected_total
- );
- println!(
- " Status: {}\n",
- if total == expected_total.into() {
- "OK"
- } else {
- "MISMATCH"
- }
- );
- Ok(())
- }
- /// Request an invoice via LNURL-pay
- async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn std::error::Error>> {
- let http_client = cdk_common::HttpClient::new();
- let lnurlp_url = format!("{}/.well-known/lnurlp/{}", NPUBCASH_URL, npub);
- let lnurlp_response: serde_json::Value = http_client.fetch(&lnurlp_url).await?;
- let callback = lnurlp_response["callback"]
- .as_str()
- .ok_or("No callback URL")?;
- let invoice_url = format!("{}?amount={}", callback, amount_msats);
- let invoice_response: serde_json::Value = http_client.fetch(&invoice_url).await?;
- let pr = invoice_response["pr"]
- .as_str()
- .ok_or("No payment request")?;
- println!(" Invoice: {}...", &pr[..50.min(pr.len())]);
- Ok(())
- }
|