1
- use std:: { collections:: BTreeSet , io:: Write } ;
2
-
3
1
use anyhow:: Ok ;
4
2
use bdk_esplora:: { esplora_client, EsploraAsyncExt } ;
5
3
use bdk_wallet:: {
6
- bitcoin:: { Amount , Network } ,
4
+ bitcoin:: { Amount , FeeRate , Network } ,
5
+ psbt:: PsbtUtils ,
7
6
rusqlite:: Connection ,
8
7
KeychainKind , SignOptions , Wallet ,
9
8
} ;
9
+ use std:: { collections:: BTreeSet , io:: Write } ;
10
+ use tokio:: time:: { sleep, Duration } ;
10
11
11
12
const SEND_AMOUNT : Amount = Amount :: from_sat ( 5000 ) ;
12
13
const STOP_GAP : usize = 5 ;
13
14
const PARALLEL_REQUESTS : usize = 5 ;
14
15
15
16
const DB_PATH : & str = "bdk-example-esplora-async.sqlite" ;
16
- const NETWORK : Network = Network :: Signet ;
17
+ const NETWORK : Network = Network :: Testnet4 ;
17
18
const EXTERNAL_DESC : & str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)" ;
18
19
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 " ;
20
21
21
22
#[ tokio:: main]
22
23
async fn main ( ) -> Result < ( ) , anyhow:: Error > {
23
- let mut conn = Connection :: open ( DB_PATH ) ?;
24
-
24
+ let mut db = Connection :: open ( DB_PATH ) ?;
25
25
let wallet_opt = Wallet :: load ( )
26
26
. descriptor ( KeychainKind :: External , Some ( EXTERNAL_DESC ) )
27
27
. descriptor ( KeychainKind :: Internal , Some ( INTERNAL_DESC ) )
28
28
. extract_keys ( )
29
29
. check_network ( NETWORK )
30
- . load_wallet ( & mut conn ) ?;
30
+ . load_wallet ( & mut db ) ?;
31
31
let mut wallet = match wallet_opt {
32
32
Some ( wallet) => wallet,
33
33
None => Wallet :: create ( EXTERNAL_DESC , INTERNAL_DESC )
34
34
. network ( NETWORK )
35
- . create_wallet ( & mut conn ) ?,
35
+ . create_wallet ( & mut db ) ?,
36
36
} ;
37
37
38
38
let address = wallet. next_unused_address ( KeychainKind :: External ) ;
39
- wallet. persist ( & mut conn ) ?;
40
- println ! ( "Next unused address: ({}) {}" , address. index, address ) ;
39
+ wallet. persist ( & mut db ) ?;
40
+ println ! ( "Next unused address: ({}) {address }" , address. index) ;
41
41
42
42
let balance = wallet. balance ( ) ;
43
43
println ! ( "Wallet balance before syncing: {}" , balance. total( ) ) ;
44
44
45
- print ! ( "Syncing ..." ) ;
45
+ println ! ( "Full Sync ..." ) ;
46
46
let client = esplora_client:: Builder :: new ( ESPLORA_URL ) . build_async ( ) ?;
47
47
48
48
let request = wallet. start_full_scan ( ) . inspect ( {
@@ -52,7 +52,9 @@ async fn main() -> Result<(), anyhow::Error> {
52
52
if once. insert ( keychain) {
53
53
print ! ( "\n Scanning keychain [{keychain:?}]" ) ;
54
54
}
55
- print ! ( " {spk_i:<3}" ) ;
55
+ if spk_i. is_multiple_of ( 5 ) {
56
+ print ! ( " {spk_i:<3}" ) ;
57
+ }
56
58
stdout. flush ( ) . expect ( "must flush" )
57
59
}
58
60
} ) ;
@@ -62,27 +64,127 @@ async fn main() -> Result<(), anyhow::Error> {
62
64
. await ?;
63
65
64
66
wallet. apply_update ( update) ?;
65
- wallet. persist ( & mut conn ) ?;
67
+ wallet. persist ( & mut db ) ?;
66
68
println ! ( ) ;
67
69
68
70
let balance = wallet. balance ( ) ;
69
- println ! ( "Wallet balance after syncing: {}" , balance. total( ) ) ;
71
+ println ! ( "Wallet balance after full sync: {}" , balance. total( ) ) ;
72
+ println ! (
73
+ "Wallet has {} transactions and {} utxos after full sync" ,
74
+ wallet. transactions( ) . count( ) ,
75
+ wallet. list_unspent( ) . count( )
76
+ ) ;
70
77
71
78
if balance. total ( ) < SEND_AMOUNT {
72
79
println ! ( "Please send at least {SEND_AMOUNT} to the receiving address" ) ;
73
80
std:: process:: exit ( 0 ) ;
74
81
}
75
82
83
+ let target_fee_rate = FeeRate :: from_sat_per_vb ( 1 ) . unwrap ( ) ;
76
84
let mut tx_builder = wallet. build_tx ( ) ;
77
85
tx_builder. add_recipient ( address. script_pubkey ( ) , SEND_AMOUNT ) ;
86
+ tx_builder. fee_rate ( target_fee_rate) ;
78
87
79
88
let mut psbt = tx_builder. finish ( ) ?;
80
89
let finalized = wallet. sign ( & mut psbt, SignOptions :: default ( ) ) ?;
81
90
assert ! ( finalized) ;
82
-
91
+ let original_fee = psbt. fee_amount ( ) . unwrap ( ) ;
92
+ let tx_feerate = psbt. fee_rate ( ) . unwrap ( ) ;
83
93
let tx = psbt. extract_tx ( ) ?;
84
94
client. broadcast ( & tx) . await ?;
85
- println ! ( "Tx broadcasted! Txid: {}" , tx. compute_txid( ) ) ;
95
+ let txid = tx. compute_txid ( ) ;
96
+ println ! ( "Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}" ) ;
97
+
98
+ println ! ( "Partial Sync..." ) ;
99
+ print ! ( "SCANNING: " ) ;
100
+ let mut printed: u32 = 0 ;
101
+ let sync_request = wallet
102
+ . start_sync_with_revealed_spks ( )
103
+ . inspect ( move |_, sync_progress| {
104
+ let progress_percent =
105
+ ( 100 * sync_progress. consumed ( ) ) as f32 / sync_progress. total ( ) as f32 ;
106
+ let progress_percent = progress_percent. round ( ) as u32 ;
107
+ if progress_percent. is_multiple_of ( 5 ) && progress_percent > printed {
108
+ print ! ( "{progress_percent}% " ) ;
109
+ std:: io:: stdout ( ) . flush ( ) . expect ( "must flush" ) ;
110
+ printed = progress_percent;
111
+ }
112
+ } ) ;
113
+ let sync_update = client. sync ( sync_request, PARALLEL_REQUESTS ) . await ?;
114
+ println ! ( ) ;
115
+ wallet. apply_update ( sync_update) ?;
116
+ wallet. persist ( & mut db) ?;
117
+
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 ( ) ;
120
+ let mut builder = wallet. build_fee_bump ( txid) . expect ( "failed to bump tx" ) ;
121
+ builder. fee_rate ( feerate) ;
122
+ let mut bumped_psbt = builder. finish ( ) . unwrap ( ) ;
123
+ let finalize_btx = wallet. sign ( & mut bumped_psbt, SignOptions :: default ( ) ) ?;
124
+ assert ! ( finalize_btx) ;
125
+ let new_fee = bumped_psbt. fee_amount ( ) . unwrap ( ) ;
126
+ let bumped_tx = bumped_psbt. extract_tx ( ) ?;
127
+ assert_eq ! (
128
+ bumped_tx
129
+ . output
130
+ . iter( )
131
+ . find( |txout| txout. script_pubkey == address. script_pubkey( ) )
132
+ . unwrap( )
133
+ . value,
134
+ SEND_AMOUNT ,
135
+ "Outputs should be the same"
136
+ ) ;
137
+ assert ! (
138
+ new_fee > original_fee,
139
+ "New fee ({new_fee}) should be higher than original ({original_fee})" ,
140
+ ) ;
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 ;
144
+ client. broadcast ( & bumped_tx) . await ?;
145
+ println ! (
146
+ "Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}" ,
147
+ bumped_tx. compute_txid( )
148
+ ) ;
149
+
150
+ println ! ( "syncing after broadcasting bumped tx..." ) ;
151
+ print ! ( "SCANNING: " ) ;
152
+ let sync_request = wallet
153
+ . start_sync_with_revealed_spks ( )
154
+ . inspect ( move |_, sync_progress| {
155
+ let progress_percent =
156
+ ( 100 * sync_progress. consumed ( ) ) as f32 / sync_progress. total ( ) as f32 ;
157
+ let progress_percent = progress_percent. round ( ) as u32 ;
158
+ if progress_percent. is_multiple_of ( 10 ) && progress_percent > printed {
159
+ print ! ( "{progress_percent}% " ) ;
160
+ std:: io:: stdout ( ) . flush ( ) . expect ( "must flush" ) ;
161
+ printed = progress_percent;
162
+ }
163
+ } ) ;
164
+ let sync_update = client. sync ( sync_request, PARALLEL_REQUESTS ) . await ?;
165
+ println ! ( ) ;
166
+
167
+ let mut evicted_txs = Vec :: new ( ) ;
168
+
169
+ for ( txid, last_seen) in & sync_update. tx_update . evicted_ats {
170
+ evicted_txs. push ( ( * txid, * last_seen) ) ;
171
+ }
172
+
173
+ wallet. apply_update ( sync_update) ?;
174
+
175
+ if !evicted_txs. is_empty ( ) {
176
+ println ! ( "Applied {} evicted transactions" , evicted_txs. len( ) ) ;
177
+ }
178
+
179
+ wallet. persist ( & mut db) ?;
180
+
181
+ let balance_after_sync = wallet. balance ( ) ;
182
+ println ! ( "Wallet balance after sync: {}" , balance_after_sync. total( ) ) ;
183
+ println ! (
184
+ "Wallet has {} transactions and {} utxos after partial sync" ,
185
+ wallet. transactions( ) . count( ) ,
186
+ wallet. list_unspent( ) . count( )
187
+ ) ;
86
188
87
189
Ok ( ( ) )
88
190
}
0 commit comments