Skip to content

Commit 5d2d25f

Browse files
committed
docs(wallet): add bumping tx to examples
1 parent fabd2d9 commit 5d2d25f

File tree

3 files changed

+175
-119
lines changed
  • examples
    • example_wallet_electrum/src
    • example_wallet_esplora_async/src
    • example_wallet_esplora_blocking/src

3 files changed

+175
-119
lines changed

examples/example_wallet_electrum/src/main.rs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use bdk_wallet::bitcoin::Txid;
1+
use bdk_wallet::bitcoin::FeeRate;
22
use bdk_wallet::file_store::Store;
3+
use bdk_wallet::psbt::PsbtUtils;
34
use bdk_wallet::Wallet;
45
use std::io::Write;
56

@@ -45,7 +46,7 @@ fn main() -> Result<(), anyhow::Error> {
4546
let balance = wallet.balance();
4647
println!("Wallet balance before syncing: {}", balance.total());
4748

48-
println!("=== Performing Full Sync ===");
49+
println!("Performing Full Sync...");
4950
let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?);
5051

5152
// Populate the electrum client's transaction cache so it doesn't redownload transaction we
@@ -59,7 +60,9 @@ fn main() -> Result<(), anyhow::Error> {
5960
if once.insert(k) {
6061
print!("\nScanning keychain [{k:?}]");
6162
}
62-
print!(" {spk_i:<3}");
63+
if spk_i.is_multiple_of(5) {
64+
print!(" {spk_i:<3}");
65+
}
6366
stdout.flush().expect("must flush");
6467
}
6568
});
@@ -83,27 +86,20 @@ fn main() -> Result<(), anyhow::Error> {
8386
println!("Please send at least {SEND_AMOUNT} to the receiving address");
8487
std::process::exit(0);
8588
}
86-
8789
let mut tx_builder = wallet.build_tx();
8890
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
8991

9092
let mut psbt = tx_builder.finish()?;
9193
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
9294
assert!(finalized);
93-
95+
let original_fee = psbt.fee_amount().unwrap();
96+
let tx_feerate = psbt.fee_rate().unwrap();
9497
let tx = psbt.extract_tx()?;
9598
client.transaction_broadcast(&tx)?;
96-
println!("Tx broadcasted! Txid: {}", tx.compute_txid());
97-
98-
let unconfirmed_txids: HashSet<Txid> = wallet
99-
.transactions()
100-
.filter(|tx| tx.chain_position.is_unconfirmed())
101-
.map(|tx| tx.tx_node.txid)
102-
.collect();
99+
let txid = tx.compute_txid();
100+
println!("Tx broadcasted! Txid: {txid}");
103101

104-
client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
105-
106-
println!("\n=== Performing Partial Sync ===\n");
102+
println!("Partial Sync...");
107103
print!("SCANNING: ");
108104
let mut last_printed = 0;
109105
let sync_request = wallet
@@ -121,30 +117,44 @@ fn main() -> Result<(), anyhow::Error> {
121117
client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
122118
let sync_update = client.sync(sync_request, BATCH_SIZE, false)?;
123119
println!();
120+
wallet.apply_update(sync_update)?;
121+
wallet.persist(&mut db)?;
122+
123+
// bump fee tx
124+
let feerate = FeeRate::from_sat_per_kwu(tx_feerate.to_sat_per_kwu() + 250);
125+
let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx");
126+
builder.fee_rate(feerate);
127+
let mut bumped_psbt = builder.finish().unwrap();
128+
let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?;
129+
assert!(finalize_btx);
130+
let new_fee = bumped_psbt.fee_amount().unwrap();
131+
let bumped_tx = bumped_psbt.extract_tx()?;
132+
assert_eq!(
133+
bumped_tx
134+
.output
135+
.iter()
136+
.find(|txout| txout.script_pubkey == address.script_pubkey())
137+
.unwrap()
138+
.value,
139+
SEND_AMOUNT,
140+
"Recipient output should remain unchanged"
141+
);
142+
assert!(
143+
new_fee > original_fee,
144+
"New fee ({}) should be higher than original ({})",
145+
new_fee,
146+
original_fee
147+
);
148+
client.transaction_broadcast(&bumped_tx)?;
149+
println!("Broadcasted bumped tx. Txid: {}", bumped_tx.compute_txid());
150+
151+
print!("Syncing after bumped tx broadcast...");
152+
let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {});
153+
let sync_update = client.sync(sync_request, BATCH_SIZE, false)?;
124154

