From: Vihiga Tyonum Date: Sat, 4 Jul 2026 10:38:46 +0000 (+0100) Subject: feat(WalletEvents): Add wallet events to clients X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/FromScriptException.UnrecognizedScript.html?a=commitdiff_plain;h=6a6390fcc5b823e1901a7105fa424689947e7dbf;p=bdk-cli feat(WalletEvents): Add wallet events to clients - Update bdk_wallet to v2.4.0 and other deps - Add wallet events to RPC, electrum, esplora and kyoto clients --- diff --git a/Cargo.lock b/Cargo.lock index 93134ad..407c962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "bdk_bitcoind_rpc" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a52a57cc5e07e207711a5064def0bd04b18e183b9f2247f41561cb12dd393be" +checksum = "99a9a7a93a0c377d1858263122afe0565e6b38800fe39b2025f217e01f146294" dependencies = [ "bdk_core", "bitcoin", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "bdk_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dbbe4aad0c898bfeb5253c222be3ea3dccfb380a07e72c87e3e4ed6664a6753" +checksum = "cb3028782f6bf14a6df987244333d34e6b272b5a40a53e4879ec2dfd82275a3a" dependencies = [ "bitcoin", "hashbrown 0.14.5", @@ -270,9 +270,9 @@ dependencies = [ [[package]] name = "bdk_electrum" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b59a3f7fbe678874fa34354097644a171276e02a49934c13b3d61c54610ddf39" +checksum = "00a9846105bf6e751adbb6946b000ff919ce24a15a941cbfe68485549b552a9c" dependencies = [ "bdk_core", "electrum-client", @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "bdk_esplora" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f5961444b5f51b9c3937e729a212363d0e4cde6390ded6e01e16292078df4" +checksum = "83986307ea92997c3d051e8c306af8115a05add601e22acb7c1903008e6b614e" dependencies = [ "async-trait", "bdk_core", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "bdk_wallet" -version = "2.1.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30b5dba770184863b5d966ccbc6a11d12c145450be3b6a4435308297e6a12dc" +checksum = "c99821af39c7df004bd411ece2ef22d9fba5a631b4489f3d9a0ac0d19637e2d0" dependencies = [ "bdk_chain", "bip39", @@ -958,9 +958,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "electrum-client" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5059f13888a90486e7268bbce59b175f5f76b1c55e5b9c568ceaa42d2b8507c" +checksum = "1970c5d7bd9de6d4041cbfc3e46faa3e85fe7efcea2b0eb06750d3ae1ef577b7" dependencies = [ "bitcoin", "byteorder", @@ -1014,9 +1014,9 @@ dependencies = [ [[package]] name = "esplora-client" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0af349d96a5d9ad77ba59f1437aa6f348b03c5865d4f7d6e7a662d60aedce39" +checksum = "f19e3ea99dbfbef0c1ec26d83e69de0c579f6aa6aaac4f44597805fcc27e97af" dependencies = [ "bitcoin", "hex-conservative 0.2.2", @@ -1024,6 +1024,7 @@ dependencies = [ "minreq", "reqwest 0.12.28", "serde", + "serde_json", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 8f76de0..36ff484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" license = "MIT" [dependencies] -bdk_wallet = { version = "2.1.0", features = ["rusqlite", "keys-bip39", "compiler", "std"] } +bdk_wallet = { version = "2.4.0", features = ["rusqlite", "keys-bip39", "compiler", "std"] } clap = { version = "4.6", features = ["derive","env"] } clap_complete = "4.6" dirs = { version = "6.0.0" } @@ -28,9 +28,9 @@ toml = "1.1.0" serde= {version = "1.0", features = ["derive"]} # Optional dependencies -bdk_bitcoind_rpc = { version = "0.21.0", features = ["std"], optional = true } -bdk_electrum = { version = "0.23.2", optional = true } -bdk_esplora = { version = "0.22.1", features = ["async-https", "tokio"], optional = true } +bdk_bitcoind_rpc = { version = "0.22.0", features = ["std"], optional = true } +bdk_electrum = { version = "0.24.0", optional = true } +bdk_esplora = { version = "0.22.2", features = ["async-https", "tokio"], optional = true } bdk_kyoto = { version = "0.15.4", optional = true } bdk_redb = { version = "0.1.1", optional = true } bdk_sp = { version = "0.1.0", optional = true, git = "https://github.com/bitcoindevkit/bdk-sp", tag = "v0.1.0" } diff --git a/src/client.rs b/src/client.rs index ad51391..483183c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -308,9 +308,10 @@ pub async fn sync_kyoto_client( let update = handle.update_subscriber.lock().await.update().await?; tracing::info!("Received update: applying to wallet"); - wallet - .apply_update(update) + let events = wallet + .apply_update_events(update) .map_err(|e| Error::Generic(format!("Failed to apply update: {e}")))?; + crate::utils::print_wallet_events(&events); tracing::info!( "Chain tip: {}, Transactions: {}, Balance: {}", diff --git a/src/handlers/online.rs b/src/handlers/online.rs index f4fc062..2416e9d 100644 --- a/src/handlers/online.rs +++ b/src/handlers/online.rs @@ -29,6 +29,7 @@ use { crate::payjoin::{PayjoinManager, ohttp::RelayManager}, crate::utils::is_final, crate::utils::output::FormatOutput, + crate::utils::print_wallet_events, crate::utils::types::{StatusResult, TransactionResult}, bdk_wallet::bitcoin::{ Psbt, Transaction, Txid, base64::Engine, base64::prelude::BASE64_STANDARD, @@ -112,7 +113,8 @@ impl AsyncAppCommand>> for FullScanCommand { Electrum { client, batch_size } => { client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); let update = client.full_scan(request, self.stop_gap, *batch_size, false)?; - wallet.apply_update(update)?; + let events = wallet.apply_update_events(update)?; + print_wallet_events(&events); } #[cfg(feature = "esplora")] @@ -124,7 +126,8 @@ impl AsyncAppCommand>> for FullScanCommand { .full_scan(request, self.stop_gap, *parallel_requests) .await .map_err(|e| *e)?; - wallet.apply_update(update)?; + let events = wallet.apply_update_events(update)?; + print_wallet_events(&events); } #[cfg(feature = "rpc")] RpcClient { client } => { @@ -142,27 +145,35 @@ impl AsyncAppCommand>> for FullScanCommand { NO_EXPECTED_MEMPOOL_TXS, ); - while let Some(block_event) = emitter.next_block()? { - if block_event.block_height() % 10_000 == 0 { - let percent_done = f64::from(block_event.block_height()) - / f64::from(blockchain_info.headers as u32) - * 100f64; - println!( - "Applying block at height: {}, {:.2}% done.", + let block_events = wallet.events_helper(|w| { + while let Some(block_event) = emitter.next_block()? { + if block_event.block_height() % 10_000 == 0 { + let percent_done = f64::from(block_event.block_height()) + / f64::from(blockchain_info.headers as u32) + * 100f64; + println!( + "Applying block at height: {}, {:.2}% done.", + block_event.block_height(), + percent_done + ); + } + + w.apply_block_connected_to( + &block_event.block, block_event.block_height(), - percent_done - ); + block_event.connected_to(), + )?; } - - wallet.apply_block_connected_to( - &block_event.block, - block_event.block_height(), - block_event.connected_to(), - )?; - } + Ok::<_, Error>(()) + })?; + print_wallet_events(&block_events); let mempool_txs = emitter.mempool()?; - wallet.apply_unconfirmed_txs(mempool_txs.update); + let mempool_events = wallet.apply_unconfirmed_txs_events(mempool_txs.update); + print_wallet_events(&mempool_events); + + let evicted_events = wallet.apply_evicted_txs_events(mempool_txs.evicted); + print_wallet_events(&evicted_events); } #[cfg(feature = "cbf")] @@ -210,9 +221,8 @@ impl AsyncAppCommand>> for SyncCommand { client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); let update = client.sync(request, *batch_size, false)?; - wallet - .apply_update(update) - .map_err(|e| Error::Generic(e.to_string()))?; + let events = wallet.apply_update_events(update)?; + print_wallet_events(&events); } #[cfg(feature = "esplora")] Esplora { @@ -223,9 +233,8 @@ impl AsyncAppCommand>> for SyncCommand { .sync(request, *parallel_requests) .await .map_err(|e| *e)?; - wallet - .apply_update(update) - .map_err(|e| Error::Generic(e.to_string()))?; + let events = wallet.apply_update_events(update)?; + print_wallet_events(&events); } #[cfg(feature = "rpc")] RpcClient { client } => { @@ -247,27 +256,35 @@ impl AsyncAppCommand>> for SyncCommand { .filter(|tx| tx.chain_position.is_unconfirmed()), ); - while let Some(block_event) = emitter.next_block()? { - if block_event.block_height() % 10_000 == 0 { - let percent_done = f64::from(block_event.block_height()) - / f64::from(blockchain_info.headers as u32) - * 100f64; - println!( - "Applying block at height: {}, {:.2}% done.", + let block_events = wallet.events_helper(|w| { + while let Some(block_event) = emitter.next_block()? { + if block_event.block_height() % 10_000 == 0 { + let percent_done = f64::from(block_event.block_height()) + / f64::from(blockchain_info.headers as u32) + * 100f64; + println!( + "Applying block at height: {}, {:.2}% done.", + block_event.block_height(), + percent_done + ); + } + + w.apply_block_connected_to( + &block_event.block, block_event.block_height(), - percent_done - ); + block_event.connected_to(), + )?; } - - wallet.apply_block_connected_to( - &block_event.block, - block_event.block_height(), - block_event.connected_to(), - )?; - } + Ok::<_, Error>(()) + })?; + print_wallet_events(&block_events); let mempool_txs = emitter.mempool()?; - wallet.apply_unconfirmed_txs(mempool_txs.update); + let mempool_events = wallet.apply_unconfirmed_txs_events(mempool_txs.update); + print_wallet_events(&mempool_events); + + let evicted_events = wallet.apply_evicted_txs_events(mempool_txs.evicted); + print_wallet_events(&evicted_events); } #[cfg(feature = "cbf")] KyotoClient { client } => sync_kyoto_client(wallet, client) diff --git a/src/utils/common.rs b/src/utils/common.rs index 21be3ad..4a2fad5 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -11,7 +11,7 @@ use bdk_sp::encoding::SilentPaymentCode; feature = "cbf", feature = "rpc" ))] -use bdk_wallet::bitcoin::Psbt; +use bdk_wallet::{bitcoin::Psbt, event::WalletEvent}; use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf}; use crate::commands::OfflineWalletSubCommand; @@ -245,3 +245,64 @@ pub fn command_requires_db(command: &OfflineWalletSubCommand) -> bool { OfflineWalletSubCommand::CreateSpTx(_) => false, } } + +/// Print a human-readable summary of the wallet events produced by a sync or scan. +/// +/// Emitted to stderr so it does not pollute the JSON result on stdout. +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf" +))] +pub fn print_wallet_events(events: &[WalletEvent]) { + for event in events { + match event { + WalletEvent::ChainTipChanged { old_tip, new_tip } => { + eprintln!( + "Chain tip advanced from height {} to {}", + old_tip.height, new_tip.height + ); + } + WalletEvent::TxConfirmed { + txid, + block_time, + old_block_time, + .. + } => match old_block_time { + Some(old) => eprintln!( + "Transaction {txid} re-confirmed at height {} (was height {}, likely a reorg)", + block_time.block_id.height, old.block_id.height + ), + None => eprintln!( + "Transaction {txid} confirmed at height {}", + block_time.block_id.height + ), + }, + WalletEvent::TxUnconfirmed { + txid, + old_block_time, + .. + } => match old_block_time { + Some(old) => eprintln!( + "Transaction {txid} became unconfirmed (was confirmed at height {}, likely a reorg)", + old.block_id.height + ), + None => eprintln!("Transaction {txid} seen in mempool"), + }, + WalletEvent::TxReplaced { + txid, conflicts, .. + } => { + let ids: Vec = conflicts.iter().map(|(_, c)| c.to_string()).collect(); + eprintln!( + "Transaction {txid} was replaced (conflicts with: {})", + ids.join(", ") + ); + } + WalletEvent::TxDropped { txid, .. } => { + eprintln!("Transaction {txid} dropped from the mempool"); + } + _ => {} + } + } +}