Skip to content

Commit 1c95ca3

Browse files
committed
Merge #526: Add code example for each supported backend
f99a6b9 add `esplora_backend` example. (w0xlt) aedbc8c add `electrum_backend` example. (w0xlt) Pull request description: This PR adds code example for connecting to Esplora, Electrum Server, Neutrino and Bitcoin Core. Also shows how to retrieve balance, sign and broadcast transactions. To test: ``` cd examples/backend/ cargo run electrum cargo run esplora cargo run neutrino cargo run rpc_core ``` ACKs for top commit: rajarshimaitra: tACK f99a6b9 Tree-SHA512: 1d99129f14d83d9a833cee1587fe0eb3e5da4c83ae9008fb3e510be96a874dc86f800f203f68f5da70648a911709107cf0f286c2623808dc97dd63b7addef16b
2 parents 108edc3 + f99a6b9 commit 1c95ca3

5 files changed

+315
-1
lines changed

Cargo.toml

+16-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ bitcoincore-rpc = { version = "0.16", optional = true }
4141

4242
# Platform-specific dependencies
4343
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
44-
tokio = { version = "1", features = ["rt"] }
44+
tokio = { version = "1", features = ["rt", "macros"] }
4545

4646
[target.'cfg(target_arch = "wasm32")'.dependencies]
4747
getrandom = "0.2"
@@ -138,6 +138,21 @@ name = "hardware_signer"
138138
path = "examples/hardware_signer.rs"
139139
required-features = ["electrum", "hardware-signer"]
140140

141+
[[example]]
142+
name = "electrum_backend"
143+
path = "examples/electrum_backend.rs"
144+
required-features = ["electrum"]
145+
146+
[[example]]
147+
name = "esplora_backend_synchronous"
148+
path = "examples/esplora_backend_synchronous.rs"
149+
required-features = ["use-esplora-ureq"]
150+
151+
[[example]]
152+
name = "esplora_backend_asynchronous"
153+
path = "examples/esplora_backend_asynchronous.rs"
154+
required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
155+
141156
[workspace]
142157
members = ["macros"]
143158
[package.metadata.docs.rs]

