Skip to content

Commit 070ee02

Browse files
committed
refactor(example): update wallet_esplora_async to use new EsploraAsyncExt scan and sync functions
1 parent fbcbbcc commit 070ee02

File tree

1 file changed

+200
-44
lines changed
  • example-crates/wallet_esplora_async/src

1 file changed

+200
-44
lines changed
Lines changed: 200 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{io::Write, str::FromStr};
22

3+
use bdk::bitcoin::{OutPoint, ScriptBuf, Txid};
34
use bdk::{
45
bitcoin::{Address, Network},
56
wallet::{AddressIndex, Update},
@@ -9,66 +10,189 @@ use bdk_esplora::{esplora_client, EsploraAsyncExt};
910
use bdk_file_store::Store;
1011

1112
const DB_MAGIC: &str = "bdk_wallet_esplora_async_example";
13+
const CHAIN_DATA_FILE: &str = "chain.dat";
1214
const SEND_AMOUNT: u64 = 5000;
1315
const STOP_GAP: usize = 50;
1416
const PARALLEL_REQUESTS: usize = 5;
17+
const ESPLORA_SERVER_URL: &str = "http://signet.bitcoindevkit.net";
1518

1619
#[tokio::main]
1720
async fn main() -> Result<(), Box<dyn std::error::Error>> {
18-
let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
19-
let db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), db_path)?;
21+
let network = Network::Signet;
22+
23+
// let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
24+
let db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), CHAIN_DATA_FILE)?;
2025
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
2126
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
2227

23-
let mut wallet = Wallet::new(
24-
external_descriptor,
25-
Some(internal_descriptor),
26-
db,
27-
Network::Testnet,
28-
)?;
28+
// Create a wallet and get a new address and current wallet balance before syncing
29+
let mut wallet = Wallet::new(external_descriptor, Some(internal_descriptor), db, network)?;
2930

3031
let address = wallet.get_address(AddressIndex::New);
3132
println!("Generated Address: {}", address);
3233

3334
let balance = wallet.get_balance();
34-
println!("Wallet balance before syncing: {} sats", balance.total());
35+
println!("Wallet balance before syncing: confirmed {} sats, trusted_pending {} sats, untrusted pending {} sats", balance.confirmed, balance.trusted_pending, balance.untrusted_pending);
3536

36-
print!("Syncing...");
37-
let client =
38-
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_async()?;
37+
// Create an async esplora client
38+
let client = esplora_client::Builder::new(ESPLORA_SERVER_URL).build_async()?;
3939

