-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathelectrum.rs
More file actions
195 lines (167 loc) · 7.25 KB
/
electrum.rs
File metadata and controls
195 lines (167 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
mod common;
use bdk_electrum::electrum_client;
use bdk_electrum::BdkElectrumClient;
use bdk_wallet::bitcoin::key::Secp256k1;
use bdk_wallet::bitcoin::Amount;
use bdk_wallet::bitcoin::FeeRate;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::chain::collections::HashSet;
use bdk_wallet::miniscript::Descriptor;
use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::rusqlite::Connection;
use bdk_wallet::Wallet;
use bdk_wallet::{KeychainKind, SignOptions};
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
const STOP_GAP: usize = 50;
const BATCH_SIZE: usize = 5;
const DB_PATH: &str = "bdk-example-electrum.sqlite";
const NETWORK: Network = Network::Testnet4;
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
const ELECTRUM_URL: &str = "ssl://mempool.space:40002";
fn main() -> Result<(), anyhow::Error> {
let mut db = Connection::open(DB_PATH)?;
let wallet_opt = Wallet::load()
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
.extract_keys()
.check_network(NETWORK)
.load_wallet(&mut db)?;
let mut wallet = match wallet_opt {
Some(wallet) => wallet,
None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
.network(NETWORK)
.create_wallet(&mut db)?,
};
let address = wallet.next_unused_address(KeychainKind::External);
wallet.persist(&mut db)?;
println!("Generated Address: {address}");
let balance = wallet.balance();
println!("Wallet balance before syncing: {}", balance.total());
println!("Performing Full Sync...");
let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?);
// Populate the electrum client's transaction cache so it doesn't redownload transaction we
// already have.
client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
let request = wallet.start_full_scan().inspect({
let mut stdout = std::io::stdout();
let mut once = HashSet::<KeychainKind>::new();
move |k, spk_i, _| {
if once.insert(k) {
print!("\nScanning keychain [{k:?}]");
}
print!(" {spk_i:<3}");
stdout.flush().expect("must flush");
}
});
let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
println!();
wallet.apply_update(update)?;
wallet.persist(&mut db)?;
let balance = wallet.balance();
println!("Wallet balance after full sync: {}", balance.total());
println!(
"Wallet has {} transactions and {} utxos after full sync",
wallet.transactions().count(),
wallet.list_unspent().count()
);
if balance.total() < SEND_AMOUNT {
println!("Please send at least {SEND_AMOUNT} to the receiving address");
std::process::exit(0);
}
let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
let mut tx_builder = wallet.build_tx();
tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT);
tx_builder.fee_rate(target_fee_rate);
let mut psbt = tx_builder.finish()?;
let secp = Secp256k1::new();
let (_, external_keymap) = Descriptor::parse_descriptor(&secp, EXTERNAL_DESC)?;
let (_, internal_keymap) = Descriptor::parse_descriptor(&secp, INTERNAL_DESC)?;
let key_map = external_keymap.into_iter().chain(internal_keymap).collect();
// Using the signer implementation from the common module.
// Note: This is temporary until miniscript 12.x is released with native KeyMap signing.
let signer = common::SignerWrapper::new(key_map);
let _ = psbt.sign(&signer, &secp);
let finalized = wallet.finalize_psbt(&mut psbt, SignOptions::default())?;
assert!(finalized);
let original_fee = psbt.fee_amount().unwrap();
let tx_feerate = psbt.fee_rate().unwrap();
let tx = psbt.extract_tx()?;
client.transaction_broadcast(&tx)?;
let txid = tx.compute_txid();
println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}");
println!("Partial Sync...");
print!("SCANNING: ");
let mut last_printed = 0;
let sync_request = wallet
.start_sync_with_revealed_spks()
.inspect(move |_, sync_progress| {
let progress_percent =
(100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32;
let progress_percent = progress_percent.round() as u32;
if progress_percent % 5 == 0 && progress_percent > last_printed {
print!("{progress_percent}% ");
std::io::stdout().flush().expect("must flush");
last_printed = progress_percent;
}
});
client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
let sync_update = client.sync(sync_request, BATCH_SIZE, false)?;
println!();
wallet.apply_update(sync_update)?;
wallet.persist(&mut db)?;
// bump fee rate for tx by at least 1 sat per vbyte
let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap();
let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx");
builder.fee_rate(feerate);
let mut bumped_psbt = builder.finish().unwrap();
let _ = bumped_psbt.sign(&signer, &secp);
let finalize_btx = wallet.finalize_psbt(&mut bumped_psbt, SignOptions::default())?;
assert!(finalize_btx);
let new_fee = bumped_psbt.fee_amount().unwrap();
let bumped_tx = bumped_psbt.extract_tx()?;
assert_eq!(
bumped_tx
.output
.iter()
.find(|txout| txout.script_pubkey == address.script_pubkey())
.unwrap()
.value,
SEND_AMOUNT,
"Recipient output should remain unchanged"
);
assert!(
new_fee > original_fee,
"New fee ({new_fee}) should be higher than original ({original_fee})"
);
// wait for first transaction to make it into the mempool and be indexed on mempool.space
sleep(Duration::from_secs(10));
client.transaction_broadcast(&bumped_tx)?;
println!(
"Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}",
bumped_tx.compute_txid()
);
println!("Syncing after bumped tx broadcast...");
let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {});
let sync_update = client.sync(sync_request, BATCH_SIZE, false)?;
let mut evicted_txs = Vec::new();
for (txid, last_seen) in &sync_update.tx_update.evicted_ats {
evicted_txs.push((*txid, *last_seen));
}
wallet.apply_update(sync_update)?;
if !evicted_txs.is_empty() {
println!("Applied {} evicted transactions", evicted_txs.len());
}
wallet.persist(&mut db)?;
let balance_after_sync = wallet.balance();
println!("Wallet balance after sync: {}", balance_after_sync.total());
println!(
"Wallet has {} transactions and {} utxos after partial sync",
wallet.transactions().count(),
wallet.list_unspent().count()
);
Ok(())
}