Skip to content

Commit aa395dc

Browse files
committed
Add code example for every supported backend
1 parent fdb272e commit aa395dc

File tree

8 files changed

+495
-1
lines changed

8 files changed

+495
-1
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ path = "examples/compiler.rs"
108108
required-features = ["compiler"]
109109

110110
[workspace]
111-
members = ["macros"]
111+
members = ["macros", "examples/backend"]
112112
[package.metadata.docs.rs]
113113
features = ["compiler", "electrum", "esplora", "ureq", "compact_filters", "rpc", "key-value-db", "sqlite", "all-keys", "verify"]
114114
# defines the configuration attribute `docsrs`

examples/backend/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "backend_test"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
bdk = { path = "../..", default-feature = false, features = ["all-keys", "compact_filters", "compiler", "key-value-db", "rpc", "use-esplora-ureq"] }
8+
dirs-next = "2.0"

examples/backend/src/main.rs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::env;
2+
3+
use bdk::bitcoin::Network;
4+
5+
mod wallet_common;
6+
mod wallet_core;
7+
mod wallet_electrum;
8+
mod wallet_esplora;
9+
mod wallet_neutrino;
10+
11+
fn run_electrum() {
12+
let network = Network::Testnet;
13+
14+
let mnemonic_words =
15+
"health lyrics appear aunt either wrist maple hover family episode seven maze";
16+
17+
let electrum_url = "ssl://electrum.blockstream.info:60002";
18+
19+
wallet_electrum::run(network, electrum_url, mnemonic_words);
20+
}
21+
22+
fn run_esplora() {
23+
let network = Network::Testnet;
24+
25+
let mnemonic_words =
26+
"health lyrics appear aunt either wrist maple hover family episode seven maze";
27+
28+
let esplora_url = "https://blockstream.info/testnet/api";
29+
30+
wallet_esplora::run(network, esplora_url, mnemonic_words);
31+
}
32+
33+
fn run_neutrino() {
34+
// let network = Network::Bitcoin;
35+
let network = Network::Testnet;
36+
37+
// let neutrino_url = "btcd-mainnet.lightning.computer:8333";
38+
// let neutrino_url = "bb1.breez.technology:8333";
39+
let neutrino_url = "faucet.lightning.community:18333";
40+
41+
let mnemonic_words =
42+
"health lyrics appear aunt either wrist maple hover family episode seven maze";
43+
44+
wallet_neutrino::run(network, neutrino_url, mnemonic_words)
45+
}
46+
47+
fn run_rpc_core() {
48+
let network = Network::Signet;
49+
50+
let mnemonic_words =
51+
"health lyrics appear aunt either wrist maple hover family episode seven maze";
52+
53+
let rpc_url = "127.0.0.1:38332";
54+
55+
let username = "admin";
56+
let password = "password";
57+
58+
wallet_core::run(network, rpc_url, username, password, mnemonic_words);
59+
}
60+
61+
fn main() {
62+
// This program can be started with the following command:
63+
// cargo run esplora
64+
// The argument represents the backend.
65+
// The options are electrum, esplora, rpc_core or neutrino
66+
67+
/*
68+
// Generate a new mnemonic
69+
let mnemonic: GeneratedKey<_, miniscript::Segwitv0> = Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
70+
71+
// Convert mnemonic to string
72+
let mnemonic_words = mnemonic.to_string();
73+
74+
println!("mnemonic {:?}", mnemonic_words);
75+
*/
76+
77+
let backend_options = vec!["electrum", "esplora", "rpc_core", "neutrino"];
78+
79+
let args: Vec<String> = env::args().collect();
80+
81+
let mut selected_option = if args.len() < 2 { "electrum" } else { &args[1] };
82+
83+
if !backend_options.contains(&selected_option) {
84+
selected_option = "electrum";
85+
}
86+
87+
println!("Running {:?} example", selected_option);
88+
89+
match selected_option {
90+
"electrum" => run_electrum(),
91+
"esplora" => run_esplora(),
92+
"neutrino" => run_neutrino(),
93+
"rpc_core" => run_rpc_core(),
94+
_ => println!("Error !"),
95+
}
96+
}

