Skip to content

Commit 11bfc2f

Browse files
committed
docs(wallet): for electrum, esplora examples use sqlite+testnet4, and mempool.space
For electrum, esplora examples print mempool.space tx URLs and add delay before RBF. For rpc example also add justfile to help testing with regtest bitcoind.
1 parent 5d2d25f commit 11bfc2f

File tree

10 files changed

+213
-116
lines changed

10 files changed

+213
-116
lines changed

examples/example_wallet_electrum/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ version = "0.2.0"
44
edition = "2021"
55

66
[dependencies]
7-
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
7+
bdk_wallet = { path = "../../wallet", features = ["rusqlite"] }
88
bdk_electrum = { version = "0.23.0" }
99
anyhow = "1"

examples/example_wallet_electrum/src/main.rs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
1-
use bdk_wallet::bitcoin::FeeRate;
2-
use bdk_wallet::file_store::Store;
3-
use bdk_wallet::psbt::PsbtUtils;
4-
use bdk_wallet::Wallet;
5-
use std::io::Write;
6-
71
use bdk_electrum::electrum_client;
82
use bdk_electrum::BdkElectrumClient;
93
use bdk_wallet::bitcoin::Amount;
4+
use bdk_wallet::bitcoin::FeeRate;
105
use bdk_wallet::bitcoin::Network;
116
use bdk_wallet::chain::collections::HashSet;
7+
use bdk_wallet::psbt::PsbtUtils;
8+
use bdk_wallet::rusqlite::Connection;
9+
use bdk_wallet::Wallet;
1210
use bdk_wallet::{KeychainKind, SignOptions};
11+
use std::io::Write;
12+
use std::thread::sleep;
13+
use std::time::Duration;
1314

14-
const DB_MAGIC: &str = "bdk_wallet_electrum_example";
1515
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
1616
const STOP_GAP: usize = 50;
1717
const BATCH_SIZE: usize = 5;
1818

19-
const NETWORK: Network = Network::Testnet;
19+
const DB_PATH: &str = "bdk-example-electrum.sqlite";
20+
const NETWORK: Network = Network::Testnet4;
2021
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
2122
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
22-
const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002";
23+
const ELECTRUM_URL: &str = "ssl://mempool.space:40002";
2324