40+
// Get wallet's previous chain tip
4041
let prev_tip = wallet.latest_checkpoint();
41-
let keychain_spks = wallet
42-
.spks_of_all_keychains()
43-
.into_iter()
44-
.map(|(k, k_spks)| {
45-
let mut once = Some(());
46-
let mut stdout = std::io::stdout();
47-
let k_spks = k_spks
48-
.inspect(move |(spk_i, _)| match once.take() {
49-
Some(_) => print!("\nScanning keychain [{:?}]", k),
50-
None => print!(" {:<3}", spk_i),
42+
43+
// Scanning: We are iterating through spks of all keychains and scanning for transactions for
44+
// each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap`
45+
// number of consecutive spks have no transaction history. A Scan is done in situations of
46+
// wallet restoration. It is a special case. Applications should use "sync" style updates
47+
// after an initial scan.
48+
if prompt("Scan wallet") {
49+
let keychain_spks = wallet
50+
.spks_of_all_keychains()
51+
.into_iter()
52+
// This `map` is purely for logging.
53+
.map(|(keychain, iter)| {
54+
let mut first = true;
55+
let spk_iter = iter.inspect(move |(i, _)| {
56+
if first {
57+
// TODO impl Display for Keychain
58+
print!("\nScanning keychain [{:?}]", keychain);
59+
first = false;
60+
}
61+
print!("{} ", i);
62+
// Flush early to ensure we print at every iteration.
63+
let _ = std::io::stdout().flush();
64+
});
65+
(keychain, spk_iter)
66+
})
67+
.collect();
68+
println!();
69+
70+
let (last_active_indices, graph_update, chain_update) = client
71+
.scan(
72+
keychain_spks,
73+
wallet.local_chain(),
74+
prev_tip,
75+
STOP_GAP,
76+
PARALLEL_REQUESTS,
77+
)
78+
.await?;
79+
80+
let wallet_update = Update {
81+
last_active_indices,
82+
graph: graph_update,
83+
chain: Some(chain_update),
84+
};
85+
wallet.apply_update(wallet_update)?;
86+
wallet.commit()?;
87+
println!("Scan completed.");
88+
}
89+
// Syncing: We only check for specified spks, utxos and txids to update their confirmation
90+
// status or fetch missing transactions.
91+
else {
92+
let mut spks = Box::new(Vec::new());
93+
94+
// Sync only unused SPKs
95+
if prompt("Sync only unused SPKs") {
96+
// TODO add Wallet::unused_spks() function, gives all unused tracked spks
97+
let unused_spks: Vec<ScriptBuf> = wallet
98+
.spk_index()
99+
.unused_spks(..)
100+
.into_iter()
101+
.map(|((keychain, index), script)| {
102+
eprintln!(
103+
"Checking if keychain: {:?}, index: {}, address: {} has been used",
104+
keychain,
105+
index,
106+
Address::from_script(script, network).unwrap(),
107+
);
108+
// Flush early to ensure we print at every iteration.
109+
let _ = std::io::stderr().flush();
110+
ScriptBuf::from(script)
51111
})
52-
.inspect(move |_| stdout.flush().expect("must flush"));
53-
(k, k_spks)
54-
})
55-
.collect();
56-
let (update_graph, last_active_indices) = client
57-
.scan_txs_with_keychains(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)
58-
.await?;
59-
let missing_heights = update_graph.missing_heights(wallet.local_chain());
60-
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
61-
let update = Update {
62-
last_active_indices,
63-
graph: update_graph,
64-
chain: Some(chain_update),
65-
};
66-
wallet.apply_update(update)?;
67-
wallet.commit()?;
68-
println!();
112+
.collect();
113+
spks = Box::new(unused_spks);
114+
println!("Syncing unused SPKs...");
115+
}
116+
// Sync all SPKs
117+
else if prompt("Sync all SPKs") {
118+
// TODO add Wallet::all_spks() function, gives all tracked spks
119+
let all_spks: Vec<ScriptBuf> = wallet
120+
.spk_index()
121+
.all_spks()
122+
.into_iter()
123+
.map(|((keychain, index), script)| {
124+
eprintln!(
125+
"Checking if keychain: {:?}, index: {}, address: {} has been used",
126+
keychain,
127+
index,
128+
Address::from_script(script.as_script(), network).unwrap(),
129+
);
130+
// Flush early to ensure we print at every iteration.
131+
let _ = std::io::stderr().flush();
132+
(*script).clone()
133+
})
134+
.collect();
135+
spks = Box::new(all_spks);
136+
println!("Syncing all SPKs...");
137+
}
138+
139+
// Sync UTXOs
140+
141+
// We want to search for whether our UTXOs are spent, and spent by which transaction.
142+
let outpoints: Vec<OutPoint> = wallet
143+
.list_unspent()
144+
.inspect(|utxo| {
145+
eprintln!(
146+
"Checking if outpoint {} (value: {}) has been spent",
147+
utxo.outpoint, utxo.txout.value
148+
);
149+
// Flush early to ensure we print at every iteration.
150+
let _ = std::io::stderr().flush();
151+
})
152+
.map(|utxo| utxo.outpoint)
153+
.collect();
154+
155+
// Sync unconfirmed TX
156+
157+
// We want to search for whether our unconfirmed transactions are now confirmed.
158+
// TODO add .unconfirmed_txs()
159+
let txids: Vec<Txid> = wallet
160+
.transactions()
161+
.filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
162+
.map(|canonical_tx| canonical_tx.tx_node.txid)
163+
.inspect(|txid| {
164+
eprintln!("Checking if {} is confirmed yet", txid);
165+
// Flush early to ensure we print at every iteration.
166+
let _ = std::io::stderr().flush();
167+
})
168+
.collect();
169+
170+
let (graph_update, chain_update) = client
171+
.sync(
172+
*spks,
173+
wallet.local_chain(),
174+
prev_tip,
175+
outpoints,
176+
txids,
177+
PARALLEL_REQUESTS,
178+
)
179+
.await?;
180+
181+
let wallet_update = Update {
182+
last_active_indices: Default::default(),
183+
graph: graph_update,
184+
chain: Some(chain_update),
185+
};
186+
187+
wallet.apply_update(wallet_update)?;
188+
wallet.commit()?;
189+
println!("Sync completed.");
190+
}
69191

70192
let balance = wallet.get_balance();
71-
println!("Wallet balance after syncing: {} sats", balance.total());
193+
dbg!(&balance);
194+
println!("Wallet balance after update: confirmed {} sats, trusted_pending {} sats, untrusted pending {} sats",
195+
balance.confirmed, balance.trusted_pending, balance.untrusted_pending);
72196

73197
if balance.total() < SEND_AMOUNT {
74198
println!(
@@ -78,11 +202,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
78202
std::process::exit(0);
79203
}
80204

81-
let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?
82-
.require_network(Network::Testnet)?;
205+
// Create TX to return sats to signet faucet https://signetfaucet.com/
206+
let faucet_address = Address::from_str("tb1qg3lau83hm9e9tdvzr5k7aqtw3uv0dwkfct4xdn")?
207+
.require_network(network)?;
83208

84209
let mut tx_builder = wallet.build_tx();
85210
tx_builder
211+
// .drain_to(faucet_address.script_pubkey())
212+
// .drain_wallet()
86213
.add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
87214
.enable_rbf();
88215

@@ -91,8 +218,37 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
91218
assert!(finalized);
92219

93220
let tx = psbt.extract_tx();
94-
client.broadcast(&tx).await?;
95-
println!("Tx broadcasted! Txid: {}", tx.txid());
221+
let (sent, received) = wallet.sent_and_received(&tx);
222+
let fee = wallet.calculate_fee(&tx).expect("fee");
223+
let fee_rate = wallet
224+
.calculate_fee_rate(&tx)
225+
.expect("fee rate")
226+
.as_sat_per_vb();
227+
println!(
228+
"Created tx sending {} sats to {}",
229+
sent - received - fee,
230+
faucet_address
231+
);
232+
println!(
233+
"Fee is {} sats, fee rate is {:.2} sats/vbyte",
234+
fee, fee_rate
235+
);
236+
237+
if prompt("Broadcast") {
238+
client.broadcast(&tx).await?;
239+
println!(
240+
"Tx broadcast! https://mempool.space/signet/tx/{}",
241+
tx.txid()
242+
);
243+
}
96244

97245
Ok(())
98246
}
247+
248+
fn prompt(question: &str) -> bool {
249+
print!("{}? (Y/N) ", question);
250+
std::io::stdout().flush().expect("stdout flush");
251+
let mut answer = String::new();
252+
std::io::stdin().read_line(&mut answer).expect("answer");
253+
answer.trim().to_ascii_lowercase() == "y"
254+
}

0 commit comments

Comments
 (0)