"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]
--- /dev/null
+[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"
--- /dev/null
+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::<bdk_wallet::ChangeSet>::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::<KeychainKind>::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(())
+}
--- /dev/null
+[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"
--- /dev/null
+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::<KeychainKind>::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(())
+}
--- /dev/null
+[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"
--- /dev/null
+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::<bdk_wallet::ChangeSet>::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::<KeychainKind>::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(())
+}
--- /dev/null
+[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"
--- /dev/null
+# 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] <DESCRIPTOR> [CHANGE_DESCRIPTOR]
+
+ARGS:
+ <DESCRIPTOR> Wallet descriptor [env: DESCRIPTOR=]
+ <CHANGE_DESCRIPTOR> Wallet change descriptor [env: CHANGE_DESCRIPTOR=]
+
+OPTIONS:
+ --db-path <DB_PATH>
+ Where to store wallet data [env: BDK_DB_PATH=] [default: .bdk_wallet_rpc_example.db]
+
+ -h, --help
+ Print help information
+
+ --network <NETWORK>
+ Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: testnet]
+
+ --rpc-cookie <RPC_COOKIE>
+ RPC auth cookie file [env: RPC_COOKIE=]
+
+ --rpc-pass <RPC_PASS>
+ RPC auth password [env: RPC_PASS=]
+
+ --rpc-user <RPC_USER>
+ RPC auth username [env: RPC_USER=]
+
+ --start-height <START_HEIGHT>
+ Earliest block height to start sync from [env: START_HEIGHT=] [default: 481824]
+
+ --url <URL>
+ RPC URL [env: RPC_URL=] [default: 127.0.0.1:8332]
+
+ -V, --version
+ Print version information
+
+```
+
--- /dev/null
+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<PathBuf>,
+ /// RPC auth username
+ #[clap(env = "RPC_USER", long)]
+ pub rpc_user: Option<String>,
+ /// RPC auth password
+ #[clap(env = "RPC_PASS", long)]
+ pub rpc_pass: Option<String>,
+}
+
+impl Args {
+ fn client(&self) -> anyhow::Result<Client> {
+ 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<Block>),
+ 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::<bdk_wallet::ChangeSet>::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::<Emission>(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(())
+}
+++ /dev/null
-[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"
+++ /dev/null
-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::<bdk_wallet::ChangeSet>::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::<KeychainKind>::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(())
-}
+++ /dev/null
-[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"
+++ /dev/null
-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::<KeychainKind>::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(())
-}
+++ /dev/null
-[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"
+++ /dev/null
-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::<bdk_wallet::ChangeSet>::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::<KeychainKind>::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(())
-}
+++ /dev/null
-[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"
+++ /dev/null
-# 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] <DESCRIPTOR> [CHANGE_DESCRIPTOR]
-
-ARGS:
- <DESCRIPTOR> Wallet descriptor [env: DESCRIPTOR=]
- <CHANGE_DESCRIPTOR> Wallet change descriptor [env: CHANGE_DESCRIPTOR=]
-
-OPTIONS:
- --db-path <DB_PATH>
- Where to store wallet data [env: BDK_DB_PATH=] [default: .bdk_wallet_rpc_example.db]
-
- -h, --help
- Print help information
-
- --network <NETWORK>
- Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: testnet]
-
- --rpc-cookie <RPC_COOKIE>
- RPC auth cookie file [env: RPC_COOKIE=]
-
- --rpc-pass <RPC_PASS>
- RPC auth password [env: RPC_PASS=]
-
- --rpc-user <RPC_USER>
- RPC auth username [env: RPC_USER=]
-
- --start-height <START_HEIGHT>
- Earliest block height to start sync from [env: START_HEIGHT=] [default: 481824]
-
- --url <URL>
- RPC URL [env: RPC_URL=] [default: 127.0.0.1:8332]
-
- -V, --version
- Print version information
-
-```
-
+++ /dev/null
-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<PathBuf>,
- /// RPC auth username
- #[clap(env = "RPC_USER", long)]
- pub rpc_user: Option<String>,
- /// RPC auth password
- #[clap(env = "RPC_PASS", long)]
- pub rpc_pass: Option<String>,
-}
-
-impl Args {
- fn client(&self) -> anyhow::Result<Client> {
- 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<Block>),
- 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::<bdk_wallet::ChangeSet>::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::<Emission>(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(())
-}