examples/backend/src/wallet_common.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
2+
use bdk::bitcoin::{Address, Network, Transaction};
3+
use bdk::database::BatchDatabase;
4+
use bdk::keys::{DerivableKey, ExtendedKey};
5+
6+
use bdk::keys::bip39::Mnemonic;
7+
8+
use bdk::wallet::signer::SignOptions;
9+
use bdk::Wallet;
10+
11+
use std::str::FromStr;
12+
13+
pub fn mnemonic_to_xprv(network: &Network, mnemonic_words: &str) -> ExtendedPrivKey {
14+
// Parse a mnemonic
15+
let mnemonic = Mnemonic::parse(mnemonic_words).unwrap();
16+
17+
// Generate the extended key
18+
let xkey: ExtendedKey = mnemonic.into_extended_key().unwrap();
19+
20+
// Get xprv from the extended key
21+
xkey.into_xprv(*network).unwrap()
22+
}
23+
24+
pub fn build_signed_tx<B, D: BatchDatabase>(
25+
wallet: &Wallet<B, D>,
26+
recipient_address: &str,
27+
amount: u64,
28+
) -> Transaction {
29+
// Create a transaction builder
30+
let mut tx_builder = wallet.build_tx();
31+
32+
let to_address = Address::from_str(recipient_address).unwrap();
33+
34+
// Set recipient of the transaction
35+
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
36+
37+
// Finalise the transaction and extract PSBT
38+
let (mut psbt, _) = tx_builder.finish().unwrap();
39+
40+
// Sign the above psbt with signing option
41+
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
42+
43+
// Extract the final transaction
44+
psbt.extract_tx()
45+
46+
}

