diff --git a/Cargo.toml b/Cargo.toml index 1bb9b983c..4cd9dbaf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ bitcoincore-rpc = { version = "0.16", optional = true } # Platform-specific dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1", features = ["rt"] } +tokio = { version = "1", features = ["rt", "macros"] } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = "0.2" @@ -138,6 +138,21 @@ name = "hardware_signer" path = "examples/hardware_signer.rs" required-features = ["electrum", "hardware-signer"] +[[example]] +name = "electrum_backend" +path = "examples/electrum_backend.rs" +required-features = ["electrum"] + +[[example]] +name = "esplora_backend_synchronous" +path = "examples/esplora_backend_synchronous.rs" +required-features = ["use-esplora-ureq"] + +[[example]] +name = "esplora_backend_asynchronous" +path = "examples/esplora_backend_asynchronous.rs" +required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"] + [workspace] members = ["macros"] [package.metadata.docs.rs] diff --git a/examples/electrum_backend.rs b/examples/electrum_backend.rs new file mode 100644 index 000000000..5259865f3 --- /dev/null +++ b/examples/electrum_backend.rs @@ -0,0 +1,87 @@ +use std::str::FromStr; + +use bdk::bitcoin::util::bip32::ExtendedPrivKey; +use bdk::bitcoin::Network; +use bdk::blockchain::{Blockchain, ElectrumBlockchain}; +use bdk::database::MemoryDatabase; +use bdk::template::Bip84; +use bdk::wallet::export::FullyNodedExport; +use bdk::{KeychainKind, SyncOptions, Wallet}; + +use bdk::electrum_client::Client; +use bdk::wallet::AddressIndex; +use bitcoin::util::bip32; + +pub mod utils; + +use crate::utils::tx::build_signed_tx; + +/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server. +/// If enough amount is available, this will send a transaction to an address. +/// Otherwise, this will display a wallet address to receive funds. +/// +/// This can be run with `cargo run --example electrum_backend` in the root folder. +fn main() { + let network = Network::Testnet; + + let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; + + let electrum_url = "ssl://electrum.blockstream.info:60002"; + + run(&network, electrum_url, xpriv); +} + +fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { + Wallet::new( + Bip84(*xpriv, KeychainKind::External), + Some(Bip84(*xpriv, KeychainKind::Internal)), + *network, + MemoryDatabase::default(), + ) + .unwrap() +} + +fn run(network: &Network, electrum_url: &str, xpriv: &str) { + let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); + + // Apparently it works only with Electrs (not EletrumX) + let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap()); + + let wallet = create_wallet(network, &xpriv); + + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + let address = wallet.get_address(AddressIndex::New).unwrap().address; + + println!("address: {}", address); + + let balance = wallet.get_balance().unwrap(); + + println!("Available coins in BDK wallet : {} sats", balance); + + if balance.confirmed > 6500 { + // the wallet sends the amount to itself. + let recipient_address = wallet + .get_address(AddressIndex::New) + .unwrap() + .address + .to_string(); + + let amount = 5359; + + let tx = build_signed_tx(&wallet, &recipient_address, amount); + + blockchain.broadcast(&tx).unwrap(); + + println!("tx id: {}", tx.txid()); + } else { + println!("Insufficient Funds. Fund the wallet with the address above"); + } + + let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) + .map_err(ToString::to_string) + .map_err(bdk::Error::Generic) + .unwrap(); + + println!("------\nWallet Backup: {}", export.to_string()); +} diff --git a/examples/esplora_backend_asynchronous.rs b/examples/esplora_backend_asynchronous.rs new file mode 100644 index 000000000..4aa149ba3 --- /dev/null +++ b/examples/esplora_backend_asynchronous.rs @@ -0,0 +1,93 @@ +use std::str::FromStr; + +use bdk::blockchain::Blockchain; +use bdk::{ + blockchain::esplora::EsploraBlockchain, + database::MemoryDatabase, + template::Bip84, + wallet::{export::FullyNodedExport, AddressIndex}, + KeychainKind, SyncOptions, Wallet, +}; +use bitcoin::{ + util::bip32::{self, ExtendedPrivKey}, + Network, +}; + +pub mod utils; + +use crate::utils::tx::build_signed_tx; + +/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server, +/// using non blocking asynchronous calls with `reqwest`. +/// If enough amount is available, this will send a transaction to an address. +/// Otherwise, this will display a wallet address to receive funds. +/// +/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous` +/// in the root folder. +#[tokio::main(flavor = "current_thread")] +async fn main() { + let network = Network::Signet; + + let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; + + let esplora_url = "https://explorer.bc-2.jp/api"; + + run(&network, esplora_url, xpriv).await; +} + +fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { + Wallet::new( + Bip84(*xpriv, KeychainKind::External), + Some(Bip84(*xpriv, KeychainKind::Internal)), + *network, + MemoryDatabase::default(), + ) + .unwrap() +} + +async fn run(network: &Network, esplora_url: &str, xpriv: &str) { + let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); + + let blockchain = EsploraBlockchain::new(esplora_url, 20); + + let wallet = create_wallet(network, &xpriv); + + wallet + .sync(&blockchain, SyncOptions::default()) + .await + .unwrap(); + + let address = wallet.get_address(AddressIndex::New).unwrap().address; + + println!("address: {}", address); + + let balance = wallet.get_balance().unwrap(); + + println!("Available coins in BDK wallet : {} sats", balance); + + if balance.confirmed > 10500 { + // the wallet sends the amount to itself. + let recipient_address = wallet + .get_address(AddressIndex::New) + .unwrap() + .address + .to_string(); + + let amount = 9359; + + let tx = build_signed_tx(&wallet, &recipient_address, amount); + + let _ = blockchain.broadcast(&tx); + + println!("tx id: {}", tx.txid()); + } else { + println!("Insufficient Funds. Fund the wallet with the address above"); + } + + let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) + .map_err(ToString::to_string) + .map_err(bdk::Error::Generic) + .unwrap(); + + println!("------\nWallet Backup: {}", export.to_string()); +} diff --git a/examples/esplora_backend_synchronous.rs b/examples/esplora_backend_synchronous.rs new file mode 100644 index 000000000..31907f836 --- /dev/null +++ b/examples/esplora_backend_synchronous.rs @@ -0,0 +1,89 @@ +use std::str::FromStr; + +use bdk::blockchain::Blockchain; +use bdk::{ + blockchain::esplora::EsploraBlockchain, + database::MemoryDatabase, + template::Bip84, + wallet::{export::FullyNodedExport, AddressIndex}, + KeychainKind, SyncOptions, Wallet, +}; +use bitcoin::{ + util::bip32::{self, ExtendedPrivKey}, + Network, +}; + +pub mod utils; + +use crate::utils::tx::build_signed_tx; + +/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server, +/// using blocking calls with `ureq`. +/// If enough amount is available, this will send a transaction to an address. +/// Otherwise, this will display a wallet address to receive funds. +/// +/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous` +/// in the root folder. +fn main() { + let network = Network::Signet; + + let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; + + let esplora_url = "https://explorer.bc-2.jp/api"; + + run(&network, esplora_url, xpriv); +} + +fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { + Wallet::new( + Bip84(*xpriv, KeychainKind::External), + Some(Bip84(*xpriv, KeychainKind::Internal)), + *network, + MemoryDatabase::default(), + ) + .unwrap() +} + +fn run(network: &Network, esplora_url: &str, xpriv: &str) { + let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); + + let blockchain = EsploraBlockchain::new(esplora_url, 20); + + let wallet = create_wallet(network, &xpriv); + + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + let address = wallet.get_address(AddressIndex::New).unwrap().address; + + println!("address: {}", address); + + let balance = wallet.get_balance().unwrap(); + + println!("Available coins in BDK wallet : {} sats", balance); + + if balance.confirmed > 10500 { + // the wallet sends the amount to itself. + let recipient_address = wallet + .get_address(AddressIndex::New) + .unwrap() + .address + .to_string(); + + let amount = 9359; + + let tx = build_signed_tx(&wallet, &recipient_address, amount); + + blockchain.broadcast(&tx).unwrap(); + + println!("tx id: {}", tx.txid()); + } else { + println!("Insufficient Funds. Fund the wallet with the address above"); + } + + let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) + .map_err(ToString::to_string) + .map_err(bdk::Error::Generic) + .unwrap(); + + println!("------\nWallet Backup: {}", export.to_string()); +} diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs new file mode 100644 index 000000000..25249fa7e --- /dev/null +++ b/examples/utils/mod.rs @@ -0,0 +1,30 @@ +pub(crate) mod tx { + + use std::str::FromStr; + + use bdk::{database::BatchDatabase, SignOptions, Wallet}; + use bitcoin::{Address, Transaction}; + + pub fn build_signed_tx( + wallet: &Wallet, + recipient_address: &str, + amount: u64, + ) -> Transaction { + // Create a transaction builder + let mut tx_builder = wallet.build_tx(); + + let to_address = Address::from_str(recipient_address).unwrap(); + + // Set recipient of the transaction + tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]); + + // Finalise the transaction and extract PSBT + let (mut psbt, _) = tx_builder.finish().unwrap(); + + // Sign the above psbt with signing option + wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + + // Extract the final transaction + psbt.extract_tx() + } +}