From: Leonardo Lima Date: Thu, 12 Sep 2024 15:01:36 +0000 (-0300) Subject: chore(examples)!: update all examples to have `example_` prefix X-Git-Tag: v1.0.0-beta.5~8^2~2 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/struct.EncoderStringWriter.html?a=commitdiff_plain;h=519728cd596c3bda60c6e70cbc12b611017e2d7f;p=bdk chore(examples)!: update all examples to have `example_` prefix --- diff --git a/Cargo.toml b/Cargo.toml index a6e6eb6e..2abc16bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,10 @@ members = [ "example-crates/example_electrum", "example-crates/example_esplora", "example-crates/example_bitcoind_rpc_polling", - "example-crates/wallet_electrum", - "example-crates/wallet_esplora_blocking", - "example-crates/wallet_esplora_async", - "example-crates/wallet_rpc", + "example-crates/example_wallet_electrum", + "example-crates/example_wallet_esplora_blocking", + "example-crates/example_wallet_esplora_async", + "example-crates/example_wallet_rpc", ] [workspace.package] diff --git a/example-crates/example_wallet_electrum/Cargo.toml b/example-crates/example_wallet_electrum/Cargo.toml new file mode 100644 index 00000000..07ba60d5 --- /dev/null +++ b/example-crates/example_wallet_electrum/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example_wallet_electrum" +version = "0.2.0" +edition = "2021" + +[dependencies] +bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } +bdk_electrum = { path = "../../crates/electrum" } +anyhow = "1" diff --git a/example-crates/example_wallet_electrum/src/main.rs b/example-crates/example_wallet_electrum/src/main.rs new file mode 100644 index 00000000..f3320d82 --- /dev/null +++ b/example-crates/example_wallet_electrum/src/main.rs @@ -0,0 +1,98 @@ +use bdk_wallet::file_store::Store; +use bdk_wallet::Wallet; +use std::io::Write; + +use bdk_electrum::electrum_client; +use bdk_electrum::BdkElectrumClient; +use bdk_wallet::bitcoin::Amount; +use bdk_wallet::bitcoin::Network; +use bdk_wallet::chain::collections::HashSet; +use bdk_wallet::{KeychainKind, SignOptions}; + +const DB_MAGIC: &str = "bdk_wallet_electrum_example"; +const SEND_AMOUNT: Amount = Amount::from_sat(5000); +const STOP_GAP: usize = 50; +const BATCH_SIZE: usize = 5; + +const NETWORK: Network = Network::Testnet; +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; +const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002"; + +fn main() -> Result<(), anyhow::Error> { + let db_path = "bdk-electrum-example.db"; + + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; + + let wallet_opt = Wallet::load() + .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + .extract_keys() + .check_network(NETWORK) + .load_wallet(&mut db)?; + let mut wallet = match wallet_opt { + Some(wallet) => wallet, + None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + .network(NETWORK) + .create_wallet(&mut db)?, + }; + + let address = wallet.next_unused_address(KeychainKind::External); + wallet.persist(&mut db)?; + println!("Generated Address: {}", address); + + let balance = wallet.balance(); + println!("Wallet balance before syncing: {}", balance.total()); + + print!("Syncing..."); + let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); + + // Populate the electrum client's transaction cache so it doesn't redownload transaction we + // already have. + client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + + let request = wallet.start_full_scan().inspect({ + let mut stdout = std::io::stdout(); + let mut once = HashSet::::new(); + move |k, spk_i, _| { + if once.insert(k) { + print!("\nScanning keychain [{:?}]", k); + } + print!(" {:<3}", spk_i); + stdout.flush().expect("must flush"); + } + }); + + let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?; + + println!(); + + wallet.apply_update(update)?; + wallet.persist(&mut db)?; + + let balance = wallet.balance(); + println!("Wallet balance after syncing: {}", balance.total()); + + if balance.total() < SEND_AMOUNT { + println!( + "Please send at least {} to the receiving address", + SEND_AMOUNT + ); + std::process::exit(0); + } + + let mut tx_builder = wallet.build_tx(); + tx_builder + .add_recipient(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.transaction_broadcast(&tx)?; + println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + + Ok(()) +} diff --git a/example-crates/example_wallet_esplora_async/Cargo.toml b/example-crates/example_wallet_esplora_async/Cargo.toml new file mode 100644 index 00000000..2121b72e --- /dev/null +++ b/example-crates/example_wallet_esplora_async/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_wallet_esplora_async" +version = "0.2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bdk_wallet = { path = "../../crates/wallet", features = ["rusqlite"] } +bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +anyhow = "1" diff --git a/example-crates/example_wallet_esplora_async/src/main.rs b/example-crates/example_wallet_esplora_async/src/main.rs new file mode 100644 index 00000000..4133982c --- /dev/null +++ b/example-crates/example_wallet_esplora_async/src/main.rs @@ -0,0 +1,93 @@ +use std::{collections::BTreeSet, io::Write}; + +use anyhow::Ok; +use bdk_esplora::{esplora_client, EsploraAsyncExt}; +use bdk_wallet::{ + bitcoin::{Amount, Network}, + rusqlite::Connection, + KeychainKind, SignOptions, Wallet, +}; + +const SEND_AMOUNT: Amount = Amount::from_sat(5000); +const STOP_GAP: usize = 5; +const PARALLEL_REQUESTS: usize = 5; + +const DB_PATH: &str = "bdk-example-esplora-async.sqlite"; +const NETWORK: Network = Network::Signet; +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; +const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let mut conn = Connection::open(DB_PATH)?; + + let wallet_opt = Wallet::load() + .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + .extract_keys() + .check_network(NETWORK) + .load_wallet(&mut conn)?; + let mut wallet = match wallet_opt { + Some(wallet) => wallet, + None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + .network(NETWORK) + .create_wallet(&mut conn)?, + }; + + let address = wallet.next_unused_address(KeychainKind::External); + wallet.persist(&mut conn)?; + println!("Next unused address: ({}) {}", address.index, address); + + let balance = wallet.balance(); + println!("Wallet balance before syncing: {}", balance.total()); + + print!("Syncing..."); + let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; + + let request = wallet.start_full_scan().inspect({ + let mut stdout = std::io::stdout(); + let mut once = BTreeSet::::new(); + move |keychain, spk_i, _| { + if once.insert(keychain) { + print!("\nScanning keychain [{:?}]", keychain); + } + print!(" {:<3}", spk_i); + stdout.flush().expect("must flush") + } + }); + + let update = client + .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) + .await?; + + wallet.apply_update(update)?; + wallet.persist(&mut conn)?; + println!(); + + let balance = wallet.balance(); + println!("Wallet balance after syncing: {}", balance.total()); + + if balance.total() < SEND_AMOUNT { + println!( + "Please send at least {} to the receiving address", + SEND_AMOUNT + ); + std::process::exit(0); + } + + let mut tx_builder = wallet.build_tx(); + tx_builder + .add_recipient(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(()) +} diff --git a/example-crates/example_wallet_esplora_blocking/Cargo.toml b/example-crates/example_wallet_esplora_blocking/Cargo.toml new file mode 100644 index 00000000..f47d040d --- /dev/null +++ b/example-crates/example_wallet_esplora_blocking/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_wallet_esplora_blocking" +version = "0.2.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } +bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] } +anyhow = "1" diff --git a/example-crates/example_wallet_esplora_blocking/src/main.rs b/example-crates/example_wallet_esplora_blocking/src/main.rs new file mode 100644 index 00000000..d12dbd92 --- /dev/null +++ b/example-crates/example_wallet_esplora_blocking/src/main.rs @@ -0,0 +1,93 @@ +use std::{collections::BTreeSet, io::Write}; + +use bdk_esplora::{esplora_client, EsploraExt}; +use bdk_wallet::{ + bitcoin::{Amount, Network}, + file_store::Store, + KeychainKind, SignOptions, Wallet, +}; + +const DB_MAGIC: &str = "bdk_wallet_esplora_example"; +const DB_PATH: &str = "bdk-example-esplora-blocking.db"; +const SEND_AMOUNT: Amount = Amount::from_sat(5000); +const STOP_GAP: usize = 5; +const PARALLEL_REQUESTS: usize = 5; + +const NETWORK: Network = Network::Signet; +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; +const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; + +fn main() -> Result<(), anyhow::Error> { + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?; + + let wallet_opt = Wallet::load() + .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + .extract_keys() + .check_network(NETWORK) + .load_wallet(&mut db)?; + let mut wallet = match wallet_opt { + Some(wallet) => wallet, + None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + .network(NETWORK) + .create_wallet(&mut db)?, + }; + + let address = wallet.next_unused_address(KeychainKind::External); + wallet.persist(&mut db)?; + println!( + "Next unused address: ({}) {}", + address.index, address.address + ); + + let balance = wallet.balance(); + println!("Wallet balance before syncing: {}", balance.total()); + + print!("Syncing..."); + let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); + + let request = wallet.start_full_scan().inspect({ + let mut stdout = std::io::stdout(); + let mut once = BTreeSet::::new(); + move |keychain, spk_i, _| { + if once.insert(keychain) { + print!("\nScanning keychain [{:?}] ", keychain); + } + print!(" {:<3}", spk_i); + stdout.flush().expect("must flush") + } + }); + + let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?; + + wallet.apply_update(update)?; + wallet.persist(&mut db)?; + println!(); + + let balance = wallet.balance(); + println!("Wallet balance after syncing: {}", balance.total()); + + if balance.total() < SEND_AMOUNT { + println!( + "Please send at least {} to the receiving address", + SEND_AMOUNT + ); + std::process::exit(0); + } + + let mut tx_builder = wallet.build_tx(); + tx_builder + .add_recipient(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)?; + println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + + Ok(()) +} diff --git a/example-crates/example_wallet_rpc/Cargo.toml b/example-crates/example_wallet_rpc/Cargo.toml new file mode 100644 index 00000000..558f43fe --- /dev/null +++ b/example-crates/example_wallet_rpc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example_wallet_rpc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } +bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" } + +anyhow = "1" +clap = { version = "3.2.25", features = ["derive", "env"] } +ctrlc = "2.0.1" diff --git a/example-crates/example_wallet_rpc/README.md b/example-crates/example_wallet_rpc/README.md new file mode 100644 index 00000000..ea2918aa --- /dev/null +++ b/example-crates/example_wallet_rpc/README.md @@ -0,0 +1,45 @@ +# Wallet RPC Example + +``` +$ cargo run --bin example_wallet_rpc -- --help + +example_wallet_rpc 0.1.0 +Bitcoind RPC example using `bdk_wallet::Wallet` + +USAGE: + example_wallet_rpc [OPTIONS] [CHANGE_DESCRIPTOR] + +ARGS: + Wallet descriptor [env: DESCRIPTOR=] + Wallet change descriptor [env: CHANGE_DESCRIPTOR=] + +OPTIONS: + --db-path + Where to store wallet data [env: BDK_DB_PATH=] [default: .bdk_wallet_rpc_example.db] + + -h, --help + Print help information + + --network + Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: testnet] + + --rpc-cookie + RPC auth cookie file [env: RPC_COOKIE=] + + --rpc-pass + RPC auth password [env: RPC_PASS=] + + --rpc-user + RPC auth username [env: RPC_USER=] + + --start-height + Earliest block height to start sync from [env: START_HEIGHT=] [default: 481824] + + --url + RPC URL [env: RPC_URL=] [default: 127.0.0.1:8332] + + -V, --version + Print version information + +``` + diff --git a/example-crates/example_wallet_rpc/src/main.rs b/example-crates/example_wallet_rpc/src/main.rs new file mode 100644 index 00000000..204224bc --- /dev/null +++ b/example-crates/example_wallet_rpc/src/main.rs @@ -0,0 +1,190 @@ +use bdk_bitcoind_rpc::{ + bitcoincore_rpc::{Auth, Client, RpcApi}, + Emitter, +}; +use bdk_wallet::{ + bitcoin::{Block, Network, Transaction}, + file_store::Store, + KeychainKind, Wallet, +}; +use clap::{self, Parser}; +use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant}; + +const DB_MAGIC: &str = "bdk-rpc-wallet-example"; + +/// Bitcoind RPC example using `bdk_wallet::Wallet`. +/// +/// This syncs the chain block-by-block and prints the current balance, transaction count and UTXO +/// count. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +pub struct Args { + /// Wallet descriptor + #[clap(env = "DESCRIPTOR")] + pub descriptor: String, + /// Wallet change descriptor + #[clap(env = "CHANGE_DESCRIPTOR")] + pub change_descriptor: String, + /// Earliest block height to start sync from + #[clap(env = "START_HEIGHT", long, default_value = "481824")] + pub start_height: u32, + /// Bitcoin network to connect to + #[clap(env = "BITCOIN_NETWORK", long, default_value = "testnet")] + pub network: Network, + /// Where to store wallet data + #[clap( + env = "BDK_DB_PATH", + long, + default_value = ".bdk_wallet_rpc_example.db" + )] + pub db_path: PathBuf, + + /// RPC URL + #[clap(env = "RPC_URL", long, default_value = "127.0.0.1:8332")] + pub url: String, + /// RPC auth cookie file + #[clap(env = "RPC_COOKIE", long)] + pub rpc_cookie: Option, + /// RPC auth username + #[clap(env = "RPC_USER", long)] + pub rpc_user: Option, + /// RPC auth password + #[clap(env = "RPC_PASS", long)] + pub rpc_pass: Option, +} + +impl Args { + fn client(&self) -> anyhow::Result { + Ok(Client::new( + &self.url, + match (&self.rpc_cookie, &self.rpc_user, &self.rpc_pass) { + (None, None, None) => Auth::None, + (Some(path), _, _) => Auth::CookieFile(path.clone()), + (_, Some(user), Some(pass)) => Auth::UserPass(user.clone(), pass.clone()), + (_, Some(_), None) => panic!("rpc auth: missing rpc_pass"), + (_, None, Some(_)) => panic!("rpc auth: missing rpc_user"), + }, + )?) + } +} + +#[derive(Debug)] +enum Emission { + SigTerm, + Block(bdk_bitcoind_rpc::BlockEvent), + Mempool(Vec<(Transaction, u64)>), +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let rpc_client = args.client()?; + println!( + "Connected to Bitcoin Core RPC at {:?}", + rpc_client.get_blockchain_info().unwrap() + ); + + let start_load_wallet = Instant::now(); + let mut db = + Store::::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?; + let wallet_opt = Wallet::load() + .descriptor(KeychainKind::External, Some(args.descriptor.clone())) + .descriptor(KeychainKind::Internal, Some(args.change_descriptor.clone())) + .extract_keys() + .check_network(args.network) + .load_wallet(&mut db)?; + let mut wallet = match wallet_opt { + Some(wallet) => wallet, + None => Wallet::create(args.descriptor, args.change_descriptor) + .network(args.network) + .create_wallet(&mut db)?, + }; + println!( + "Loaded wallet in {}s", + start_load_wallet.elapsed().as_secs_f32() + ); + + let balance = wallet.balance(); + println!("Wallet balance before syncing: {}", balance.total()); + + let wallet_tip = wallet.latest_checkpoint(); + println!( + "Wallet tip: {} at height {}", + wallet_tip.hash(), + wallet_tip.height() + ); + + let (sender, receiver) = sync_channel::(21); + + let signal_sender = sender.clone(); + ctrlc::set_handler(move || { + signal_sender + .send(Emission::SigTerm) + .expect("failed to send sigterm") + }); + + let emitter_tip = wallet_tip.clone(); + spawn(move || -> Result<(), anyhow::Error> { + let mut emitter = Emitter::new(&rpc_client, emitter_tip, args.start_height); + while let Some(emission) = emitter.next_block()? { + sender.send(Emission::Block(emission))?; + } + sender.send(Emission::Mempool(emitter.mempool()?))?; + Ok(()) + }); + + let mut blocks_received = 0_usize; + for emission in receiver { + match emission { + Emission::SigTerm => { + println!("Sigterm received, exiting..."); + break; + } + Emission::Block(block_emission) => { + blocks_received += 1; + let height = block_emission.block_height(); + let hash = block_emission.block_hash(); + let connected_to = block_emission.connected_to(); + let start_apply_block = Instant::now(); + wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; + wallet.persist(&mut db)?; + let elapsed = start_apply_block.elapsed().as_secs_f32(); + println!( + "Applied block {} at height {} in {}s", + hash, height, elapsed + ); + } + Emission::Mempool(mempool_emission) => { + let start_apply_mempool = Instant::now(); + wallet.apply_unconfirmed_txs(mempool_emission); + wallet.persist(&mut db)?; + println!( + "Applied unconfirmed transactions in {}s", + start_apply_mempool.elapsed().as_secs_f32() + ); + break; + } + } + } + let wallet_tip_end = wallet.latest_checkpoint(); + let balance = wallet.balance(); + println!( + "Synced {} blocks in {}s", + blocks_received, + start_load_wallet.elapsed().as_secs_f32(), + ); + println!( + "Wallet tip is '{}:{}'", + wallet_tip_end.height(), + wallet_tip_end.hash() + ); + println!("Wallet balance is {}", balance.total()); + println!( + "Wallet has {} transactions and {} utxos", + wallet.transactions().count(), + wallet.list_unspent().count() + ); + + Ok(()) +} diff --git a/example-crates/wallet_electrum/Cargo.toml b/example-crates/wallet_electrum/Cargo.toml deleted file mode 100644 index 10b662e8..00000000 --- a/example-crates/wallet_electrum/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "wallet_electrum_example" -version = "0.2.0" -edition = "2021" - -[dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } -bdk_electrum = { path = "../../crates/electrum" } -anyhow = "1" diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs deleted file mode 100644 index f3320d82..00000000 --- a/example-crates/wallet_electrum/src/main.rs +++ /dev/null @@ -1,98 +0,0 @@ -use bdk_wallet::file_store::Store; -use bdk_wallet::Wallet; -use std::io::Write; - -use bdk_electrum::electrum_client; -use bdk_electrum::BdkElectrumClient; -use bdk_wallet::bitcoin::Amount; -use bdk_wallet::bitcoin::Network; -use bdk_wallet::chain::collections::HashSet; -use bdk_wallet::{KeychainKind, SignOptions}; - -const DB_MAGIC: &str = "bdk_wallet_electrum_example"; -const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 50; -const BATCH_SIZE: usize = 5; - -const NETWORK: Network = Network::Testnet; -const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; -const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; -const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002"; - -fn main() -> Result<(), anyhow::Error> { - let db_path = "bdk-electrum-example.db"; - - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; - - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut db)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!("Generated Address: {}", address); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - print!("Syncing..."); - let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); - - // Populate the electrum client's transaction cache so it doesn't redownload transaction we - // already have. - client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = HashSet::::new(); - move |k, spk_i, _| { - if once.insert(k) { - print!("\nScanning keychain [{:?}]", k); - } - print!(" {:<3}", spk_i); - stdout.flush().expect("must flush"); - } - }); - - let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?; - - println!(); - - wallet.apply_update(update)?; - wallet.persist(&mut db)?; - - let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); - - if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); - std::process::exit(0); - } - - let mut tx_builder = wallet.build_tx(); - tx_builder - .add_recipient(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.transaction_broadcast(&tx)?; - println!("Tx broadcasted! Txid: {}", tx.compute_txid()); - - Ok(()) -} diff --git a/example-crates/wallet_esplora_async/Cargo.toml b/example-crates/wallet_esplora_async/Cargo.toml deleted file mode 100644 index aa18a5e9..00000000 --- a/example-crates/wallet_esplora_async/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "wallet_esplora_async" -version = "0.2.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["rusqlite"] } -bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] } -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } -anyhow = "1" diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs deleted file mode 100644 index 4133982c..00000000 --- a/example-crates/wallet_esplora_async/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::{collections::BTreeSet, io::Write}; - -use anyhow::Ok; -use bdk_esplora::{esplora_client, EsploraAsyncExt}; -use bdk_wallet::{ - bitcoin::{Amount, Network}, - rusqlite::Connection, - KeychainKind, SignOptions, Wallet, -}; - -const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 5; -const PARALLEL_REQUESTS: usize = 5; - -const DB_PATH: &str = "bdk-example-esplora-async.sqlite"; -const NETWORK: Network = Network::Signet; -const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; -const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; -const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; - -#[tokio::main] -async fn main() -> Result<(), anyhow::Error> { - let mut conn = Connection::open(DB_PATH)?; - - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut conn)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut conn)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut conn)?; - println!("Next unused address: ({}) {}", address.index, address); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - print!("Syncing..."); - let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{:?}]", keychain); - } - print!(" {:<3}", spk_i); - stdout.flush().expect("must flush") - } - }); - - let update = client - .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) - .await?; - - wallet.apply_update(update)?; - wallet.persist(&mut conn)?; - println!(); - - let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); - - if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); - std::process::exit(0); - } - - let mut tx_builder = wallet.build_tx(); - tx_builder - .add_recipient(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(()) -} diff --git a/example-crates/wallet_esplora_blocking/Cargo.toml b/example-crates/wallet_esplora_blocking/Cargo.toml deleted file mode 100644 index 4228c983..00000000 --- a/example-crates/wallet_esplora_blocking/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "wallet_esplora_blocking" -version = "0.2.0" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } -bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] } -anyhow = "1" diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs deleted file mode 100644 index d12dbd92..00000000 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::{collections::BTreeSet, io::Write}; - -use bdk_esplora::{esplora_client, EsploraExt}; -use bdk_wallet::{ - bitcoin::{Amount, Network}, - file_store::Store, - KeychainKind, SignOptions, Wallet, -}; - -const DB_MAGIC: &str = "bdk_wallet_esplora_example"; -const DB_PATH: &str = "bdk-example-esplora-blocking.db"; -const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 5; -const PARALLEL_REQUESTS: usize = 5; - -const NETWORK: Network = Network::Signet; -const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; -const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; -const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; - -fn main() -> Result<(), anyhow::Error> { - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?; - - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut db)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!( - "Next unused address: ({}) {}", - address.index, address.address - ); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - print!("Syncing..."); - let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{:?}] ", keychain); - } - print!(" {:<3}", spk_i); - stdout.flush().expect("must flush") - } - }); - - let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?; - - wallet.apply_update(update)?; - wallet.persist(&mut db)?; - println!(); - - let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); - - if balance.total() < SEND_AMOUNT { - println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT - ); - std::process::exit(0); - } - - let mut tx_builder = wallet.build_tx(); - tx_builder - .add_recipient(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)?; - println!("Tx broadcasted! Txid: {}", tx.compute_txid()); - - Ok(()) -} diff --git a/example-crates/wallet_rpc/Cargo.toml b/example-crates/wallet_rpc/Cargo.toml deleted file mode 100644 index ffda1d3e..00000000 --- a/example-crates/wallet_rpc/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "wallet_rpc" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } -bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" } - -anyhow = "1" -clap = { version = "3.2.25", features = ["derive", "env"] } -ctrlc = "2.0.1" diff --git a/example-crates/wallet_rpc/README.md b/example-crates/wallet_rpc/README.md deleted file mode 100644 index 28eb07b1..00000000 --- a/example-crates/wallet_rpc/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Wallet RPC Example - -``` -$ cargo run --bin wallet_rpc -- --help - -wallet_rpc 0.1.0 -Bitcoind RPC example using `bdk_wallet::Wallet` - -USAGE: - wallet_rpc [OPTIONS] [CHANGE_DESCRIPTOR] - -ARGS: - Wallet descriptor [env: DESCRIPTOR=] - Wallet change descriptor [env: CHANGE_DESCRIPTOR=] - -OPTIONS: - --db-path - Where to store wallet data [env: BDK_DB_PATH=] [default: .bdk_wallet_rpc_example.db] - - -h, --help - Print help information - - --network - Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: testnet] - - --rpc-cookie - RPC auth cookie file [env: RPC_COOKIE=] - - --rpc-pass - RPC auth password [env: RPC_PASS=] - - --rpc-user - RPC auth username [env: RPC_USER=] - - --start-height - Earliest block height to start sync from [env: START_HEIGHT=] [default: 481824] - - --url - RPC URL [env: RPC_URL=] [default: 127.0.0.1:8332] - - -V, --version - Print version information - -``` - diff --git a/example-crates/wallet_rpc/src/main.rs b/example-crates/wallet_rpc/src/main.rs deleted file mode 100644 index 204224bc..00000000 --- a/example-crates/wallet_rpc/src/main.rs +++ /dev/null @@ -1,190 +0,0 @@ -use bdk_bitcoind_rpc::{ - bitcoincore_rpc::{Auth, Client, RpcApi}, - Emitter, -}; -use bdk_wallet::{ - bitcoin::{Block, Network, Transaction}, - file_store::Store, - KeychainKind, Wallet, -}; -use clap::{self, Parser}; -use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant}; - -const DB_MAGIC: &str = "bdk-rpc-wallet-example"; - -/// Bitcoind RPC example using `bdk_wallet::Wallet`. -/// -/// This syncs the chain block-by-block and prints the current balance, transaction count and UTXO -/// count. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -#[clap(propagate_version = true)] -pub struct Args { - /// Wallet descriptor - #[clap(env = "DESCRIPTOR")] - pub descriptor: String, - /// Wallet change descriptor - #[clap(env = "CHANGE_DESCRIPTOR")] - pub change_descriptor: String, - /// Earliest block height to start sync from - #[clap(env = "START_HEIGHT", long, default_value = "481824")] - pub start_height: u32, - /// Bitcoin network to connect to - #[clap(env = "BITCOIN_NETWORK", long, default_value = "testnet")] - pub network: Network, - /// Where to store wallet data - #[clap( - env = "BDK_DB_PATH", - long, - default_value = ".bdk_wallet_rpc_example.db" - )] - pub db_path: PathBuf, - - /// RPC URL - #[clap(env = "RPC_URL", long, default_value = "127.0.0.1:8332")] - pub url: String, - /// RPC auth cookie file - #[clap(env = "RPC_COOKIE", long)] - pub rpc_cookie: Option, - /// RPC auth username - #[clap(env = "RPC_USER", long)] - pub rpc_user: Option, - /// RPC auth password - #[clap(env = "RPC_PASS", long)] - pub rpc_pass: Option, -} - -impl Args { - fn client(&self) -> anyhow::Result { - Ok(Client::new( - &self.url, - match (&self.rpc_cookie, &self.rpc_user, &self.rpc_pass) { - (None, None, None) => Auth::None, - (Some(path), _, _) => Auth::CookieFile(path.clone()), - (_, Some(user), Some(pass)) => Auth::UserPass(user.clone(), pass.clone()), - (_, Some(_), None) => panic!("rpc auth: missing rpc_pass"), - (_, None, Some(_)) => panic!("rpc auth: missing rpc_user"), - }, - )?) - } -} - -#[derive(Debug)] -enum Emission { - SigTerm, - Block(bdk_bitcoind_rpc::BlockEvent), - Mempool(Vec<(Transaction, u64)>), -} - -fn main() -> anyhow::Result<()> { - let args = Args::parse(); - - let rpc_client = args.client()?; - println!( - "Connected to Bitcoin Core RPC at {:?}", - rpc_client.get_blockchain_info().unwrap() - ); - - let start_load_wallet = Instant::now(); - let mut db = - Store::::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?; - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(args.descriptor.clone())) - .descriptor(KeychainKind::Internal, Some(args.change_descriptor.clone())) - .extract_keys() - .check_network(args.network) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(args.descriptor, args.change_descriptor) - .network(args.network) - .create_wallet(&mut db)?, - }; - println!( - "Loaded wallet in {}s", - start_load_wallet.elapsed().as_secs_f32() - ); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - let wallet_tip = wallet.latest_checkpoint(); - println!( - "Wallet tip: {} at height {}", - wallet_tip.hash(), - wallet_tip.height() - ); - - let (sender, receiver) = sync_channel::(21); - - let signal_sender = sender.clone(); - ctrlc::set_handler(move || { - signal_sender - .send(Emission::SigTerm) - .expect("failed to send sigterm") - }); - - let emitter_tip = wallet_tip.clone(); - spawn(move || -> Result<(), anyhow::Error> { - let mut emitter = Emitter::new(&rpc_client, emitter_tip, args.start_height); - while let Some(emission) = emitter.next_block()? { - sender.send(Emission::Block(emission))?; - } - sender.send(Emission::Mempool(emitter.mempool()?))?; - Ok(()) - }); - - let mut blocks_received = 0_usize; - for emission in receiver { - match emission { - Emission::SigTerm => { - println!("Sigterm received, exiting..."); - break; - } - Emission::Block(block_emission) => { - blocks_received += 1; - let height = block_emission.block_height(); - let hash = block_emission.block_hash(); - let connected_to = block_emission.connected_to(); - let start_apply_block = Instant::now(); - wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; - wallet.persist(&mut db)?; - let elapsed = start_apply_block.elapsed().as_secs_f32(); - println!( - "Applied block {} at height {} in {}s", - hash, height, elapsed - ); - } - Emission::Mempool(mempool_emission) => { - let start_apply_mempool = Instant::now(); - wallet.apply_unconfirmed_txs(mempool_emission); - wallet.persist(&mut db)?; - println!( - "Applied unconfirmed transactions in {}s", - start_apply_mempool.elapsed().as_secs_f32() - ); - break; - } - } - } - let wallet_tip_end = wallet.latest_checkpoint(); - let balance = wallet.balance(); - println!( - "Synced {} blocks in {}s", - blocks_received, - start_load_wallet.elapsed().as_secs_f32(), - ); - println!( - "Wallet tip is '{}:{}'", - wallet_tip_end.height(), - wallet_tip_end.hash() - ); - println!("Wallet balance is {}", balance.total()); - println!( - "Wallet has {} transactions and {} utxos", - wallet.transactions().count(), - wallet.list_unspent().count() - ); - - Ok(()) -}