2425
fn main() -> Result<(), anyhow::Error> {
25-
let db_path = "bdk-electrum-example.db";
26-
27-
let (mut db, _) = Store::<bdk_wallet::ChangeSet>::load_or_create(DB_MAGIC.as_bytes(), db_path)?;
28-
26+
let mut db = Connection::open(DB_PATH)?;
2927
let wallet_opt = Wallet::load()
3028
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
3129
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
@@ -86,8 +84,11 @@ fn main() -> Result<(), anyhow::Error> {
8684
println!("Please send at least {SEND_AMOUNT} to the receiving address");
8785
std::process::exit(0);
8886
}
87+
88+
let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
8989
let mut tx_builder = wallet.build_tx();
9090
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
91+
tx_builder.fee_rate(target_fee_rate);
9192

9293
let mut psbt = tx_builder.finish()?;
9394
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
@@ -97,7 +98,7 @@ fn main() -> Result<(), anyhow::Error> {
9798
let tx = psbt.extract_tx()?;
9899
client.transaction_broadcast(&tx)?;
99100
let txid = tx.compute_txid();
100-
println!("Tx broadcasted! Txid: {txid}");
101+
println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}");
101102

102103
println!("Partial Sync...");
103104
print!("SCANNING: ");
@@ -120,8 +121,8 @@ fn main() -> Result<(), anyhow::Error> {
120121
wallet.apply_update(sync_update)?;
121122
wallet.persist(&mut db)?;
122123

123-
// bump fee tx
124-
let feerate = FeeRate::from_sat_per_kwu(tx_feerate.to_sat_per_kwu() + 250);
124+
// bump fee rate for tx by at least 1 sat per vbyte
125+
let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap();
125126
let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx");
126127
builder.fee_rate(feerate);
127128
let mut bumped_psbt = builder.finish().unwrap();
@@ -141,14 +142,18 @@ fn main() -> Result<(), anyhow::Error> {
141142
);
142143
assert!(
143144
new_fee > original_fee,
144-
"New fee ({}) should be higher than original ({})",
145-
new_fee,
146-
original_fee
145+
"New fee ({new_fee}) should be higher than original ({original_fee})"
147146
);
147+
148+
// wait for first transaction to make it into the mempool and be indexed on mempool.space
149+
sleep(Duration::from_secs(10));
148150
client.transaction_broadcast(&bumped_tx)?;
149-
println!("Broadcasted bumped tx. Txid: {}", bumped_tx.compute_txid());
151+
println!(
152+
"Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}",
153+
bumped_tx.compute_txid()
154+
);
150155

151-
print!("Syncing after bumped tx broadcast...");
156+
println!("Syncing after bumped tx broadcast...");
152157
let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {});
153158
let sync_update = client.sync(sync_request, BATCH_SIZE, false)?;
154159

@@ -157,12 +162,10 @@ fn main() -> Result<(), anyhow::Error> {
157162
evicted_txs.push((*txid, *last_seen));
158163
}
159164

165+
wallet.apply_update(sync_update)?;
160166
if !evicted_txs.is_empty() {
161-
wallet.apply_evicted_txs(evicted_txs.clone());
162167
println!("Applied {} evicted transactions", evicted_txs.len());
163168
}
164-
165-
wallet.apply_update(sync_update)?;
166169
wallet.persist(&mut db)?;
167170

168171
let balance_after_sync = wallet.balance();

examples/example_wallet_esplora_async/src/main.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,36 @@ use bdk_wallet::{
77
KeychainKind, SignOptions, Wallet,
88
};
99
use std::{collections::BTreeSet, io::Write};
10+
use tokio::time::{sleep, Duration};
1011

1112
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
1213
const STOP_GAP: usize = 5;
1314
const PARALLEL_REQUESTS: usize = 5;
1415

1516
const DB_PATH: &str = "bdk-example-esplora-async.sqlite";
16-
const NETWORK: Network = Network::Signet;
17+
const NETWORK: Network = Network::Testnet4;
1718
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
1819
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
19-
const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
20+
const ESPLORA_URL: &str = "https://mempool.space/testnet4/api";
2021

2122
#[tokio::main]
2223
async fn main() -> Result<(), anyhow::Error> {
23-
let mut conn = Connection::open(DB_PATH)?;
24+
let mut db = Connection::open(DB_PATH)?;
2425
let wallet_opt = Wallet::load()
2526
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
2627
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
2728
.extract_keys()
2829
.check_network(NETWORK)
29-
.load_wallet(&mut conn)?;
30+
.load_wallet(&mut db)?;
3031
let mut wallet = match wallet_opt {
3132
Some(wallet) => wallet,
3233
None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
3334
.network(NETWORK)
34-
.create_wallet(&mut conn)?,
35+
.create_wallet(&mut db)?,
3536
};
3637

3738
let address = wallet.next_unused_address(KeychainKind::External);
38-
wallet.persist(&mut conn)?;
39+
wallet.persist(&mut db)?;
3940
println!("Next unused address: ({}) {address}", address.index);
4041

4142
let balance = wallet.balance();
@@ -63,7 +64,7 @@ async fn main() -> Result<(), anyhow::Error> {
6364
.await?;
6465

6566
wallet.apply_update(update)?;
66-
wallet.persist(&mut conn)?;
67+
wallet.persist(&mut db)?;
6768
println!();
6869

6970
let balance = wallet.balance();
@@ -78,8 +79,11 @@ async fn main() -> Result<(), anyhow::Error> {
7879
println!("Please send at least {SEND_AMOUNT} to the receiving address");
7980
std::process::exit(0);
8081
}
82+
83+
let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
8184
let mut tx_builder = wallet.build_tx();
8285
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
86+
tx_builder.fee_rate(target_fee_rate);
8387

8488
let mut psbt = tx_builder.finish()?;
8589
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
@@ -89,7 +93,7 @@ async fn main() -> Result<(), anyhow::Error> {
8993
let tx = psbt.extract_tx()?;
9094
client.broadcast(&tx).await?;
9195
let txid = tx.compute_txid();
92-
println!("Tx broadcasted! Txid: {txid}");
96+
println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}");
9397

9498
println!("Partial Sync...");
9599
print!("SCANNING: ");
@@ -109,9 +113,10 @@ async fn main() -> Result<(), anyhow::Error> {
109113
let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?;
110114
println!();
111115
wallet.apply_update(sync_update)?;
112-
wallet.persist(&mut conn)?;
116+
wallet.persist(&mut db)?;
113117

114-
let feerate = FeeRate::from_sat_per_kwu(tx_feerate.to_sat_per_kwu() + 250);
118+
// bump fee rate for tx by at least 1 sat per vbyte
119+
let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap();
115120
let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx");
116121
builder.fee_rate(feerate);
117122
let mut bumped_psbt = builder.finish().unwrap();
@@ -133,8 +138,14 @@ async fn main() -> Result<(), anyhow::Error> {
133138
new_fee > original_fee,
134139
"New fee ({new_fee}) should be higher than original ({original_fee})",
135140
);
141+
142+
// wait for first transaction to make it into the mempool and be indexed on mempool.space
143+
sleep(Duration::from_secs(10)).await;
136144
client.broadcast(&bumped_tx).await?;
137-
println!("Broadcasted bumped tx. Txid: {}", bumped_tx.compute_txid());
145+
println!(
146+
"Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}",
147+
bumped_tx.compute_txid()
148+
);
138149

139150
println!("syncing after broadcasting bumped tx...");
140151
print!("SCANNING: ");
@@ -155,26 +166,17 @@ async fn main() -> Result<(), anyhow::Error> {
155166

156167
let mut evicted_txs = Vec::new();
157168

158-
let last_seen = wallet
159-
.tx_graph()
160-
.full_txs()
161-
.find(|full_tx| full_tx.txid == txid)
162-
.map_or(0, |full_tx| full_tx.last_seen.unwrap_or(0));
163-
if !evicted_txs
164-
.iter()
165-
.any(|(evicted_txid, _)| evicted_txid == &txid)
166-
{
167-
evicted_txs.push((txid, last_seen));
169+
for (txid, last_seen) in &sync_update.tx_update.evicted_ats {
170+
evicted_txs.push((*txid, *last_seen));
168171
}
169172

173+
wallet.apply_update(sync_update)?;
174+
170175
if !evicted_txs.is_empty() {
171-
let evicted_count = evicted_txs.len();
172-
wallet.apply_evicted_txs(evicted_txs);
173-
println!("Applied {evicted_count} evicted transactions");
176+
println!("Applied {} evicted transactions", evicted_txs.len());
174177
}
175178

176-
wallet.apply_update(sync_update)?;
177-
wallet.persist(&mut conn)?;
179+
wallet.persist(&mut db)?;
178180

179181
let balance_after_sync = wallet.balance();
180182
println!("Wallet balance after sync: {}", balance_after_sync.total());

examples/example_wallet_esplora_blocking/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ publish = false
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10-
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
10+
bdk_wallet = { path = "../../wallet", features = ["rusqlite"] }
1111
bdk_esplora = { version = "0.22.0", features = ["blocking"] }
1212
anyhow = "1"

examples/example_wallet_esplora_blocking/src/main.rs

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
use bdk_esplora::{esplora_client, EsploraExt};
2+
use bdk_wallet::rusqlite::Connection;
23
use bdk_wallet::{
34
bitcoin::{Amount, FeeRate, Network},
4-
file_store::Store,
55
psbt::PsbtUtils,
66
KeychainKind, SignOptions, Wallet,
77
};
8+
use std::thread::sleep;
9+
use std::time::Duration;
810
use std::{collections::BTreeSet, io::Write};
911

10-
const DB_MAGIC: &str = "bdk_wallet_esplora_example";
11-
const DB_PATH: &str = "bdk-example-esplora-blocking.db";
1212
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
1313
const STOP_GAP: usize = 5;
1414
const PARALLEL_REQUESTS: usize = 5;
1515

16-
const NETWORK: Network = Network::Signet;
16+
const DB_PATH: &str = "bdk-example-esplora-blocking.sqlite";
17+
const NETWORK: Network = Network::Testnet4;
1718
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
1819
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
19-
const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
20+
const ESPLORA_URL: &str = "https://mempool.space/testnet4/api";
2021

2122
fn main() -> Result<(), anyhow::Error> {
22-
let (mut db, _) = Store::<bdk_wallet::ChangeSet>::load_or_create(DB_MAGIC.as_bytes(), DB_PATH)?;
23-
23+
let mut db = Connection::open(DB_PATH)?;
2424
let wallet_opt = Wallet::load()
2525
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
2626
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
@@ -74,8 +74,10 @@ fn main() -> Result<(), anyhow::Error> {
7474
std::process::exit(0);
7575
}
7676

77+
let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
7778
let mut tx_builder = wallet.build_tx();
7879
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
80+
tx_builder.fee_rate(target_fee_rate);
7981

8082
let mut psbt = tx_builder.finish()?;
8183
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
@@ -85,7 +87,7 @@ fn main() -> Result<(), anyhow::Error> {
8587
let tx = psbt.extract_tx()?;
8688
client.broadcast(&tx)?;
8789
let txid = tx.compute_txid();
88-
println!("Tx broadcasted! Txid: {txid}");
90+
println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}");
8991

9092
println!("Partial Sync...");
9193
print!("SCANNING: ");
@@ -108,7 +110,8 @@ fn main() -> Result<(), anyhow::Error> {
108110
wallet.persist(&mut db)?;
109111
println!();
110112

111-
let feerate = FeeRate::from_sat_per_kwu(tx_feerate.to_sat_per_kwu() + 200);
113+
// bump fee rate for tx by at least 1 sat per vbyte
114+
let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap();
112115
let mut builder = wallet.build_fee_bump(txid).unwrap();
113116
builder.fee_rate(feerate);
114117
let mut new_psbt = builder.finish().unwrap();
@@ -130,9 +133,12 @@ fn main() -> Result<(), anyhow::Error> {
130133
new_fee > original_fee,
131134
"Replacement tx fee ({new_fee}) should be higher than original ({original_fee})",
132135
);
136+
137+
// wait for first transaction to make it into the mempool and be indexed on mempool.space
138+
sleep(Duration::from_secs(10));
133139
client.broadcast(&bumped_tx)?;
134140
println!(
135-
"Broadcast replacement transaction. Txid: {}",
141+
"Broadcast replacement transaction. Txid: https://mempool.space/testnet4/tx/{}",
136142
bumped_tx.compute_txid()
137143
);
138144

@@ -142,25 +148,14 @@ fn main() -> Result<(), anyhow::Error> {
142148
println!();
143149

144150
let mut evicted_txs = Vec::new();
145-
let last_seen = wallet
146-
.tx_graph()
147-
.full_txs()
148-
.find(|full_tx| full_tx.txid == txid)
149-
.map_or(0, |full_tx| full_tx.last_seen.unwrap_or(0));
150-
if !evicted_txs
151-
.iter()
152-
.any(|(evicted_txid, _)| evicted_txid == &txid)
153-
{
154-
evicted_txs.push((txid, last_seen));
151+
for (txid, last_seen) in &sync_update.tx_update.evicted_ats {
152+
evicted_txs.push((*txid, *last_seen));
155153
}
156154

155+
wallet.apply_update(sync_update)?;
157156
if !evicted_txs.is_empty() {
158-
let evicted_count = evicted_txs.len();
159-
wallet.apply_evicted_txs(evicted_txs);
160-
println!("Applied {evicted_count} evicted transactions");
157+
println!("Applied {} evicted transactions", evicted_txs.len());
161158
}
162-
163-
wallet.apply_update(sync_update)?;
164159
wallet.persist(&mut db)?;
165160

166161
let balance_after_sync = wallet.balance();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test_data/

examples/example_wallet_rpc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9-
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
9+
bdk_wallet = { path = "../../wallet", features = ["rusqlite"] }
1010
bdk_bitcoind_rpc = { version = "0.20.0" }
1111

1212
anyhow = "1"

0 commit comments

Comments
 (0)