125155
let mut evicted_txs = Vec::new();
126-
for txid in unconfirmed_txids {
127-
let tx_node = wallet
128-
.tx_graph()
129-
.full_txs()
130-
.find(|full_tx| full_tx.txid == txid);
131-
let wallet_tx = wallet.get_tx(txid);
132-
133-
let is_evicted = match wallet_tx {
134-
Some(wallet_tx) => {
135-
!wallet_tx.chain_position.is_unconfirmed()
136-
&& !wallet_tx.chain_position.is_confirmed()
137-
}
138-
None => true,
139-
};
140-
141-
if is_evicted {
142-
if let Some(full_tx) = tx_node {
143-
evicted_txs.push((full_tx.txid, full_tx.last_seen.unwrap_or(0)));
144-
} else {
145-
evicted_txs.push((txid, 0));
146-
}
147-
}
156+
for (txid, last_seen) in &sync_update.tx_update.evicted_ats {
157+
evicted_txs.push((*txid, *last_seen));
148158
}
149159

150160
if !evicted_txs.is_empty() {

examples/example_wallet_esplora_async/src/main.rs

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use anyhow::Ok;
22
use bdk_esplora::{esplora_client, EsploraAsyncExt};
33
use bdk_wallet::{
4-
bitcoin::{Amount, Network, Txid},
4+
bitcoin::{Amount, FeeRate, Network},
5+
psbt::PsbtUtils,
56
rusqlite::Connection,
67
KeychainKind, SignOptions, Wallet,
78
};
8-
use std::{
9-
collections::{BTreeSet, HashSet},
10-
io::Write,
11-
};
9+
use std::{collections::BTreeSet, io::Write};
1210

1311
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
1412
const STOP_GAP: usize = 5;
@@ -23,7 +21,6 @@ const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
2321
#[tokio::main]
2422
async fn main() -> Result<(), anyhow::Error> {
2523
let mut conn = Connection::open(DB_PATH)?;
26-
2724
let wallet_opt = Wallet::load()
2825
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
2926
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
@@ -39,12 +36,12 @@ async fn main() -> Result<(), anyhow::Error> {
3936

4037
let address = wallet.next_unused_address(KeychainKind::External);
4138
wallet.persist(&mut conn)?;
42-
println!("Next unused address: ({}) {}", address.index, address);
39+
println!("Next unused address: ({}) {address}", address.index);
4340

4441
let balance = wallet.balance();
4542
println!("Wallet balance before syncing: {}", balance.total());
4643

47-
println!("=== Performing Full Sync ===");
44+
println!("Full Sync...");
4845
let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?;
4946

5047
let request = wallet.start_full_scan().inspect({
@@ -54,7 +51,9 @@ async fn main() -> Result<(), anyhow::Error> {
5451
if once.insert(keychain) {
5552
print!("\nScanning keychain [{keychain:?}]");
5653
}
57-
print!(" {spk_i:<3}");
54+
if spk_i.is_multiple_of(5) {
55+
print!(" {spk_i:<3}");
56+
}
5857
stdout.flush().expect("must flush")
5958
}
6059
});
@@ -79,27 +78,22 @@ async fn main() -> Result<(), anyhow::Error> {
7978
println!("Please send at least {SEND_AMOUNT} to the receiving address");
8079
std::process::exit(0);
8180
}
82-
8381
let mut tx_builder = wallet.build_tx();
8482
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
8583

8684
let mut psbt = tx_builder.finish()?;
8785
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
8886
assert!(finalized);
89-
87+
let original_fee = psbt.fee_amount().unwrap();
88+
let tx_feerate = psbt.fee_rate().unwrap();
9089
let tx = psbt.extract_tx()?;
9190
client.broadcast(&tx).await?;
92-
println!("Tx broadcasted! Txid: {}", tx.compute_txid());
93-
94-
let unconfirmed_txids: HashSet<Txid> = wallet
95-
.transactions()
96-
.filter(|tx| tx.chain_position.is_unconfirmed())
97-
.map(|tx| tx.tx_node.txid)
98-
.collect();
91+
let txid = tx.compute_txid();
92+
println!("Tx broadcasted! Txid: {txid}");
9993

100-
println!("\n=== Performing Partial Sync ===\n");
94+
println!("Partial Sync...");
10195
print!("SCANNING: ");
102-
let mut printed = 0;
96+
let mut printed: u32 = 0;
10397
let sync_request = wallet
10498
.start_sync_with_revealed_spks()
10599
.inspect(move |_, sync_progress| {
@@ -114,30 +108,63 @@ async fn main() -> Result<(), anyhow::Error> {
114108
});
115109
let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?;
116110
println!();
111+
wallet.apply_update(sync_update)?;
112+
wallet.persist(&mut conn)?;
117113

118-
let mut evicted_txs = Vec::new();
119-
for txid in unconfirmed_txids {
120-
let tx_node = wallet
121-
.tx_graph()
122-
.full_txs()
123-
.find(|full_tx| full_tx.txid == txid);
124-
let wallet_tx = wallet.get_tx(txid);
125-
126-
let is_evicted = match wallet_tx {
127-
Some(wallet_tx) => {
128-
!wallet_tx.chain_position.is_unconfirmed()
129-
&& !wallet_tx.chain_position.is_confirmed()
130-
}
131-
None => true,
132-
};
133-
134-
if is_evicted {
135-
if let Some(full_tx) = tx_node {
136-
evicted_txs.push((full_tx.txid, full_tx.last_seen.unwrap_or(0)));
137-
} else {
138-
evicted_txs.push((txid, 0));
114+
let feerate = FeeRate::from_sat_per_kwu(tx_feerate.to_sat_per_kwu() + 250);
115+
let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx");
116+
builder.fee_rate(feerate);
117+
let mut bumped_psbt = builder.finish().unwrap();
118+
let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?;
119+
assert!(finalize_btx);
120+
let new_fee = bumped_psbt.fee_amount().unwrap();
121+
let bumped_tx = bumped_psbt.extract_tx()?;
122+
assert_eq!(
123+
bumped_tx
124+
.output
125+
.iter()
126+
.find(|txout| txout.script_pubkey == address.script_pubkey())
127+
.unwrap()
128+
.value,
129+
SEND_AMOUNT,
130+
"Outputs should be the same"
131+
);
132+
assert!(
133+
new_fee > original_fee,
134+
"New fee ({new_fee}) should be higher than original ({original_fee})",
135+
);
136+
client.broadcast(&bumped_tx).await?;
137+
println!("Broadcasted bumped tx. Txid: {}", bumped_tx.compute_txid());
138+
139+
println!("syncing after broadcasting bumped tx...");
140+
print!("SCANNING: ");
141+
let sync_request = wallet
142+
.start_sync_with_revealed_spks()
143+
.inspect(move |_, sync_progress| {
144+
let progress_percent =
145+
(100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32;
146+
let progress_percent = progress_percent.round() as u32;
147+
if progress_percent.is_multiple_of(10) && progress_percent > printed {
148+
print!("{progress_percent}% ");
149+
std::io::stdout().flush().expect("must flush");
150+
printed = progress_percent;
139151
}
140-
}
152+
});
153+
let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?;
154+
println!();
155+
156+
let mut evicted_txs = Vec::new();
157+
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));
141168
}
142169

143170
if !evicted_txs.is_empty() {

0 commit comments

Comments
 (0)