1- use std:: { collections:: BTreeSet , io:: Write } ;
2-
31use anyhow:: Ok ;
42use bdk_esplora:: { esplora_client, EsploraAsyncExt } ;
53use bdk_wallet:: {
6- bitcoin:: { Amount , Network } ,
4+ bitcoin:: { Amount , FeeRate , Network } ,
5+ psbt:: PsbtUtils ,
76 rusqlite:: Connection ,
87 KeychainKind , SignOptions , Wallet ,
98} ;
9+ use std:: { collections:: BTreeSet , io:: Write } ;
10+ use tokio:: time:: { sleep, Duration } ;
1011
1112const SEND_AMOUNT : Amount = Amount :: from_sat ( 5000 ) ;
1213const STOP_GAP : usize = 5 ;
1314const PARALLEL_REQUESTS : usize = 5 ;
1415
1516const DB_PATH : & str = "bdk-example-esplora-async.sqlite" ;
16- const NETWORK : Network = Network :: Signet ;
17+ const NETWORK : Network = Network :: Testnet4 ;
1718const EXTERNAL_DESC : & str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)" ;
1819const 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 " ;
2021
2122#[ tokio:: main]
2223async fn main ( ) -> Result < ( ) , anyhow:: Error > {
23- let mut conn = Connection :: open ( DB_PATH ) ?;
24-
24+ let mut db = Connection :: open ( DB_PATH ) ?;
2525 let wallet_opt = Wallet :: load ( )
2626 . descriptor ( KeychainKind :: External , Some ( EXTERNAL_DESC ) )
2727 . descriptor ( KeychainKind :: Internal , Some ( INTERNAL_DESC ) )
2828 . extract_keys ( )
2929 . check_network ( NETWORK )
30- . load_wallet ( & mut conn ) ?;
30+ . load_wallet ( & mut db ) ?;
3131 let mut wallet = match wallet_opt {
3232 Some ( wallet) => wallet,
3333 None => Wallet :: create ( EXTERNAL_DESC , INTERNAL_DESC )
3434 . network ( NETWORK )
35- . create_wallet ( & mut conn ) ?,
35+ . create_wallet ( & mut db ) ?,
3636 } ;
3737
3838 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) ;
4141
4242 let balance = wallet. balance ( ) ;
4343 println ! ( "Wallet balance before syncing: {}" , balance. total( ) ) ;
4444
45- print ! ( "Syncing ..." ) ;
45+ println ! ( "Full Sync ..." ) ;
4646 let client = esplora_client:: Builder :: new ( ESPLORA_URL ) . build_async ( ) ?;
4747
4848 let request = wallet. start_full_scan ( ) . inspect ( {
@@ -52,7 +52,9 @@ async fn main() -> Result<(), anyhow::Error> {
5252 if once. insert ( keychain) {
5353 print ! ( "\n Scanning keychain [{keychain:?}]" ) ;
5454 }
55- print ! ( " {spk_i:<3}" ) ;
55+ if spk_i. is_multiple_of ( 5 ) {
56+ print ! ( " {spk_i:<3}" ) ;
57+ }
5658 stdout. flush ( ) . expect ( "must flush" )
5759 }
5860 } ) ;
@@ -62,27 +64,127 @@ async fn main() -> Result<(), anyhow::Error> {
6264 . await ?;
6365
6466 wallet. apply_update ( update) ?;
65- wallet. persist ( & mut conn ) ?;
67+ wallet. persist ( & mut db ) ?;
6668 println ! ( ) ;
6769
6870 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+ ) ;
7077
7178 if balance. total ( ) < SEND_AMOUNT {
7279 println ! ( "Please send at least {SEND_AMOUNT} to the receiving address" ) ;
7380 std:: process:: exit ( 0 ) ;
7481 }
7582
83+ let target_fee_rate = FeeRate :: from_sat_per_vb ( 1 ) . unwrap ( ) ;
7684 let mut tx_builder = wallet. build_tx ( ) ;
7785 tx_builder. add_recipient ( address. script_pubkey ( ) , SEND_AMOUNT ) ;
86+ tx_builder. fee_rate ( target_fee_rate) ;
7887
7988 let mut psbt = tx_builder. finish ( ) ?;
8089 let finalized = wallet. sign ( & mut psbt, SignOptions :: default ( ) ) ?;
8190 assert ! ( finalized) ;
82-
91+ let original_fee = psbt. fee_amount ( ) . unwrap ( ) ;
92+ let tx_feerate = psbt. fee_rate ( ) . unwrap ( ) ;
8393 let tx = psbt. extract_tx ( ) ?;
8494 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+ ) ;
86188
87189 Ok ( ( ) )
88190}
0 commit comments