use std::{collections::BTreeSet, fs, io::Write, str::FromStr}; use bdk::keys::bip39::Mnemonic; use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ bitcoin::{Address, Amount, Network, Script}, keys::{DerivableKey, ExtendedKey}, KeychainKind, SignOptions, Wallet, }; use bdk_sqlite::{rusqlite::Connection, Store}; use crate::{keys, utils}; const SEND_AMOUNT: Amount = Amount::from_sat(5000); const STOP_GAP: usize = 50; const PARALLEL_REQUESTS: usize = 5; /// Demonstrates the use of bdk with the Esplora client. pub async fn run() -> Result<(), anyhow::Error> { let dave_dir = utils::home(&"dave".to_string()); let mnemonic_path = crate::utils::side_paths(dave_dir).1; // TODO: this tuple stinks let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); println!("Creating wallet from mnemonic: {mnemonic_words}"); let mnemonic = Mnemonic::parse(mnemonic_words).unwrap(); // Generate the extended key let xkey: ExtendedKey = mnemonic .into_extended_key() .expect("couldn't turn mnemonic into xkey"); let xprv = xkey .into_xprv(Network::Signet) .expect("problem converting xkey to xprv") .to_string(); println!("Setting up esplora database"); let db_path = "/tmp/bdk-esplora-async-example.sqlite"; let conn = Connection::open(db_path)?; let mut db = Store::new(conn)?; let external_descriptor = format!("wpkh({xprv}/84'/1'/0'/0/*)"); let internal_descriptor = format!("wpkh({xprv}/84'/1'/0'/1/*)"); let changeset = db.read().expect("couldn't read esplora database"); let mut wallet = Wallet::new_or_load( &external_descriptor, &internal_descriptor, changeset, Network::Signet, ) .expect("problem setting up wallet"); let address = wallet.next_unused_address(KeychainKind::External); if let Some(changeset) = wallet.take_staged() { db.write(&changeset)?; } println!("Generated Address: {}", address); let balance = wallet.balance(); println!("Wallet balance before syncing: {} sats", balance.total()); print!("Syncing..."); let client = esplora_client::Builder::new("https://mutinynet.com/api") .build_async() .expect("couldn't build esplora client"); fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static { let mut once = Some(()); let mut stdout = std::io::stdout(); move |spk_i, _| { match once.take() { Some(_) => print!("\nScanning keychain [{:?}]", kind), None => print!(" {:<3}", spk_i), }; stdout.flush().expect("must flush"); } } let request = wallet .start_full_scan() .inspect_spks_for_all_keychains({ let mut once = BTreeSet::::new(); move |keychain, spk_i, _| { match once.insert(keychain) { true => print!("\nScanning keychain [{:?}]", keychain), false => print!(" {:<3}", spk_i), } std::io::stdout().flush().expect("must flush") } }) .inspect_spks_for_keychain( KeychainKind::External, generate_inspect(KeychainKind::External), ) .inspect_spks_for_keychain( KeychainKind::Internal, generate_inspect(KeychainKind::Internal), ); let mut update = client .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) .await?; let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); let _ = update.graph_update.update_last_seen_unconfirmed(now); wallet.apply_update(update)?; if let Some(changeset) = wallet.take_staged() { db.write(&changeset)?; } println!(); let balance = wallet.balance(); println!("Wallet balance after syncing: {} sats", balance.total()); if balance.total() < SEND_AMOUNT { println!( "Please send at least {} sats to the receiving address", SEND_AMOUNT ); std::process::exit(0); } let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")? .require_network(Network::Signet)?; let mut tx_builder = wallet.build_tx(); tx_builder .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT) .enable_rbf(); let mut psbt = tx_builder.finish()?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?; assert!(finalized); let tx = psbt.extract_tx()?; client.broadcast(&tx).await?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); Ok(()) }