examples/backend/src/wallet_core.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use bdk::bitcoin::secp256k1::Secp256k1;
2+
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
3+
use bdk::bitcoin::Network;
4+
use bdk::blockchain::{ConfigurableBlockchain, LogProgress};
5+
6+
use bdk::template::Bip84;
7+
use bdk::wallet::export::WalletExport;
8+
use bdk::KeychainKind;
9+
10+
use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig};
11+
12+
use bdk::wallet::{wallet_name_from_descriptor, AddressIndex};
13+
use bdk::Wallet;
14+
15+
use bdk::sled::{self, Tree};
16+
17+
use crate::wallet_common::{build_signed_tx, mnemonic_to_xprv};
18+
19+
fn load_or_create_wallet(
20+
network: &Network,
21+
rpc_url: &str,
22+
username: &str,
23+
password: &str,
24+
xpriv: &ExtendedPrivKey,
25+
) -> Wallet<RpcBlockchain, Tree> {
26+
let auth = Auth::UserPass {
27+
username: username.to_string(),
28+
password: password.to_string(),
29+
};
30+
31+
// Use deterministic wallet name derived from descriptor
32+
let wallet_name = wallet_name_from_descriptor(
33+
Bip84(*xpriv, KeychainKind::External),
34+
Some(Bip84(*xpriv, KeychainKind::Internal)),
35+
*network,
36+
&Secp256k1::new(),
37+
)
38+
.unwrap();
39+
40+
println!("wallet name: {:?}", wallet_name);
41+
42+
// Setup the RPC configuration
43+
let rpc_config = RpcConfig {
44+
url: rpc_url.to_string(),
45+
auth,
46+
network: *network,
47+
wallet_name: wallet_name.clone(),
48+
skip_blocks: Some(70_000), // Some(block_count)
49+
};
50+
51+
// Use the above configuration to create a RPC blockchain backend
52+
let blockchain = RpcBlockchain::from_config(&rpc_config).unwrap();
53+
54+
// Create the datadir to store wallet data
55+
let mut datadir = dirs_next::home_dir().unwrap();
56+
datadir.push(".bdk-wallets");
57+
datadir.push(wallet_name.clone());
58+
let database = sled::open(datadir).unwrap();
59+
let db_tree = database.open_tree(wallet_name).unwrap();
60+
61+
// Combine everything and finally create the BDK wallet structure
62+
let wallet = Wallet::new(
63+
Bip84(*xpriv, KeychainKind::External),
64+
Some(Bip84(*xpriv, KeychainKind::Internal)),
65+
*network,
66+
db_tree,
67+
blockchain,
68+
)
69+
.unwrap();
70+
71+
// Sync the wallet
72+
//let sync_result = wallet.sync(LogProgress, None);
73+
wallet.sync(LogProgress, None).unwrap();
74+
75+
wallet
76+
}
77+
78+
pub fn run(network: Network, rpc_url: &str, username: &str, password: &str, mnemonic_words: &str) {
79+
let xpriv = mnemonic_to_xprv(&network, mnemonic_words);
80+
81+
let wallet = load_or_create_wallet(&network, rpc_url, username, password, &xpriv);
82+
83+
println!(
84+
"mnemonic: {}\n\nrecv desc (pub key): {:#?}\n\nchng desc (pub key): {:#?}",
85+
mnemonic_words,
86+
wallet
87+
.get_descriptor_for_keychain(KeychainKind::External)
88+
.to_string(),
89+
wallet
90+
.get_descriptor_for_keychain(KeychainKind::Internal)
91+
.to_string()
92+
);
93+
94+
// Fetch a fresh address to receive coins
95+
let address = wallet.get_address(AddressIndex::New).unwrap().address;
96+
97+
println!("new address: {}", address);
98+
99+
let balance = wallet.get_balance().unwrap();
100+
101+
println!("balance: {}", balance);
102+
103+
if balance > 5500 {
104+
let recipient_address = "tb1q766f5v4h9ml8dh99ev5ertg2ysrjz2kkuzq8up";
105+
106+
let tx = build_signed_tx(&wallet, recipient_address, 5000);
107+
108+
// Broadcast the transaction
109+
let tx_id = wallet.broadcast(&tx).unwrap();
110+
111+
println!("tx id: {}", tx_id.to_string());
112+
} else {
113+
println!("Insufficient Funds. Fund the wallet with the address above");
114+
}
115+
116+
let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
117+
.map_err(ToString::to_string)
118+
.map_err(bdk::Error::Generic)
119+
.unwrap();
120+
121+
println!("------\nWallet Backup: {}", export.to_string());
122+
}
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
2+
use bdk::bitcoin::Network;
3+
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
4+
use bdk::database::MemoryDatabase;
5+
use bdk::template::Bip84;
6+
use bdk::wallet::export::WalletExport;
7+
use bdk::{KeychainKind, Wallet};
8+
9+
use bdk::electrum_client::Client;
10+
use bdk::wallet::AddressIndex;
11+
12+
use crate::wallet_common::{build_signed_tx, mnemonic_to_xprv};
13+
14+
pub fn load_or_create_wallet(
15+
electrum_url: &str,
16+
network: &Network,
17+
xpriv: &ExtendedPrivKey,
18+
) -> Wallet<ElectrumBlockchain, MemoryDatabase> {
19+
// Apparently it works only with Electrs (not EletrumX)
20+
let client = Client::new(electrum_url).unwrap();
21+
22+
let wallet = Wallet::new(
23+
Bip84(*xpriv, KeychainKind::External),
24+
Some(Bip84(*xpriv, KeychainKind::Internal)),
25+
*network,
26+
MemoryDatabase::default(),
27+
ElectrumBlockchain::from(client),
28+
)
29+
.unwrap();
30+
31+
wallet.sync(noop_progress(), None).unwrap();
32+
33+
wallet
34+
}
35+
36+
pub fn run(network: Network, electrum_url: &str, mnemonic_words: &str) {
37+
let xpriv = mnemonic_to_xprv(&network, mnemonic_words);
38+
39+
let wallet = load_or_create_wallet(electrum_url, &network, &xpriv);
40+
41+
let address = wallet.get_address(AddressIndex::New).unwrap().address;
42+
43+
println!("address: {}", address);
44+
45+
let balance = wallet.get_balance().unwrap();
46+
47+
println!("balance: {}", balance);
48+
49+
if balance > 10500 {
50+
// tBTC address of https://testnet-faucet.mempool.co/
51+
let recipient_address = "mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt";
52+
53+
let amount = 9359;
54+
55+
let tx = build_signed_tx(&wallet, recipient_address, amount);
56+
57+
let tx_id = wallet.broadcast(&tx).unwrap();
58+
59+
println!("tx id: {}", tx_id.to_string());
60+
} else {
61+
println!("Insufficient Funds. Fund the wallet with the address above");
62+
}
63+
64+
let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
65+
.map_err(ToString::to_string)
66+
.map_err(bdk::Error::Generic)
67+
.unwrap();
68+
69+
println!("------\nWallet Backup: {}", export.to_string());
70+
}

0 commit comments

Comments
 (0)