examples/electrum_backend.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::str::FromStr;
2+
3+
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
4+
use bdk::bitcoin::Network;
5+
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
6+
use bdk::database::MemoryDatabase;
7+
use bdk::template::Bip84;
8+
use bdk::wallet::export::FullyNodedExport;
9+
use bdk::{KeychainKind, SyncOptions, Wallet};
10+
11+
use bdk::electrum_client::Client;
12+
use bdk::wallet::AddressIndex;
13+
use bitcoin::util::bip32;
14+
15+
pub mod utils;
16+
17+
use crate::utils::tx::build_signed_tx;
18+
19+
/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
20+
/// If enough amount is available, this will send a transaction to an address.
21+
/// Otherwise, this will display a wallet address to receive funds.
22+
///
23+
/// This can be run with `cargo run --example electrum_backend` in the root folder.
24+
fn main() {
25+
let network = Network::Testnet;
26+
27+
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
28+
29+
let electrum_url = "ssl://electrum.blockstream.info:60002";
30+
31+
run(&network, electrum_url, xpriv);
32+
}
33+
34+
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
35+
Wallet::new(
36+
Bip84(*xpriv, KeychainKind::External),
37+
Some(Bip84(*xpriv, KeychainKind::Internal)),
38+
*network,
39+
MemoryDatabase::default(),
40+
)
41+
.unwrap()
42+
}
43+
44+
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
45+
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
46+
47+
// Apparently it works only with Electrs (not EletrumX)
48+
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
49+
50+
let wallet = create_wallet(network, &xpriv);
51+
52+
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
53+
54+
let address = wallet.get_address(AddressIndex::New).unwrap().address;
55+
56+
println!("address: {}", address);
57+
58+
let balance = wallet.get_balance().unwrap();
59+
60+
println!("Available coins in BDK wallet : {} sats", balance);
61+
62+
if balance.confirmed > 6500 {
63+
// the wallet sends the amount to itself.
64+
let recipient_address = wallet
65+
.get_address(AddressIndex::New)
66+
.unwrap()
67+
.address
68+
.to_string();
69+
70+
let amount = 5359;
71+
72+
let tx = build_signed_tx(&wallet, &recipient_address, amount);
73+
74+
blockchain.broadcast(&tx).unwrap();
75+
76+
println!("tx id: {}", tx.txid());
77+
} else {
78+
println!("Insufficient Funds. Fund the wallet with the address above");
79+
}
80+
81+
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
82+
.map_err(ToString::to_string)
83+
.map_err(bdk::Error::Generic)
84+
.unwrap();
85+
86+
println!("------\nWallet Backup: {}", export.to_string());
87+
}
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::str::FromStr;
2+
3+
use bdk::blockchain::Blockchain;
4+
use bdk::{
5+
blockchain::esplora::EsploraBlockchain,
6+
database::MemoryDatabase,
7+
template::Bip84,
8+
wallet::{export::FullyNodedExport, AddressIndex},
9+
KeychainKind, SyncOptions, Wallet,
10+
};
11+
use bitcoin::{
12+
util::bip32::{self, ExtendedPrivKey},
13+
Network,
14+
};
15+
16+
pub mod utils;
17+
18+
use crate::utils::tx::build_signed_tx;
19+
20+
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
21+
/// using non blocking asynchronous calls with `reqwest`.
22+
/// If enough amount is available, this will send a transaction to an address.
23+
/// Otherwise, this will display a wallet address to receive funds.
24+
///
25+
/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous`
26+
/// in the root folder.
27+
#[tokio::main(flavor = "current_thread")]
28+
async fn main() {
29+
let network = Network::Signet;
30+
31+
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
32+
33+
let esplora_url = "https://explorer.bc-2.jp/api";
34+
35+
run(&network, esplora_url, xpriv).await;
36+
}
37+
38+
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
39+
Wallet::new(
40+
Bip84(*xpriv, KeychainKind::External),
41+
Some(Bip84(*xpriv, KeychainKind::Internal)),
42+
*network,
43+
MemoryDatabase::default(),
44+
)
45+
.unwrap()
46+
}
47+
48+
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
49+
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
50+
51+
let blockchain = EsploraBlockchain::new(esplora_url, 20);
52+
53+
let wallet = create_wallet(network, &xpriv);
54+
55+
wallet
56+
.sync(&blockchain, SyncOptions::default())
57+
.await
58+
.unwrap();
59+
60+
let address = wallet.get_address(AddressIndex::New).unwrap().address;
61+
62+
println!("address: {}", address);
63+
64+
let balance = wallet.get_balance().unwrap();
65+
66+
println!("Available coins in BDK wallet : {} sats", balance);
67+
68+
if balance.confirmed > 10500 {
69+
// the wallet sends the amount to itself.
70+
let recipient_address = wallet
71+
.get_address(AddressIndex::New)
72+
.unwrap()
73+
.address
74+
.to_string();
75+
76+
let amount = 9359;
77+
78+
let tx = build_signed_tx(&wallet, &recipient_address, amount);
79+
80+
let _ = blockchain.broadcast(&tx);
81+
82+
println!("tx id: {}", tx.txid());
83+
} else {
84+
println!("Insufficient Funds. Fund the wallet with the address above");
85+
}
86+
87+
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
88+
.map_err(ToString::to_string)
89+
.map_err(bdk::Error::Generic)
90+
.unwrap();
91+
92+
println!("------\nWallet Backup: {}", export.to_string());
93+
}
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::str::FromStr;
2+
3+
use bdk::blockchain::Blockchain;
4+
use bdk::{
5+
blockchain::esplora::EsploraBlockchain,
6+
database::MemoryDatabase,
7+
template::Bip84,
8+
wallet::{export::FullyNodedExport, AddressIndex},
9+
KeychainKind, SyncOptions, Wallet,
10+
};
11+
use bitcoin::{
12+
util::bip32::{self, ExtendedPrivKey},
13+
Network,
14+
};
15+
16+
pub mod utils;
17+
18+
use crate::utils::tx::build_signed_tx;
19+
20+
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
21+
/// using blocking calls with `ureq`.
22+
/// If enough amount is available, this will send a transaction to an address.
23+
/// Otherwise, this will display a wallet address to receive funds.
24+
///
25+
/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous`
26+
/// in the root folder.
27+
fn main() {
28+
let network = Network::Signet;
29+
30+
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
31+
32+
let esplora_url = "https://explorer.bc-2.jp/api";
33+
34+
run(&network, esplora_url, xpriv);
35+
}
36+
37+
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
38+
Wallet::new(
39+
Bip84(*xpriv, KeychainKind::External),
40+
Some(Bip84(*xpriv, KeychainKind::Internal)),
41+
*network,
42+
MemoryDatabase::default(),
43+
)
44+
.unwrap()
45+
}
46+
47+
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
48+
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
49+
50+
let blockchain = EsploraBlockchain::new(esplora_url, 20);
51+
52+
let wallet = create_wallet(network, &xpriv);
53+
54+
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
55+
56+
let address = wallet.get_address(AddressIndex::New).unwrap().address;
57+
58+
println!("address: {}", address);
59+
60+
let balance = wallet.get_balance().unwrap();
61+
62+
println!("Available coins in BDK wallet : {} sats", balance);
63+
64+
if balance.confirmed > 10500 {
65+
// the wallet sends the amount to itself.
66+
let recipient_address = wallet
67+
.get_address(AddressIndex::New)
68+
.unwrap()
69+
.address
70+
.to_string();
71+
72+
let amount = 9359;
73+
74+
let tx = build_signed_tx(&wallet, &recipient_address, amount);
75+
76+
blockchain.broadcast(&tx).unwrap();
77+
78+
println!("tx id: {}", tx.txid());
79+
} else {
80+
println!("Insufficient Funds. Fund the wallet with the address above");
81+
}
82+
83+
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
84+
.map_err(ToString::to_string)
85+
.map_err(bdk::Error::Generic)
86+
.unwrap();
87+
88+
println!("------\nWallet Backup: {}", export.to_string());
89+
}

examples/utils/mod.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
pub(crate) mod tx {
2+
3+
use std::str::FromStr;
4+
5+
use bdk::{database::BatchDatabase, SignOptions, Wallet};
6+
use bitcoin::{Address, Transaction};
7+
8+
pub fn build_signed_tx<D: BatchDatabase>(
9+
wallet: &Wallet<D>,
10+
recipient_address: &str,
11+
amount: u64,
12+
) -> Transaction {
13+
// Create a transaction builder
14+
let mut tx_builder = wallet.build_tx();
15+
16+
let to_address = Address::from_str(recipient_address).unwrap();
17+
18+
// Set recipient of the transaction
19+
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
20+
21+
// Finalise the transaction and extract PSBT
22+
let (mut psbt, _) = tx_builder.finish().unwrap();
23+
24+
// Sign the above psbt with signing option
25+
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
26+
27+
// Extract the final transaction
28+
psbt.extract_tx()
29+
}
30+
}

0 commit comments

Comments
 (0)