1
1
use std:: { io:: Write , str:: FromStr } ;
2
2
3
+ use bdk:: bitcoin:: { OutPoint , ScriptBuf , Txid } ;
3
4
use bdk:: {
4
5
bitcoin:: { Address , Network } ,
5
6
wallet:: { AddressIndex , Update } ,
@@ -9,66 +10,189 @@ use bdk_esplora::{esplora_client, EsploraAsyncExt};
9
10
use bdk_file_store:: Store ;
10
11
11
12
const DB_MAGIC : & str = "bdk_wallet_esplora_async_example" ;
13
+ const CHAIN_DATA_FILE : & str = "chain.dat" ;
12
14
const SEND_AMOUNT : u64 = 5000 ;
13
15
const STOP_GAP : usize = 50 ;
14
16
const PARALLEL_REQUESTS : usize = 5 ;
17
+ const ESPLORA_SERVER_URL : & str = "http://signet.bitcoindevkit.net" ;
15
18
16
19
#[ tokio:: main]
17
20
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 ) ?;
20
25
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)" ;
21
26
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)" ;
22
27
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) ?;
29
30
30
31
let address = wallet. get_address ( AddressIndex :: New ) ;
31
32
println ! ( "Generated Address: {}" , address) ;
32
33
33
34
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 ) ;
35
36
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 ( ) ?;
39
39
40
+ // Get wallet's previous chain tip
40
41
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 ! ( "\n Scanning 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 ! ( "\n Scanning 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)
51
111
} )
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
+ }
69
191
70
192
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) ;
72
196
73
197
if balance. total ( ) < SEND_AMOUNT {
74
198
println ! (
@@ -78,11 +202,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
78
202
std:: process:: exit ( 0 ) ;
79
203
}
80
204
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) ?;
83
208
84
209
let mut tx_builder = wallet. build_tx ( ) ;
85
210
tx_builder
211
+ // .drain_to(faucet_address.script_pubkey())
212
+ // .drain_wallet()
86
213
. add_recipient ( faucet_address. script_pubkey ( ) , SEND_AMOUNT )
87
214
. enable_rbf ( ) ;
88
215
@@ -91,8 +218,37 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
91
218
assert ! ( finalized) ;
92
219
93
220
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
+ }
96
244
97
245
Ok ( ( ) )
98
246
}
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