]> Untitled Git - bdk/commitdiff
chore: Remove example_cli
authorKyle 🐆 <kyle@privkey.io>
Mon, 25 May 2026 00:22:17 +0000 (20:22 -0400)
committervalued mammal <valuedmammal@protonmail.com>
Mon, 25 May 2026 14:20:16 +0000 (10:20 -0400)
Remove the standalone example crates under examples/ and clean up
workspace, docs, and CI references that depended on them.

This commit only does the removal and cleanup. Adding targeted
rustdoc examples is deferred to follow-up PRs.

14 files changed:
.github/workflows/cont_integration.yml
Cargo.toml
README.md
crates/electrum/src/lib.rs
crates/esplora/README.md
examples/example_bitcoind_rpc_polling/Cargo.toml [deleted file]
examples/example_bitcoind_rpc_polling/README.md [deleted file]
examples/example_bitcoind_rpc_polling/src/main.rs [deleted file]
examples/example_cli/Cargo.toml [deleted file]
examples/example_cli/src/lib.rs [deleted file]
examples/example_electrum/Cargo.toml [deleted file]
examples/example_electrum/src/main.rs [deleted file]
examples/example_esplora/Cargo.toml [deleted file]
examples/example_esplora/src/main.rs [deleted file]

index 8d06dd97bf152e411a5b893aabfb5d965037b7dd..1a5c384449a84e1ec623a474b9a627f1204d62e7 100644 (file)
@@ -49,8 +49,8 @@ jobs:
         env:
           MATRIX_RUST_VERSION: ${{ matrix.rust.version }}
         run: |
-            cargo build --workspace --exclude 'example_*' ${{ matrix.features }}
-            cargo test --workspace --exclude 'example_*' ${{ matrix.features }}
+            cargo build --workspace ${{ matrix.features }}
+            cargo test --workspace ${{ matrix.features }}
 
   check-no-std:
     needs: prepare
@@ -141,32 +141,6 @@ jobs:
       - name: Clippy
         run: cargo clippy --all-features --all-targets -- -D warnings
 
-  build-examples:
-    needs: prepare
-    name: Build & Test Examples
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        example-dir:
-          - example_cli
-          - example_bitcoind_rpc_polling
-          - example_electrum
-          - example_esplora
-    steps:
-      - name: checkout
-        uses: actions/checkout@v6
-        with:
-          persist-credentials: false
-      - name: Install Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1
-        with:
-          toolchain: ${{ needs.prepare.outputs.rust_version }}
-          override: true
-          cache: true
-      - name: Build
-        working-directory: examples/${{ matrix.example-dir }}
-        run: cargo build
-
   check-docs:
     needs: prepare
     name: Check documentation errors
index d505c1a0a9908844cc0eac359fde67d240ea43c0..fc0253b16c6662b3d9afbbc0e3c9d39c7c37656e 100644 (file)
@@ -8,10 +8,6 @@ members = [
     "crates/esplora",
     "crates/bitcoind_rpc",
     "crates/testenv",
-    "examples/example_cli",
-    "examples/example_electrum",
-    "examples/example_esplora",
-    "examples/example_bitcoind_rpc_polling",
 ]
 
 [workspace.package]
index 472222ad6c98ed250d3a9a5fa91e86d08713bc74..45fc46807f9a010ae52a2e7a63529e46880ed6da 100644 (file)
--- a/README.md
+++ b/README.md
@@ -41,13 +41,6 @@ The workspace in this repository contains several crates in the `/crates` direct
 
 The [`bdk_wallet`] repository and crate contains a higher level `Wallet` type that depends on the above lower-level mechanism crates.
 
-Fully working examples of how to use these components are in `/examples`:
-
-- [`example_cli`](examples/example_cli): Library used by the `example_*` crates. Provides utilities for syncing, showing the balance, generating addresses and creating transactions without using the bdk_wallet `Wallet`.
-- [`example_electrum`](examples/example_electrum): A command line Bitcoin wallet application built on top of `example_cli` and the `electrum` crate. It shows the power of the bdk tools (`chain` + `file_store` + `electrum`), without depending on the main `bdk_wallet` library.
-- [`example_esplora`](examples/example_esplora): A command line Bitcoin wallet application built on top of `example_cli` and the `esplora` crate. It shows the power of the bdk tools (`chain` + `file_store` + `esplora`), without depending on the main `bdk_wallet` library.
-- [`example_bitcoind_rpc_polling`](examples/example_bitcoind_rpc_polling): A command line Bitcoin wallet application built on top of `example_cli` and the `bitcoind_rpc` crate. It shows the power of the bdk tools (`chain` + `file_store` + `bitcoind_rpc`), without depending on the main `bdk_wallet` library.
-
 [`rust-miniscript`]: https://github.com/rust-bitcoin/rust-miniscript
 [`rust-bitcoin`]: https://github.com/rust-bitcoin/rust-bitcoin
 [`esplora-client`]: https://docs.rs/esplora-client/
index 9c1d9f452c93453a6f4a0f324d39b2eb277851bc..7af8b847ddb8cbab46388a97b8c34511324e7dc7 100644 (file)
@@ -11,9 +11,6 @@
 //! where the range of possibly used scripts is not known. In this case it is necessary to scan all
 //! keychain scripts until a number (the "stop gap") of unused scripts is discovered.
 //!
-//! Refer to [`example_electrum`] for a complete example.
-//!
-//! [`example_electrum`]: https://github.com/bitcoindevkit/bdk/tree/master/examples/example_electrum
 //! [`SyncResponse`]: bdk_core::spk_client::SyncResponse
 //! [`FullScanResponse`]: bdk_core::spk_client::FullScanResponse
 #![cfg_attr(coverage_nightly, feature(coverage_attribute))]
index 4a6acb377dc2c8675d6916232ae40d13b68ec6b7..d82afe1a2dcb0f7bf2837dbe9bd39b6bfceac188 100644 (file)
@@ -41,8 +41,6 @@ use bdk_esplora::EsploraExt;
 use bdk_esplora::EsploraAsyncExt;
 ```
 
-For full examples, refer to [`example_wallet_esplora_blocking`](https://github.com/bitcoindevkit/bdk/tree/master/examples/example_wallet_esplora_blocking) and [`example_wallet_esplora_async`](https://github.com/bitcoindevkit/bdk/tree/master/examples/example_wallet_esplora_async).
-
 [`esplora-client`]: https://docs.rs/esplora-client/
 [`bdk_chain`]: https://docs.rs/bdk-chain/
 [`EsploraExt`]: crate::EsploraExt
diff --git a/examples/example_bitcoind_rpc_polling/Cargo.toml b/examples/example_bitcoind_rpc_polling/Cargo.toml
deleted file mode 100644 (file)
index 6728bb1..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "example_bitcoind_rpc_polling"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-bdk_chain = { path = "../../crates/chain", features = ["serde"] }
-bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" }
-example_cli = { path = "../example_cli" }
-ctrlc = { version = "^2" }
diff --git a/examples/example_bitcoind_rpc_polling/README.md b/examples/example_bitcoind_rpc_polling/README.md
deleted file mode 100644 (file)
index 1ffbc40..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-# Example RPC CLI
-
-### Simple Regtest Test
-
-1. Start local regtest bitcoind.
-   ```
-    mkdir -p /tmp/regtest/bitcoind
-    bitcoind -regtest -server -fallbackfee=0.0002 -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> -datadir=/tmp/regtest/bitcoind -daemon
-   ```
-2. Create a test bitcoind wallet and set bitcoind env.
-   ```
-   bitcoin-cli -datadir=/tmp/regtest/bitcoind -regtest -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> -named createwallet wallet_name="test"
-   export RPC_URL=127.0.0.1:18443
-   export RPC_USER=<your-rpc-username>
-   export RPC_PASS=<your-rpc-password>
-   ```
-3. Get test bitcoind wallet info.
-   ```
-   bitcoin-cli -rpcwallet="test" -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> -datadir=/tmp/regtest/bitcoind -regtest getwalletinfo
-   ```
-4. Get new test bitcoind wallet address.
-   ```
-   BITCOIND_ADDRESS=$(bitcoin-cli -rpcwallet="test" -datadir=/tmp/regtest/bitcoind -regtest -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> getnewaddress)
-   echo $BITCOIND_ADDRESS
-   ```
-5. Generate 101 blocks with reward to test bitcoind wallet address.
-   ```
-   bitcoin-cli -datadir=/tmp/regtest/bitcoind -regtest -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> generatetoaddress 101 $BITCOIND_ADDRESS
-   ```
-6. Verify test bitcoind wallet balance.
-   ```
-   bitcoin-cli -rpcwallet="test" -datadir=/tmp/regtest/bitcoind -regtest -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> getbalances
-   ```
-7. Set descriptor env and get address from RPC CLI wallet.
-   ```
-   export DESCRIPTOR="wpkh(tprv8ZgxMBicQKsPfK9BTf82oQkHhawtZv19CorqQKPFeaHDMA4dXYX6eWsJGNJ7VTQXWmoHdrfjCYuDijcRmNFwSKcVhswzqs4fugE8turndGc/1/*)"
-   cargo run -- init --network regtest
-   cargo run -- address next
-   ```
-8. Send 0.05 test bitcoin to RPC CLI wallet.
-   ```
-   bitcoin-cli -rpcwallet="test" -datadir=/tmp/regtest/bitcoind -regtest -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> sendtoaddress <address> 0.05
-   ```
-9. Sync blockchain with RPC CLI wallet.
-   ```
-   cargo run -- sync
-   ```
-10. Get RPC CLI wallet unconfirmed balances.
-   ```
-   cargo run -- balance
-   ```
-11. Generate 1 block with reward to test bitcoind wallet address.
-   ```
-   bitcoin-cli -datadir=/tmp/regtest/bitcoind -rpcuser=<your-rpc-username> -rpcpassword=<your-rpc-password> -regtest generatetoaddress 1 $BITCOIND_ADDRESS
-   ```
-12. Sync the blockchain with RPC CLI wallet.
-   ```
-   cargo run -- sync
-   ```
-13. Get RPC CLI wallet confirmed balances.
-   ```
-   cargo run -- balance
-   ```
-14. Get RPC CLI wallet transactions.
-   ```
-   cargo run -- txout list
-   ```
diff --git a/examples/example_bitcoind_rpc_polling/src/main.rs b/examples/example_bitcoind_rpc_polling/src/main.rs
deleted file mode 100644 (file)
index 0263c5b..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-use std::{
-    path::PathBuf,
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc,
-    },
-    time::{Duration, Instant},
-};
-
-use bdk_bitcoind_rpc::{
-    bitcoincore_rpc::{Auth, Client, RpcApi},
-    Emitter,
-};
-use bdk_chain::{bitcoin::Block, local_chain, CanonicalizationParams, Merge};
-use example_cli::{
-    anyhow,
-    clap::{self, Args, Subcommand},
-    ChangeSet, Keychain,
-};
-
-const DB_MAGIC: &[u8] = b"bdk_example_rpc";
-const DB_PATH: &str = ".bdk_example_rpc.db";
-
-/// The mpsc channel bound for emissions from [`Emitter`].
-const CHANNEL_BOUND: usize = 10;
-/// Delay for printing status to stdout.
-const STDOUT_PRINT_DELAY: Duration = Duration::from_secs(6);
-/// Delay between mempool emissions.
-const MEMPOOL_EMIT_DELAY: Duration = Duration::from_secs(30);
-/// Delay for committing to persistence.
-const DB_COMMIT_DELAY: Duration = Duration::from_secs(60);
-
-#[derive(Debug)]
-enum Emission {
-    Block(bdk_bitcoind_rpc::BlockEvent<Block>),
-    Mempool(bdk_bitcoind_rpc::MempoolEvent),
-    Tip(u32),
-}
-
-#[derive(Args, Debug, Clone)]
-struct RpcArgs {
-    /// RPC URL
-    #[clap(env = "RPC_URL", long, default_value = "127.0.0.1:8332")]
-    url: String,
-    /// RPC auth cookie file
-    #[clap(env = "RPC_COOKIE", long)]
-    rpc_cookie: Option<PathBuf>,
-    /// RPC auth username
-    #[clap(env = "RPC_USER", long)]
-    rpc_user: Option<String>,
-    /// RPC auth password
-    #[clap(env = "RPC_PASS", long)]
-    rpc_password: Option<String>,
-    /// Starting block height to fallback to if no point of agreement if found
-    #[clap(env = "FALLBACK_HEIGHT", long, default_value = "0")]
-    fallback_height: u32,
-}
-
-impl From<RpcArgs> for Auth {
-    fn from(args: RpcArgs) -> Self {
-        match (args.rpc_cookie, args.rpc_user, args.rpc_password) {
-            (None, None, None) => Self::None,
-            (Some(path), _, _) => Self::CookieFile(path),
-            (_, Some(user), Some(pass)) => Self::UserPass(user, pass),
-            (_, Some(_), None) => panic!("rpc auth: missing rpc_pass"),
-            (_, None, Some(_)) => panic!("rpc auth: missing rpc_user"),
-        }
-    }
-}
-
-impl RpcArgs {
-    fn new_client(&self) -> anyhow::Result<Client> {
-        Ok(Client::new(
-            &self.url,
-            match (&self.rpc_cookie, &self.rpc_user, &self.rpc_password) {
-                (None, None, None) => Auth::None,
-                (Some(path), _, _) => Auth::CookieFile(path.clone()),
-                (_, Some(user), Some(pass)) => Auth::UserPass(user.clone(), pass.clone()),
-                (_, Some(_), None) => panic!("rpc auth: missing rpc_pass"),
-                (_, None, Some(_)) => panic!("rpc auth: missing rpc_user"),
-            },
-        )?)
-    }
-}
-
-#[derive(Subcommand, Debug, Clone)]
-enum RpcCommands {
-    /// Syncs local state with remote state via RPC (starting from last point of agreement) and
-    /// stores/indexes relevant transactions
-    Sync {
-        #[clap(flatten)]
-        rpc_args: RpcArgs,
-    },
-    /// Sync by having the emitter logic in a separate thread
-    Live {
-        #[clap(flatten)]
-        rpc_args: RpcArgs,
-    },
-}
-
-fn main() -> anyhow::Result<()> {
-    let start = Instant::now();
-
-    let example_cli::Init {
-        args,
-        graph,
-        chain,
-        db,
-        network,
-    } = match example_cli::init_or_load::<RpcCommands, RpcArgs>(DB_MAGIC, DB_PATH)? {
-        Some(init) => init,
-        None => return Ok(()),
-    };
-
-    let rpc_cmd = match args.command {
-        example_cli::Commands::ChainSpecific(rpc_cmd) => rpc_cmd,
-        general_cmd => {
-            return example_cli::handle_commands(
-                &graph,
-                &chain,
-                &db,
-                network,
-                |rpc_args, tx| {
-                    let client = rpc_args.new_client()?;
-                    client.send_raw_transaction(tx)?;
-                    Ok(())
-                },
-                general_cmd,
-            );
-        }
-    };
-
-    match rpc_cmd {
-        RpcCommands::Sync { rpc_args } => {
-            let RpcArgs {
-                fallback_height, ..
-            } = rpc_args;
-
-            let rpc_client = rpc_args.new_client()?;
-            let mut emitter = {
-                let chain = chain.lock().unwrap();
-                let graph = graph.lock().unwrap();
-                Emitter::new(
-                    &rpc_client,
-                    chain.tip(),
-                    fallback_height,
-                    graph
-                        .canonical_view(
-                            &*chain,
-                            chain.tip().block_id(),
-                            CanonicalizationParams::default(),
-                        )
-                        .txs()
-                        .filter(|tx| tx.pos.is_unconfirmed())
-                        .map(|tx| tx.tx),
-                )
-            };
-            let mut db_stage = ChangeSet::default();
-
-            let mut last_db_commit = Instant::now();
-            let mut last_print = Instant::now();
-
-            while let Some(emission) = emitter.next_block()? {
-                let height = emission.block_height();
-
-                let mut chain = chain.lock().unwrap();
-                let mut graph = graph.lock().unwrap();
-
-                let chain_changeset = chain
-                    .apply_update(emission.checkpoint)
-                    .expect("must always apply as we receive blocks in order from emitter");
-                let graph_changeset = graph.apply_block_relevant(&emission.block, height);
-                db_stage.merge(ChangeSet {
-                    local_chain: chain_changeset,
-                    tx_graph: graph_changeset.tx_graph,
-                    indexer: graph_changeset.indexer,
-                    ..Default::default()
-                });
-
-                // commit staged db changes in intervals
-                if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
-                    let db = &mut *db.lock().unwrap();
-                    last_db_commit = Instant::now();
-                    if let Some(changeset) = db_stage.take() {
-                        db.append(&changeset)?;
-                    }
-                    println!(
-                        "[{:>10}s] committed to db (took {}s)",
-                        start.elapsed().as_secs_f32(),
-                        last_db_commit.elapsed().as_secs_f32()
-                    );
-                }
-
-                // print synced-to height and current balance in intervals
-                if last_print.elapsed() >= STDOUT_PRINT_DELAY {
-                    last_print = Instant::now();
-                    let synced_to = chain.tip();
-                    let balance = {
-                        graph
-                            .canonical_view(
-                                &*chain,
-                                synced_to.block_id(),
-                                CanonicalizationParams::default(),
-                            )
-                            .balance(
-                                graph.index.outpoints().iter().cloned(),
-                                |(k, _), _| k == &Keychain::Internal,
-                                1,
-                            )
-                    };
-                    println!(
-                        "[{:>10}s] synced to {} @ {} | total: {}",
-                        start.elapsed().as_secs_f32(),
-                        synced_to.hash(),
-                        synced_to.height(),
-                        balance.total()
-                    );
-                }
-            }
-
-            let mempool_txs = emitter.mempool()?;
-            let graph_changeset = graph
-                .lock()
-                .unwrap()
-                .batch_insert_relevant_unconfirmed(mempool_txs.update);
-            {
-                let db = &mut *db.lock().unwrap();
-                db_stage.merge(ChangeSet {
-                    tx_graph: graph_changeset.tx_graph,
-                    indexer: graph_changeset.indexer,
-                    ..Default::default()
-                });
-                if let Some(changeset) = db_stage.take() {
-                    db.append(&changeset)?;
-                }
-            }
-        }
-        RpcCommands::Live { rpc_args } => {
-            let RpcArgs {
-                fallback_height, ..
-            } = rpc_args;
-            let sigterm_flag = start_ctrlc_handler();
-
-            let rpc_client = Arc::new(rpc_args.new_client()?);
-            let mut emitter = {
-                let chain = chain.lock().unwrap();
-                let graph = graph.lock().unwrap();
-                Emitter::new(
-                    rpc_client.clone(),
-                    chain.tip(),
-                    fallback_height,
-                    graph
-                        .canonical_view(
-                            &*chain,
-                            chain.tip().block_id(),
-                            CanonicalizationParams::default(),
-                        )
-                        .txs()
-                        .filter(|tx| tx.pos.is_unconfirmed())
-                        .map(|tx| tx.tx),
-                )
-            };
-
-            println!(
-                "[{:>10}s] starting emitter thread...",
-                start.elapsed().as_secs_f32()
-            );
-            let (tx, rx) = std::sync::mpsc::sync_channel::<Emission>(CHANNEL_BOUND);
-            let emission_jh = std::thread::spawn(move || -> anyhow::Result<()> {
-                let mut block_count = rpc_client.get_block_count()? as u32;
-                tx.send(Emission::Tip(block_count))?;
-
-                loop {
-                    match emitter.next_block()? {
-                        Some(block_emission) => {
-                            let height = block_emission.block_height();
-                            if sigterm_flag.load(Ordering::Acquire) {
-                                break;
-                            }
-                            if height > block_count {
-                                block_count = rpc_client.get_block_count()? as u32;
-                                tx.send(Emission::Tip(block_count))?;
-                            }
-                            tx.send(Emission::Block(block_emission))?;
-                        }
-                        None => {
-                            if await_flag(&sigterm_flag, MEMPOOL_EMIT_DELAY) {
-                                break;
-                            }
-                            println!("preparing mempool emission...");
-                            let now = Instant::now();
-                            tx.send(Emission::Mempool(emitter.mempool()?))?;
-                            println!("mempool emission prepared in {}s", now.elapsed().as_secs());
-                            continue;
-                        }
-                    };
-                }
-
-                println!("emitter thread shutting down...");
-                Ok(())
-            });
-
-            let mut tip_height = 0_u32;
-            let mut last_db_commit = Instant::now();
-            let mut last_print = Option::<Instant>::None;
-            let mut db_stage = ChangeSet::default();
-
-            for emission in rx {
-                let mut graph = graph.lock().unwrap();
-                let mut chain = chain.lock().unwrap();
-
-                let (chain_changeset, graph_changeset) = match emission {
-                    Emission::Block(block_emission) => {
-                        let height = block_emission.block_height();
-                        let chain_changeset = chain
-                            .apply_update(block_emission.checkpoint)
-                            .expect("must always apply as we receive blocks in order from emitter");
-                        let graph_changeset =
-                            graph.apply_block_relevant(&block_emission.block, height);
-                        (chain_changeset, graph_changeset)
-                    }
-                    Emission::Mempool(mempool_txs) => {
-                        let mut graph_changeset =
-                            graph.batch_insert_relevant_unconfirmed(mempool_txs.update.clone());
-                        graph_changeset
-                            .merge(graph.batch_insert_relevant_evicted_at(mempool_txs.evicted));
-                        (local_chain::ChangeSet::default(), graph_changeset)
-                    }
-                    Emission::Tip(h) => {
-                        tip_height = h;
-                        continue;
-                    }
-                };
-
-                db_stage.merge(ChangeSet {
-                    local_chain: chain_changeset,
-                    tx_graph: graph_changeset.tx_graph,
-                    indexer: graph_changeset.indexer,
-                    ..Default::default()
-                });
-
-                if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
-                    let db = &mut *db.lock().unwrap();
-                    last_db_commit = Instant::now();
-                    if let Some(changeset) = db_stage.take() {
-                        db.append(&changeset)?;
-                    }
-                    println!(
-                        "[{:>10}s] committed to db (took {}s)",
-                        start.elapsed().as_secs_f32(),
-                        last_db_commit.elapsed().as_secs_f32()
-                    );
-                }
-
-                if last_print.map_or(Duration::MAX, |i| i.elapsed()) >= STDOUT_PRINT_DELAY {
-                    last_print = Some(Instant::now());
-                    let synced_to = chain.tip();
-                    let balance = {
-                        graph
-                            .canonical_view(
-                                &*chain,
-                                synced_to.block_id(),
-                                CanonicalizationParams::default(),
-                            )
-                            .balance(
-                                graph.index.outpoints().iter().cloned(),
-                                |(k, _), _| k == &Keychain::Internal,
-                                1,
-                            )
-                    };
-                    println!(
-                        "[{:>10}s] synced to {} @ {} / {} | total: {}",
-                        start.elapsed().as_secs_f32(),
-                        synced_to.hash(),
-                        synced_to.height(),
-                        tip_height,
-                        balance.total()
-                    );
-                }
-            }
-
-            emission_jh.join().expect("must join emitter thread")?;
-        }
-    }
-
-    Ok(())
-}
-
-#[allow(dead_code)]
-fn start_ctrlc_handler() -> Arc<AtomicBool> {
-    let flag = Arc::new(AtomicBool::new(false));
-    let cloned_flag = flag.clone();
-
-    ctrlc::set_handler(move || cloned_flag.store(true, Ordering::Release));
-
-    flag
-}
-
-#[allow(dead_code)]
-fn await_flag(flag: &AtomicBool, duration: Duration) -> bool {
-    let start = Instant::now();
-    loop {
-        if flag.load(Ordering::Acquire) {
-            return true;
-        }
-        if start.elapsed() >= duration {
-            return false;
-        }
-        std::thread::sleep(Duration::from_secs(1));
-    }
-}
diff --git a/examples/example_cli/Cargo.toml b/examples/example_cli/Cargo.toml
deleted file mode 100644 (file)
index 7c96292..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-[package]
-name = "example_cli"
-version = "0.2.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-bdk_chain = { path = "../../crates/chain", features = ["serde", "miniscript"]}
-bdk_coin_select = "0.4"
-bdk_file_store = { path = "../../crates/file_store" }
-bitcoin = { version = "0.32.0", features = ["base64"], default-features = false }
-
-anyhow = "1"
-clap = { version = "4.5.17", features = ["derive", "env"] }
-rand = "0.8"
-serde = { version = "1", features = ["derive"] }
-serde_json = { version = "1.0", features = ["alloc"] }
diff --git a/examples/example_cli/src/lib.rs b/examples/example_cli/src/lib.rs
deleted file mode 100644 (file)
index 6745ae6..0000000
+++ /dev/null
@@ -1,950 +0,0 @@
-use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD;
-use serde_json::json;
-use std::cmp;
-use std::env;
-use std::fmt;
-use std::str::FromStr;
-use std::sync::Mutex;
-
-use anyhow::bail;
-use anyhow::Context;
-use bdk_chain::bitcoin::{
-    absolute, address::NetworkUnchecked, bip32, consensus, constants, hex::DisplayHex, relative,
-    secp256k1::Secp256k1, transaction, Address, Amount, Network, NetworkKind, Psbt, Sequence,
-    Transaction, TxIn, TxOut,
-};
-use bdk_chain::miniscript::{
-    plan::{Assets, Plan},
-    psbt::PsbtExt,
-    Descriptor, DescriptorPublicKey, ForEachKey,
-};
-use bdk_chain::CanonicalizationParams;
-use bdk_chain::ConfirmationBlockTime;
-use bdk_chain::{
-    indexer::keychain_txout::{self, KeychainTxOutIndex},
-    local_chain::{self, LocalChain},
-    tx_graph, ChainOracle, DescriptorExt, FullTxOut, IndexedTxGraph, Merge,
-};
-use bdk_coin_select::{
-    metrics::LowestFee, Candidate, ChangePolicy, CoinSelector, DrainWeights, FeeRate, Target,
-    TargetFee, TargetOutputs,
-};
-use bdk_file_store::Store;
-use clap::{Parser, Subcommand};
-use rand::prelude::*;
-
-pub use anyhow;
-pub use clap;
-
-/// Alias for a `IndexedTxGraph` with specific `Anchor` and `Indexer`.
-pub type KeychainTxGraph = IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<Keychain>>;
-
-/// ChangeSet
-#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
-pub struct ChangeSet {
-    /// Descriptor for recipient addresses.
-    pub descriptor: Option<Descriptor<DescriptorPublicKey>>,
-    /// Descriptor for change addresses.
-    pub change_descriptor: Option<Descriptor<DescriptorPublicKey>>,
-    /// Stores the network type of the transaction data.
-    pub network: Option<Network>,
-    /// Changes to the [`LocalChain`].
-    pub local_chain: local_chain::ChangeSet,
-    /// Changes to [`TxGraph`](tx_graph::TxGraph).
-    pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
-    /// Changes to [`KeychainTxOutIndex`].
-    pub indexer: keychain_txout::ChangeSet,
-}
-
-#[derive(Parser)]
-#[clap(author, version, about, long_about = None)]
-#[clap(propagate_version = true)]
-pub struct Args<CS: clap::Subcommand, S: clap::Args> {
-    #[clap(subcommand)]
-    pub command: Commands<CS, S>,
-}
-
-#[derive(Subcommand, Debug, Clone)]
-pub enum Commands<CS: clap::Subcommand, S: clap::Args> {
-    /// Initialize a new data store.
-    Init {
-        /// Network
-        #[clap(long, short, default_value = "signet")]
-        network: Network,
-        /// Descriptor
-        #[clap(env = "DESCRIPTOR")]
-        descriptor: String,
-        /// Change descriptor
-        #[clap(long, short, env = "CHANGE_DESCRIPTOR")]
-        change_descriptor: Option<String>,
-    },
-    #[clap(flatten)]
-    ChainSpecific(CS),
-    /// Address generation and inspection.
-    Address {
-        #[clap(subcommand)]
-        addr_cmd: AddressCmd,
-    },
-    /// Get the wallet balance.
-    Balance,
-    /// TxOut related commands.
-    #[clap(name = "txout")]
-    TxOut {
-        #[clap(subcommand)]
-        txout_cmd: TxOutCmd,
-    },
-    /// PSBT operations
-    Psbt {
-        #[clap(subcommand)]
-        psbt_cmd: PsbtCmd<S>,
-    },
-    /// Generate new BIP86 descriptors.
-    Generate {
-        /// Network
-        #[clap(long, short, default_value = "signet")]
-        network: Network,
-    },
-}
-
-#[derive(Subcommand, Debug, Clone)]
-pub enum AddressCmd {
-    /// Get the next unused address.
-    Next,
-    /// Get a new address regardless of the existing unused addresses.
-    New,
-    /// List all addresses
-    List {
-        /// List change addresses
-        #[clap(long)]
-        change: bool,
-    },
-    /// Get last revealed address index for each keychain.
-    Index,
-}
-
-#[derive(Subcommand, Debug, Clone)]
-pub enum TxOutCmd {
-    /// List transaction outputs.
-    List {
-        /// Return only spent outputs.
-        #[clap(short, long)]
-        spent: bool,
-        /// Return only unspent outputs.
-        #[clap(short, long)]
-        unspent: bool,
-        /// Return only confirmed outputs.
-        #[clap(long)]
-        confirmed: bool,
-        /// Return only unconfirmed outputs.
-        #[clap(long)]
-        unconfirmed: bool,
-    },
-}
-
-#[derive(Subcommand, Debug, Clone)]
-pub enum PsbtCmd<S: clap::Args> {
-    /// Create a new PSBT.
-    New {
-        /// Amount to send in satoshis
-        #[clap(required = true)]
-        value: u64,
-        /// Recipient address
-        #[clap(required = true)]
-        address: Address<NetworkUnchecked>,
-        /// Set the feerate of the tx (sat/vbyte)
-        #[clap(long, short, default_value = "1.0")]
-        feerate: Option<f32>,
-        /// Set max absolute timelock (from consensus value)
-        #[clap(long, short)]
-        after: Option<u32>,
-        /// Set max relative timelock (from consensus value)
-        #[clap(long, short)]
-        older: Option<u32>,
-        /// Coin selection algorithm
-        #[clap(long, short, default_value = "bnb")]
-        coin_select: CoinSelectionAlgo,
-        /// Debug print the PSBT
-        #[clap(long, short)]
-        debug: bool,
-    },
-    /// Sign with a hot signer
-    Sign {
-        /// Private descriptor [env: DESCRIPTOR=]
-        #[clap(long, short)]
-        descriptor: Option<String>,
-        /// PSBT
-        #[clap(long, short, required = true)]
-        psbt: String,
-    },
-    /// Extract transaction
-    Extract {
-        /// PSBT
-        #[clap(long, short, required = true)]
-        psbt: String,
-        /// Whether to try broadcasting the tx
-        #[clap(long, short)]
-        broadcast: bool,
-        #[clap(flatten)]
-        chain_specific: S,
-    },
-}
-
-#[derive(
-    Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, serde::Deserialize, serde::Serialize,
-)]
-pub enum Keychain {
-    External,
-    Internal,
-}
-
-impl fmt::Display for Keychain {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Keychain::External => write!(f, "external"),
-            Keychain::Internal => write!(f, "internal"),
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-pub enum CoinSelectionAlgo {
-    LargestFirst,
-    SmallestFirst,
-    OldestFirst,
-    NewestFirst,
-    #[default]
-    BranchAndBound,
-}
-
-impl FromStr for CoinSelectionAlgo {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        use CoinSelectionAlgo::*;
-        Ok(match s {
-            "largest-first" => LargestFirst,
-            "smallest-first" => SmallestFirst,
-            "oldest-first" => OldestFirst,
-            "newest-first" => NewestFirst,
-            "bnb" => BranchAndBound,
-            unknown => bail!("unknown coin selection algorithm '{unknown}'"),
-        })
-    }
-}
-
-impl fmt::Display for CoinSelectionAlgo {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        use CoinSelectionAlgo::*;
-        write!(
-            f,
-            "{}",
-            match self {
-                LargestFirst => "largest-first",
-                SmallestFirst => "smallest-first",
-                OldestFirst => "oldest-first",
-                NewestFirst => "newest-first",
-                BranchAndBound => "bnb",
-            }
-        )
-    }
-}
-
-// Records changes to the internal keychain when we
-// have to include a change output during tx creation.
-#[derive(Debug)]
-pub struct ChangeInfo {
-    pub change_keychain: Keychain,
-    pub indexer: keychain_txout::ChangeSet,
-    pub index: u32,
-}
-
-pub fn create_tx<O: ChainOracle>(
-    graph: &mut KeychainTxGraph,
-    chain: &O,
-    assets: &Assets,
-    cs_algorithm: CoinSelectionAlgo,
-    address: Address,
-    value: u64,
-    feerate: f32,
-) -> anyhow::Result<(Psbt, Option<ChangeInfo>)>
-where
-    O::Error: core::error::Error + Send + Sync + 'static,
-{
-    let mut changeset = keychain_txout::ChangeSet::default();
-
-    // get planned utxos
-    let mut plan_utxos = planned_utxos(graph, chain, assets)?;
-
-    // sort utxos if cs-algo requires it
-    match cs_algorithm {
-        CoinSelectionAlgo::LargestFirst => {
-            plan_utxos.sort_by_key(|(_, utxo)| cmp::Reverse(utxo.txout.value))
-        }
-        CoinSelectionAlgo::SmallestFirst => plan_utxos.sort_by_key(|(_, utxo)| utxo.txout.value),
-        CoinSelectionAlgo::OldestFirst => plan_utxos.sort_by_key(|(_, utxo)| utxo.chain_position),
-        CoinSelectionAlgo::NewestFirst => {
-            plan_utxos.sort_by_key(|(_, utxo)| cmp::Reverse(utxo.chain_position))
-        }
-        CoinSelectionAlgo::BranchAndBound => plan_utxos.shuffle(&mut thread_rng()),
-    }
-
-    // build candidate set
-    let candidates: Vec<Candidate> = plan_utxos
-        .iter()
-        .map(|(plan, utxo)| {
-            Candidate::new(
-                utxo.txout.value.to_sat(),
-                plan.satisfaction_weight() as u64,
-                plan.witness_version().is_some(),
-            )
-        })
-        .collect();
-
-    // create recipient output(s)
-    let mut outputs = vec![TxOut {
-        value: Amount::from_sat(value),
-        script_pubkey: address.script_pubkey(),
-    }];
-
-    let (change_keychain, _) = graph
-        .index
-        .keychains()
-        .last()
-        .expect("must have a keychain");
-
-    let ((change_index, change_script), index_changeset) = graph
-        .index
-        .next_unused_spk(change_keychain)
-        .expect("Must exist");
-    changeset.merge(index_changeset);
-
-    let mut change_output = TxOut {
-        value: Amount::ZERO,
-        script_pubkey: change_script,
-    };
-
-    let change_desc = graph
-        .index
-        .keychains()
-        .find(|(k, _)| k == &change_keychain)
-        .expect("must exist")
-        .1;
-
-    let min_drain_value = change_desc.dust_value().to_sat();
-
-    let target = Target {
-        outputs: TargetOutputs::fund_outputs(
-            outputs
-                .iter()
-                .map(|output| (output.weight().to_wu(), output.value.to_sat())),
-        ),
-        fee: TargetFee {
-            rate: FeeRate::from_sat_per_vb(feerate),
-            ..Default::default()
-        },
-    };
-
-    let change_policy = ChangePolicy {
-        min_value: min_drain_value,
-        drain_weights: DrainWeights::TR_KEYSPEND,
-    };
-
-    // run coin selection
-    let mut selector = CoinSelector::new(&candidates);
-    match cs_algorithm {
-        CoinSelectionAlgo::BranchAndBound => {
-            let metric = LowestFee {
-                target,
-                long_term_feerate: FeeRate::from_sat_per_vb(10.0),
-                change_policy,
-            };
-            match selector.run_bnb(metric, 10_000) {
-                Ok(_) => {}
-                Err(_) => selector
-                    .select_until_target_met(target)
-                    .context("selecting coins")?,
-            }
-        }
-        _ => selector
-            .select_until_target_met(target)
-            .context("selecting coins")?,
-    }
-
-    // get the selected plan utxos
-    let selected: Vec<_> = selector.apply_selection(&plan_utxos).collect();
-
-    // if the selection tells us to use change and the change value is sufficient, we add it as an
-    // output
-    let mut change_info = Option::<ChangeInfo>::None;
-    let drain = selector.drain(target, change_policy);
-    if drain.value > min_drain_value {
-        change_output.value = Amount::from_sat(drain.value);
-        outputs.push(change_output);
-        change_info = Some(ChangeInfo {
-            change_keychain,
-            indexer: changeset,
-            index: change_index,
-        });
-        outputs.shuffle(&mut thread_rng());
-    }
-
-    let unsigned_tx = Transaction {
-        version: transaction::Version::TWO,
-        lock_time: assets
-            .absolute_timelock
-            .unwrap_or(absolute::LockTime::from_height(
-                chain.get_chain_tip()?.height,
-            )?),
-        input: selected
-            .iter()
-            .map(|(plan, utxo)| TxIn {
-                previous_output: utxo.outpoint,
-                sequence: plan
-                    .relative_timelock
-                    .map_or(Sequence::ENABLE_RBF_NO_LOCKTIME, Sequence::from),
-                ..Default::default()
-            })
-            .collect(),
-        output: outputs,
-    };
-
-    // update psbt with plan
-    let mut psbt = Psbt::from_unsigned_tx(unsigned_tx)?;
-    for (i, (plan, utxo)) in selected.iter().enumerate() {
-        let psbt_input = &mut psbt.inputs[i];
-        plan.update_psbt_input(psbt_input);
-        psbt_input.witness_utxo = Some(utxo.txout.clone());
-    }
-
-    Ok((psbt, change_info))
-}
-
-// Alias the elements of `planned_utxos`
-pub type PlanUtxo = (Plan, FullTxOut<ConfirmationBlockTime>);
-
-pub fn planned_utxos<O: ChainOracle>(
-    graph: &KeychainTxGraph,
-    chain: &O,
-    assets: &Assets,
-) -> Result<Vec<PlanUtxo>, O::Error> {
-    let chain_tip = chain.get_chain_tip()?;
-    let outpoints = graph.index.outpoints();
-    graph
-        .try_canonical_view(chain, chain_tip, CanonicalizationParams::default())?
-        .filter_unspent_outpoints(outpoints.iter().cloned())
-        .filter_map(|((k, i), full_txo)| -> Option<Result<PlanUtxo, _>> {
-            let desc = graph
-                .index
-                .keychains()
-                .find(|(keychain, _)| *keychain == k)
-                .expect("keychain must exist")
-                .1
-                .at_derivation_index(i)
-                .expect("i can't be hardened");
-
-            let plan = desc.plan(assets).ok()?;
-
-            Some(Ok((plan, full_txo)))
-        })
-        .collect()
-}
-
-pub fn handle_commands<CS: clap::Subcommand, S: clap::Args>(
-    graph: &Mutex<KeychainTxGraph>,
-    chain: &Mutex<LocalChain>,
-    db: &Mutex<Store<ChangeSet>>,
-    network: Network,
-    broadcast_fn: impl FnOnce(S, &Transaction) -> anyhow::Result<()>,
-    cmd: Commands<CS, S>,
-) -> anyhow::Result<()> {
-    match cmd {
-        Commands::Init { .. } => unreachable!("handled by init command"),
-        Commands::Generate { .. } => unreachable!("handled by generate command"),
-        Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
-        Commands::Address { addr_cmd } => {
-            let graph = &mut *graph.lock().unwrap();
-            let index = &mut graph.index;
-
-            match addr_cmd {
-                AddressCmd::Next | AddressCmd::New => {
-                    let spk_chooser = match addr_cmd {
-                        AddressCmd::Next => KeychainTxOutIndex::next_unused_spk,
-                        AddressCmd::New => KeychainTxOutIndex::reveal_next_spk,
-                        _ => unreachable!("only these two variants exist in match arm"),
-                    };
-
-                    let ((spk_i, spk), index_changeset) =
-                        spk_chooser(index, Keychain::External).expect("Must exist");
-                    let db = &mut *db.lock().unwrap();
-                    db.append(&ChangeSet {
-                        indexer: index_changeset,
-                        ..Default::default()
-                    })?;
-                    let addr = Address::from_script(spk.as_script(), network)?;
-                    println!("[address @ {spk_i}] {addr}");
-                    Ok(())
-                }
-                AddressCmd::Index => {
-                    for (keychain, derivation_index) in index.last_revealed_indices() {
-                        println!("{keychain:?}: {derivation_index}");
-                    }
-                    Ok(())
-                }
-                AddressCmd::List { change } => {
-                    let target_keychain = match change {
-                        true => Keychain::Internal,
-                        false => Keychain::External,
-                    };
-                    for (spk_i, spk) in index.revealed_keychain_spks(target_keychain) {
-                        let address = Address::from_script(spk.as_script(), network)
-                            .expect("should always be able to derive address");
-                        println!(
-                            "{:?} {} used:{}",
-                            spk_i,
-                            address,
-                            index.is_used(target_keychain, spk_i)
-                        );
-                    }
-                    Ok(())
-                }
-            }
-        }
-        Commands::Balance => {
-            let graph = &*graph.lock().unwrap();
-            let chain = &*chain.lock().unwrap();
-            fn print_balances<'a>(
-                title_str: &'a str,
-                items: impl IntoIterator<Item = (&'a str, Amount)>,
-            ) {
-                println!("{title_str}:");
-                for (name, amount) in items.into_iter() {
-                    println!("    {:<10} {:>12} sats", name, amount.to_sat())
-                }
-            }
-
-            let balance = graph
-                .try_canonical_view(
-                    chain,
-                    chain.get_chain_tip()?,
-                    CanonicalizationParams::default(),
-                )?
-                .balance(
-                    graph.index.outpoints().iter().cloned(),
-                    |(k, _), _| k == &Keychain::Internal,
-                    1,
-                );
-
-            let confirmed_total = balance.confirmed + balance.immature;
-            let unconfirmed_total = balance.untrusted_pending + balance.trusted_pending;
-
-            print_balances(
-                "confirmed",
-                [
-                    ("total", confirmed_total),
-                    ("spendable", balance.confirmed),
-                    ("immature", balance.immature),
-                ],
-            );
-            print_balances(
-                "unconfirmed",
-                [
-                    ("total", unconfirmed_total),
-                    ("trusted", balance.trusted_pending),
-                    ("untrusted", balance.untrusted_pending),
-                ],
-            );
-
-            Ok(())
-        }
-        Commands::TxOut { txout_cmd } => {
-            let graph = &*graph.lock().unwrap();
-            let chain = &*chain.lock().unwrap();
-            let chain_tip = chain.get_chain_tip()?;
-            let outpoints = graph.index.outpoints();
-
-            match txout_cmd {
-                TxOutCmd::List {
-                    spent,
-                    unspent,
-                    confirmed,
-                    unconfirmed,
-                } => {
-                    let txouts = graph
-                        .try_canonical_view(chain, chain_tip, CanonicalizationParams::default())?
-                        .filter_outpoints(outpoints.iter().cloned())
-                        .filter(|(_, full_txo)| match (spent, unspent) {
-                            (true, false) => full_txo.spent_by.is_some(),
-                            (false, true) => full_txo.spent_by.is_none(),
-                            _ => true,
-                        })
-                        .filter(|(_, full_txo)| match (confirmed, unconfirmed) {
-                            (true, false) => full_txo.chain_position.is_confirmed(),
-                            (false, true) => !full_txo.chain_position.is_confirmed(),
-                            _ => true,
-                        })
-                        .collect::<Vec<_>>();
-
-                    for (spk_i, full_txo) in txouts {
-                        let addr = Address::from_script(&full_txo.txout.script_pubkey, network)?;
-                        println!(
-                            "{:?} {} {} {} spent:{:?}",
-                            spk_i, full_txo.txout.value, full_txo.outpoint, addr, full_txo.spent_by
-                        )
-                    }
-                    Ok(())
-                }
-            }
-        }
-        Commands::Psbt { psbt_cmd } => match psbt_cmd {
-            PsbtCmd::New {
-                value,
-                address,
-                feerate,
-                after,
-                older,
-                coin_select,
-                debug,
-            } => {
-                let address = address.require_network(network)?;
-
-                let (psbt, change_info) = {
-                    let mut graph = graph.lock().unwrap();
-                    let chain = chain.lock().unwrap();
-
-                    // collect assets we can sign for
-                    let mut pks = vec![];
-                    for (_, desc) in graph.index.keychains() {
-                        desc.for_each_key(|k| {
-                            pks.push(k.clone());
-                            true
-                        });
-                    }
-                    let mut assets = Assets::new().add(pks);
-                    if let Some(n) = after {
-                        assets = assets.after(absolute::LockTime::from_consensus(n));
-                    }
-                    if let Some(n) = older {
-                        assets = assets.older(relative::LockTime::from_consensus(n)?);
-                    }
-
-                    create_tx(
-                        &mut graph,
-                        &*chain,
-                        &assets,
-                        coin_select,
-                        address,
-                        value,
-                        feerate.expect("must have feerate"),
-                    )?
-                };
-
-                if let Some(ChangeInfo {
-                    change_keychain,
-                    indexer,
-                    index,
-                }) = change_info
-                {
-                    // We must first persist to disk the fact that we've got a new address from the
-                    // change keychain so future scans will find the tx we're about to broadcast.
-                    // If we're unable to persist this, then we don't want to broadcast.
-                    {
-                        let db = &mut *db.lock().unwrap();
-                        db.append(&ChangeSet {
-                            indexer,
-                            ..Default::default()
-                        })?;
-                    }
-
-                    // We don't want other callers/threads to use this address while we're using it
-                    // but we also don't want to scan the tx we just created because it's not
-                    // technically in the blockchain yet.
-                    graph
-                        .lock()
-                        .unwrap()
-                        .index
-                        .mark_used(change_keychain, index);
-                }
-
-                if debug {
-                    dbg!(psbt);
-                } else {
-                    // print base64 encoded psbt
-                    let fee = psbt.fee()?.to_sat();
-                    let mut obj = serde_json::Map::new();
-                    obj.insert("psbt".to_string(), json!(psbt.to_string()));
-                    obj.insert("fee".to_string(), json!(fee));
-                    println!("{}", serde_json::to_string_pretty(&obj)?);
-                };
-
-                Ok(())
-            }
-            PsbtCmd::Sign { psbt, descriptor } => {
-                let mut psbt = Psbt::from_str(&psbt)?;
-
-                let desc_str = match descriptor {
-                    Some(s) => s,
-                    None => env::var("DESCRIPTOR").context("unable to sign")?,
-                };
-
-                let secp = Secp256k1::new();
-                let (_, keymap) = Descriptor::parse_descriptor(&secp, &desc_str)?;
-
-                if keymap.is_empty() {
-                    bail!("unable to sign")
-                }
-
-                let _sign_res = psbt
-                    .sign(&keymap, &secp)
-                    .map_err(|errors| anyhow::anyhow!("failed to sign PSBT {errors:?}"))?;
-
-                let mut obj = serde_json::Map::new();
-                obj.insert("psbt".to_string(), json!(psbt.to_string()));
-                println!("{}", serde_json::to_string_pretty(&obj)?);
-
-                Ok(())
-            }
-            PsbtCmd::Extract {
-                broadcast,
-                chain_specific,
-                psbt,
-            } => {
-                let mut psbt = Psbt::from_str(&psbt)?;
-                psbt.finalize_mut(&Secp256k1::new())
-                    .map_err(|errors| anyhow::anyhow!("failed to finalize PSBT {errors:?}"))?;
-
-                let tx = psbt.extract_tx()?;
-
-                if broadcast {
-                    let mut graph = graph.lock().unwrap();
-
-                    match broadcast_fn(chain_specific, &tx) {
-                        Ok(_) => {
-                            println!("Broadcasted Tx: {}", tx.compute_txid());
-
-                            let changeset = graph.insert_tx(tx);
-
-                            // We know the tx is at least unconfirmed now. Note if persisting here
-                            // fails, it's not a big deal since we can
-                            // always find it again from the blockchain.
-                            db.lock().unwrap().append(&ChangeSet {
-                                tx_graph: changeset.tx_graph,
-                                indexer: changeset.indexer,
-                                ..Default::default()
-                            })?;
-                        }
-                        Err(e) => {
-                            // We failed to broadcast, so allow our change address to be used in the
-                            // future
-                            let (change_keychain, _) = graph
-                                .index
-                                .keychains()
-                                .last()
-                                .expect("must have a keychain");
-                            let change_index = tx.output.iter().find_map(|txout| {
-                                let spk = txout.script_pubkey.as_script();
-                                match graph.index.index_of_spk(spk) {
-                                    Some(&(keychain, index)) if keychain == change_keychain => {
-                                        Some((keychain, index))
-                                    }
-                                    _ => None,
-                                }
-                            });
-                            if let Some((keychain, index)) = change_index {
-                                graph.index.unmark_used(keychain, index);
-                            }
-                            bail!(e);
-                        }
-                    }
-                } else {
-                    // encode raw tx hex
-                    let hex = consensus::serialize(&tx).to_lower_hex_string();
-                    let mut obj = serde_json::Map::new();
-                    obj.insert("tx".to_string(), json!(hex));
-                    println!("{}", serde_json::to_string_pretty(&obj)?);
-                }
-
-                Ok(())
-            }
-        },
-    }
-}
-
-/// The initial state returned by [`init_or_load`].
-pub struct Init<CS: clap::Subcommand, S: clap::Args> {
-    /// CLI args
-    pub args: Args<CS, S>,
-    /// Indexed graph
-    pub graph: Mutex<KeychainTxGraph>,
-    /// Local chain
-    pub chain: Mutex<LocalChain>,
-    /// Database
-    pub db: Mutex<Store<ChangeSet>>,
-    /// Network
-    pub network: Network,
-}
-
-/// Loads from persistence or creates new
-pub fn init_or_load<CS: clap::Subcommand, S: clap::Args>(
-    db_magic: &[u8],
-    db_path: &str,
-) -> anyhow::Result<Option<Init<CS, S>>> {
-    let args = Args::<CS, S>::parse();
-
-    match args.command {
-        // initialize new db
-        Commands::Init { .. } => initialize::<CS, S>(args, db_magic, db_path).map(|_| None),
-        // generate keys
-        Commands::Generate { network } => generate_bip86_helper(network).map(|_| None),
-        // try load
-        _ => {
-            let (mut db, changeset) =
-                Store::<ChangeSet>::load(db_magic, db_path).context("could not open file store")?;
-
-            let changeset = changeset.expect("should not be empty");
-            let network = changeset.network.expect("changeset network");
-
-            let chain = Mutex::new({
-                let (mut chain, _) =
-                    LocalChain::from_genesis(constants::genesis_block(network).block_hash());
-                chain.apply_changeset(&changeset.local_chain)?;
-                chain
-            });
-
-            let (graph, changeset) = IndexedTxGraph::from_changeset(
-                (changeset.tx_graph, changeset.indexer).into(),
-                |c| -> anyhow::Result<_> {
-                    let mut indexer =
-                        KeychainTxOutIndex::from_changeset(DEFAULT_LOOKAHEAD, true, c);
-                    if let Some(desc) = changeset.descriptor {
-                        indexer.insert_descriptor(Keychain::External, desc)?;
-                    }
-                    if let Some(change_desc) = changeset.change_descriptor {
-                        indexer.insert_descriptor(Keychain::Internal, change_desc)?;
-                    }
-                    Ok(indexer)
-                },
-            )?;
-            db.append(&ChangeSet {
-                indexer: changeset.indexer,
-                tx_graph: changeset.tx_graph,
-                ..Default::default()
-            })?;
-
-            let graph = Mutex::new(graph);
-            let db = Mutex::new(db);
-
-            Ok(Some(Init {
-                args,
-                graph,
-                chain,
-                db,
-                network,
-            }))
-        }
-    }
-}
-
-/// Initialize db backend.
-fn initialize<CS, S>(args: Args<CS, S>, db_magic: &[u8], db_path: &str) -> anyhow::Result<()>
-where
-    CS: clap::Subcommand,
-    S: clap::Args,
-{
-    if let Commands::Init {
-        network,
-        descriptor,
-        change_descriptor,
-    } = args.command
-    {
-        let mut changeset = ChangeSet::default();
-
-        // parse descriptors
-        let secp = Secp256k1::new();
-        let mut index = KeychainTxOutIndex::default();
-        let (descriptor, _) =
-            Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &descriptor)?;
-        let _ = index.insert_descriptor(Keychain::External, descriptor.clone())?;
-        changeset.descriptor = Some(descriptor);
-
-        if let Some(desc) = change_descriptor {
-            let (change_descriptor, _) =
-                Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &desc)?;
-            let _ = index.insert_descriptor(Keychain::Internal, change_descriptor.clone())?;
-            changeset.change_descriptor = Some(change_descriptor);
-        }
-
-        // create new
-        let (_, chain_changeset) =
-            LocalChain::from_genesis(constants::genesis_block(network).block_hash());
-        changeset.network = Some(network);
-        changeset.local_chain = chain_changeset;
-        let mut db = Store::<ChangeSet>::create(db_magic, db_path)?;
-        db.append(&changeset)?;
-        println!("New database {db_path}");
-    }
-
-    Ok(())
-}
-
-/// Generate BIP86 descriptors.
-fn generate_bip86_helper(network: impl Into<NetworkKind>) -> anyhow::Result<()> {
-    let secp = Secp256k1::new();
-    let mut seed = [0x00; 32];
-    thread_rng().fill_bytes(&mut seed);
-
-    let m = bip32::Xpriv::new_master(network, &seed)?;
-    let fp = m.fingerprint(&secp);
-    let path = if m.network.is_mainnet() {
-        "86h/0h/0h"
-    } else {
-        "86h/1h/0h"
-    };
-
-    let descriptors: Vec<String> = [0, 1]
-        .iter()
-        .map(|i| format!("tr([{fp}]{m}/{path}/{i}/*)"))
-        .collect();
-    let external_desc = &descriptors[0];
-    let internal_desc = &descriptors[1];
-    let (descriptor, keymap) =
-        <Descriptor<DescriptorPublicKey>>::parse_descriptor(&secp, external_desc)?;
-    let (internal_descriptor, internal_keymap) =
-        <Descriptor<DescriptorPublicKey>>::parse_descriptor(&secp, internal_desc)?;
-    println!("Public");
-    println!("{descriptor}");
-    println!("{internal_descriptor}");
-    println!("\nPrivate");
-    println!("{}", descriptor.to_string_with_secret(&keymap));
-    println!(
-        "{}",
-        internal_descriptor.to_string_with_secret(&internal_keymap)
-    );
-
-    Ok(())
-}
-
-impl Merge for ChangeSet {
-    fn merge(&mut self, other: Self) {
-        if other.descriptor.is_some() {
-            self.descriptor = other.descriptor;
-        }
-        if other.change_descriptor.is_some() {
-            self.change_descriptor = other.change_descriptor;
-        }
-        if other.network.is_some() {
-            self.network = other.network;
-        }
-        Merge::merge(&mut self.local_chain, other.local_chain);
-        Merge::merge(&mut self.tx_graph, other.tx_graph);
-        Merge::merge(&mut self.indexer, other.indexer);
-    }
-
-    fn is_empty(&self) -> bool {
-        self.descriptor.is_none()
-            && self.change_descriptor.is_none()
-            && self.network.is_none()
-            && self.local_chain.is_empty()
-            && self.tx_graph.is_empty()
-            && self.indexer.is_empty()
-    }
-}
diff --git a/examples/example_electrum/Cargo.toml b/examples/example_electrum/Cargo.toml
deleted file mode 100644 (file)
index 9dcd540..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "example_electrum"
-version = "0.2.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-bdk_chain = { path = "../../crates/chain", features = ["serde"] }
-bdk_electrum = { path = "../../crates/electrum" }
-example_cli = { path = "../example_cli" }
diff --git a/examples/example_electrum/src/main.rs b/examples/example_electrum/src/main.rs
deleted file mode 100644 (file)
index c926663..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-use std::io::{self, Write};
-
-use bdk_chain::{
-    bitcoin::Network,
-    collections::BTreeSet,
-    indexed_tx_graph,
-    spk_client::{FullScanRequest, SyncRequest},
-    CanonicalizationParams, ConfirmationBlockTime, Merge,
-};
-use bdk_electrum::{
-    electrum_client::{self, Client, ElectrumApi},
-    BdkElectrumClient,
-};
-use example_cli::{
-    self,
-    anyhow::{self, Context},
-    clap::{self, Parser, Subcommand},
-    ChangeSet, Keychain,
-};
-
-const DB_MAGIC: &[u8] = b"bdk_example_electrum";
-const DB_PATH: &str = ".bdk_example_electrum.db";
-
-#[derive(Subcommand, Debug, Clone)]
-enum ElectrumCommands {
-    /// Scans the addresses in the wallet using the electrum API.
-    Scan {
-        /// When a gap this large has been found for a keychain, it will stop.
-        #[clap(long, default_value = "5")]
-        stop_gap: usize,
-        #[clap(flatten)]
-        scan_options: ScanOptions,
-        #[clap(flatten)]
-        electrum_args: ElectrumArgs,
-    },
-    /// Scans particular addresses using the electrum API.
-    Sync {
-        /// Scan all the unused addresses.
-        #[clap(long)]
-        unused_spks: bool,
-        /// Scan every address that you have derived.
-        #[clap(long)]
-        all_spks: bool,
-        /// Scan unspent outpoints for spends or changes to confirmation status of residing tx.
-        #[clap(long)]
-        utxos: bool,
-        /// Scan unconfirmed transactions for updates.
-        #[clap(long)]
-        unconfirmed: bool,
-        #[clap(flatten)]
-        scan_options: ScanOptions,
-        #[clap(flatten)]
-        electrum_args: ElectrumArgs,
-    },
-}
-
-impl ElectrumCommands {
-    fn electrum_args(&self) -> ElectrumArgs {
-        match self {
-            ElectrumCommands::Scan { electrum_args, .. } => electrum_args.clone(),
-            ElectrumCommands::Sync { electrum_args, .. } => electrum_args.clone(),
-        }
-    }
-}
-
-#[derive(clap::Args, Debug, Clone)]
-pub struct ElectrumArgs {
-    /// The electrum url to use to connect to. If not provided it will use a default electrum
-    /// server for your chosen network.
-    electrum_url: Option<String>,
-}
-
-impl ElectrumArgs {
-    pub fn client(&self, network: Network) -> anyhow::Result<Client> {
-        let electrum_url = self.electrum_url.as_deref().unwrap_or(match network {
-            Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
-            Network::Testnet => "ssl://electrum.blockstream.info:60002",
-            Network::Regtest => "tcp://localhost:60401",
-            Network::Signet => "tcp://signet-electrumx.wakiyamap.dev:50001",
-            _ => panic!("Unknown network"),
-        });
-        let config = electrum_client::Config::builder()
-            .validate_domain(matches!(network, Network::Bitcoin))
-            .build();
-
-        Ok(electrum_client::Client::from_config(electrum_url, config)?)
-    }
-}
-
-#[derive(Parser, Debug, Clone, PartialEq)]
-pub struct ScanOptions {
-    /// Set batch size for each script_history call to electrum client.
-    #[clap(long, default_value = "25")]
-    pub batch_size: usize,
-}
-
-fn main() -> anyhow::Result<()> {
-    let example_cli::Init {
-        args,
-        graph,
-        chain,
-        db,
-        network,
-    } = match example_cli::init_or_load::<ElectrumCommands, ElectrumArgs>(DB_MAGIC, DB_PATH)? {
-        Some(init) => init,
-        None => return Ok(()),
-    };
-
-    let electrum_cmd = match &args.command {
-        example_cli::Commands::ChainSpecific(electrum_cmd) => electrum_cmd,
-        general_cmd => {
-            return example_cli::handle_commands(
-                &graph,
-                &chain,
-                &db,
-                network,
-                |electrum_args, tx| {
-                    let client = electrum_args.client(network)?;
-                    client.transaction_broadcast(tx)?;
-                    Ok(())
-                },
-                general_cmd.clone(),
-            );
-        }
-    };
-
-    let client = BdkElectrumClient::new(electrum_cmd.electrum_args().client(network)?);
-
-    // Tell the electrum client about the txs and anchors we've already got locally so it doesn't
-    // re-download .them
-    {
-        let graph = graph.lock().unwrap();
-        client.populate_tx_cache(graph.graph().full_txs().map(|tx_node| tx_node.tx));
-        client.populate_anchor_cache(graph.graph().all_anchors().clone());
-    }
-
-    let (chain_update, tx_update, keychain_update) = match electrum_cmd.clone() {
-        ElectrumCommands::Scan {
-            stop_gap,
-            scan_options,
-            ..
-        } => {
-            let request = {
-                let graph = &*graph.lock().unwrap();
-                let chain = &*chain.lock().unwrap();
-
-                FullScanRequest::builder()
-                    .chain_tip(chain.tip())
-                    .spks_for_keychain(
-                        Keychain::External,
-                        graph
-                            .index
-                            .unbounded_spk_iter(Keychain::External)
-                            .into_iter()
-                            .flatten(),
-                    )
-                    .spks_for_keychain(
-                        Keychain::Internal,
-                        graph
-                            .index
-                            .unbounded_spk_iter(Keychain::Internal)
-                            .into_iter()
-                            .flatten(),
-                    )
-                    .inspect({
-                        let mut once = BTreeSet::new();
-                        move |k, spk_i, _| {
-                            if once.insert(k) {
-                                eprint!("\nScanning {k}: {spk_i} ");
-                            } else {
-                                eprint!("{spk_i} ");
-                            }
-                            io::stdout().flush().expect("must flush");
-                        }
-                    })
-            };
-
-            let res = client
-                .full_scan::<_>(request, stop_gap, scan_options.batch_size, false)
-                .context("scanning the blockchain")?;
-            (
-                res.chain_update,
-                res.tx_update,
-                Some(res.last_active_indices),
-            )
-        }
-        ElectrumCommands::Sync {
-            mut unused_spks,
-            all_spks,
-            mut utxos,
-            mut unconfirmed,
-            scan_options,
-            ..
-        } => {
-            // Get a short lock on the tracker to get the spks we're interested in
-            let graph = graph.lock().unwrap();
-            let chain = chain.lock().unwrap();
-
-            if !(all_spks || unused_spks || utxos || unconfirmed) {
-                unused_spks = true;
-                unconfirmed = true;
-                utxos = true;
-            } else if all_spks {
-                unused_spks = false;
-            }
-
-            let chain_tip = chain.tip();
-            let mut request =
-                SyncRequest::builder()
-                    .chain_tip(chain_tip.clone())
-                    .inspect(|item, progress| {
-                        let pc = (100 * progress.consumed()) as f32 / progress.total() as f32;
-                        match item {
-                            bdk_chain::spk_client::SyncItem::Spk((keychain, index), spk) => {
-                                eprintln!(
-                                    "[ SCANNING {pc:3.0}% ] script {} {} {}",
-                                    keychain, index, spk
-                                );
-                            }
-                            bdk_chain::spk_client::SyncItem::Txid(txid) => {
-                                eprintln!("[ SCANNING {pc:3.0}% ] txid {}", txid);
-                            }
-                            bdk_chain::spk_client::SyncItem::OutPoint(op) => {
-                                eprintln!("[ SCANNING {pc:3.0}% ] outpoint {}", op);
-                            }
-                        }
-                        let _ = io::stderr().flush();
-                    });
-            let canonical_view = graph.canonical_view(
-                &*chain,
-                chain_tip.block_id(),
-                CanonicalizationParams::default(),
-            );
-
-            request = request
-                .expected_spk_txids(canonical_view.list_expected_spk_txids(&graph.index, ..));
-            if all_spks {
-                request = request.spks_with_indexes(graph.index.revealed_spks(..));
-            }
-            if unused_spks {
-                request = request.spks_with_indexes(graph.index.unused_spks());
-            }
-            if utxos {
-                let init_outpoints = graph.index.outpoints();
-                request = request.outpoints(
-                    canonical_view
-                        .filter_unspent_outpoints(init_outpoints.iter().cloned())
-                        .map(|(_, utxo)| utxo.outpoint),
-                );
-            };
-            if unconfirmed {
-                request = request.txids(
-                    canonical_view
-                        .txs()
-                        .filter(|canonical_tx| !canonical_tx.pos.is_confirmed())
-                        .map(|canonical_tx| canonical_tx.txid),
-                );
-            }
-
-            let res = client
-                .sync(request, scan_options.batch_size, false)
-                .context("scanning the blockchain")?;
-
-            // drop lock on graph and chain
-            drop((graph, chain));
-
-            (res.chain_update, res.tx_update, None)
-        }
-    };
-
-    let db_changeset = {
-        let mut chain = chain.lock().unwrap();
-        let mut graph = graph.lock().unwrap();
-
-        let chain_changeset = chain.apply_update(chain_update.expect("request has chain tip"))?;
-
-        let mut indexed_tx_graph_changeset =
-            indexed_tx_graph::ChangeSet::<ConfirmationBlockTime, _>::default();
-        if let Some(keychain_update) = keychain_update {
-            let keychain_changeset = graph.index.reveal_to_target_multi(&keychain_update);
-            indexed_tx_graph_changeset.merge(keychain_changeset.into());
-        }
-        indexed_tx_graph_changeset.merge(graph.apply_update(tx_update));
-
-        ChangeSet {
-            local_chain: chain_changeset,
-            tx_graph: indexed_tx_graph_changeset.tx_graph,
-            indexer: indexed_tx_graph_changeset.indexer,
-            ..Default::default()
-        }
-    };
-
-    let mut db = db.lock().unwrap();
-    db.append(&db_changeset)?;
-    Ok(())
-}
diff --git a/examples/example_esplora/Cargo.toml b/examples/example_esplora/Cargo.toml
deleted file mode 100644 (file)
index ccad862..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "example_esplora"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-bdk_chain = { path = "../../crates/chain", features = ["serde"] }
-bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] }
-example_cli = { path = "../example_cli" }
-
diff --git a/examples/example_esplora/src/main.rs b/examples/example_esplora/src/main.rs
deleted file mode 100644 (file)
index 9bc3231..0000000
+++ /dev/null
@@ -1,301 +0,0 @@
-use core::f32;
-use std::{
-    collections::BTreeSet,
-    io::{self, Write},
-};
-
-use bdk_chain::{
-    bitcoin::Network,
-    keychain_txout::FullScanRequestBuilderExt,
-    spk_client::{FullScanRequest, SyncRequest},
-    CanonicalizationParams, Merge,
-};
-use bdk_esplora::{esplora_client, EsploraExt};
-use example_cli::{
-    anyhow::{self, Context},
-    clap::{self, Parser, Subcommand},
-    ChangeSet, Keychain,
-};
-
-const DB_MAGIC: &[u8] = b"bdk_example_esplora";
-const DB_PATH: &str = ".bdk_example_esplora.db";
-
-#[derive(Subcommand, Debug, Clone)]
-enum EsploraCommands {
-    /// Scans the addresses in the wallet using the esplora API.
-    Scan {
-        /// When a gap this large has been found for a keychain, it will stop.
-        #[clap(long, short = 'g', default_value = "10")]
-        stop_gap: usize,
-        #[clap(flatten)]
-        scan_options: ScanOptions,
-        #[clap(flatten)]
-        esplora_args: EsploraArgs,
-    },
-    /// Scan for particular addresses and unconfirmed transactions using the esplora API.
-    Sync {
-        /// Scan all the unused addresses.
-        #[clap(long)]
-        unused_spks: bool,
-        /// Scan every address that you have derived.
-        #[clap(long)]
-        all_spks: bool,
-        /// Scan unspent outpoints for spends or changes to confirmation status of residing tx.
-        #[clap(long)]
-        utxos: bool,
-        /// Scan unconfirmed transactions for updates.
-        #[clap(long)]
-        unconfirmed: bool,
-        #[clap(flatten)]
-        scan_options: ScanOptions,
-        #[clap(flatten)]
-        esplora_args: EsploraArgs,
-    },
-}
-
-impl EsploraCommands {
-    fn esplora_args(&self) -> EsploraArgs {
-        match self {
-            EsploraCommands::Scan { esplora_args, .. } => esplora_args.clone(),
-            EsploraCommands::Sync { esplora_args, .. } => esplora_args.clone(),
-        }
-    }
-}
-
-#[derive(clap::Args, Debug, Clone)]
-pub struct EsploraArgs {
-    /// The esplora url endpoint to connect to.
-    #[clap(long, short = 'u', env = "ESPLORA_SERVER")]
-    esplora_url: Option<String>,
-}
-
-impl EsploraArgs {
-    pub fn client(&self, network: Network) -> anyhow::Result<esplora_client::BlockingClient> {
-        let esplora_url = self.esplora_url.as_deref().unwrap_or(match network {
-            Network::Bitcoin => "https://blockstream.info/api",
-            Network::Testnet => "https://blockstream.info/testnet/api",
-            Network::Regtest => "http://localhost:3002",
-            Network::Signet => "http://signet.bitcoindevkit.net",
-            _ => panic!("unsupported network"),
-        });
-
-        let client = esplora_client::Builder::new(esplora_url).build_blocking();
-        Ok(client)
-    }
-}
-
-#[derive(Parser, Debug, Clone, PartialEq)]
-pub struct ScanOptions {
-    /// Max number of concurrent esplora server requests.
-    #[clap(long, default_value = "2")]
-    pub parallel_requests: usize,
-}
-
-fn main() -> anyhow::Result<()> {
-    let example_cli::Init {
-        args,
-        graph,
-        chain,
-        db,
-        network,
-    } = match example_cli::init_or_load::<EsploraCommands, EsploraArgs>(DB_MAGIC, DB_PATH)? {
-        Some(init) => init,
-        None => return Ok(()),
-    };
-
-    let esplora_cmd = match &args.command {
-        // These are commands that are handled by this example (sync, scan).
-        example_cli::Commands::ChainSpecific(esplora_cmd) => esplora_cmd,
-        // These are general commands handled by example_cli. Execute the cmd and return.
-        general_cmd => {
-            return example_cli::handle_commands(
-                &graph,
-                &chain,
-                &db,
-                network,
-                |esplora_args, tx| {
-                    let client = esplora_args.client(network)?;
-                    client
-                        .broadcast(tx)
-                        .map(|_| ())
-                        .map_err(anyhow::Error::from)
-                },
-                general_cmd.clone(),
-            );
-        }
-    };
-
-    let client = esplora_cmd.esplora_args().client(network)?;
-    // Prepare the `IndexedTxGraph` and `LocalChain` updates based on whether we are scanning or
-    // syncing.
-    //
-    // Scanning: We are iterating through spks of all keychains and scanning for transactions for
-    //   each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap`
-    //   number of consecutive spks have no transaction history. A Scan is done in situations of
-    //   wallet restoration. It is a special case. Applications should use "sync" style updates
-    //   after an initial scan.
-    //
-    // Syncing: We only check for specified spks, utxos and txids to update their confirmation
-    //   status or fetch missing transactions.
-    let (local_chain_changeset, indexed_tx_graph_changeset) = match &esplora_cmd {
-        EsploraCommands::Scan {
-            stop_gap,
-            scan_options,
-            ..
-        } => {
-            let request = {
-                let chain_tip = chain.lock().expect("mutex must not be poisoned").tip();
-                let indexed_graph = &*graph.lock().expect("mutex must not be poisoned");
-                FullScanRequest::builder()
-                    .chain_tip(chain_tip)
-                    .spks_from_indexer(&indexed_graph.index)
-                    .inspect({
-                        let mut once = BTreeSet::<Keychain>::new();
-                        move |keychain, spk_i, _| {
-                            if once.insert(keychain) {
-                                eprint!("\nscanning {keychain}: ");
-                            }
-                            eprint!("{spk_i} ");
-                            // Flush early to ensure we print at every iteration.
-                            let _ = io::stderr().flush();
-                        }
-                    })
-                    .build()
-            };
-
-            // The client scans keychain spks for transaction histories, stopping after `stop_gap`
-            // is reached. It returns a `TxGraph` update (`tx_update`) and a structure that
-            // represents the last active spk derivation indices of keychains
-            // (`keychain_indices_update`).
-            let update = client
-                .full_scan(request, *stop_gap, scan_options.parallel_requests)
-                .context("scanning for transactions")?;
-
-            let mut graph = graph.lock().expect("mutex must not be poisoned");
-            let mut chain = chain.lock().expect("mutex must not be poisoned");
-            // Because we did a stop gap based scan we are likely to have some updates to our
-            // deriviation indices. Usually before a scan you are on a fresh wallet with no
-            // addresses derived so we need to derive up to last active addresses the scan found
-            // before adding the transactions.
-            (
-                chain.apply_update(update.chain_update.expect("request included chain tip"))?,
-                {
-                    let index_changeset = graph
-                        .index
-                        .reveal_to_target_multi(&update.last_active_indices);
-                    let mut indexed_tx_graph_changeset = graph.apply_update(update.tx_update);
-                    indexed_tx_graph_changeset.merge(index_changeset.into());
-                    indexed_tx_graph_changeset
-                },
-            )
-        }
-        EsploraCommands::Sync {
-            mut unused_spks,
-            all_spks,
-            mut utxos,
-            mut unconfirmed,
-            scan_options,
-            ..
-        } => {
-            if !(*all_spks || unused_spks || utxos || unconfirmed) {
-                // If nothing is specifically selected, we select everything (except all spks).
-                unused_spks = true;
-                unconfirmed = true;
-                utxos = true;
-            } else if *all_spks {
-                // If all spks is selected, we don't need to also select unused spks (as unused spks
-                // is a subset of all spks).
-                unused_spks = false;
-            }
-
-            let local_tip = chain.lock().expect("mutex must not be poisoned").tip();
-            // Spks, outpoints and txids we want updates on will be accumulated here.
-            let mut request =
-                SyncRequest::builder()
-                    .chain_tip(local_tip.clone())
-                    .inspect(|item, progress| {
-                        let pc = (100 * progress.consumed()) as f32 / progress.total() as f32;
-                        match item {
-                            bdk_chain::spk_client::SyncItem::Spk((keychain, index), spk) => {
-                                eprintln!(
-                                    "[ SCANNING {pc:3.0}% ] script {} {} {}",
-                                    keychain, index, spk
-                                );
-                            }
-                            bdk_chain::spk_client::SyncItem::Txid(txid) => {
-                                eprintln!("[ SCANNING {pc:3.0}% ] txid {}", txid);
-                            }
-                            bdk_chain::spk_client::SyncItem::OutPoint(op) => {
-                                eprintln!("[ SCANNING {pc:3.0}% ] outpoint {}", op);
-                            }
-                        }
-                        let _ = io::stderr().flush();
-                    });
-
-            // Get a short lock on the structures to get spks, utxos, and txs that we are interested
-            // in.
-            {
-                let graph = graph.lock().unwrap();
-                let chain = chain.lock().unwrap();
-                let canonical_view = graph.canonical_view(
-                    &*chain,
-                    local_tip.block_id(),
-                    CanonicalizationParams::default(),
-                );
-
-                request = request
-                    .expected_spk_txids(canonical_view.list_expected_spk_txids(&graph.index, ..));
-                if *all_spks {
-                    request = request.spks_with_indexes(graph.index.revealed_spks(..));
-                }
-                if unused_spks {
-                    request = request.spks_with_indexes(graph.index.unused_spks());
-                }
-                if utxos {
-                    // We want to search for whether the UTXO is spent, and spent by which
-                    // transaction. We provide the outpoint of the UTXO to
-                    // `EsploraExt::update_tx_graph_without_keychain`.
-                    let init_outpoints = graph.index.outpoints();
-                    request = request.outpoints(
-                        canonical_view
-                            .filter_unspent_outpoints(init_outpoints.iter().cloned())
-                            .map(|(_, utxo)| utxo.outpoint),
-                    );
-                };
-                if unconfirmed {
-                    // We want to search for whether the unconfirmed transaction is now confirmed.
-                    // We provide the unconfirmed txids to
-                    // `EsploraExt::update_tx_graph_without_keychain`.
-                    request = request.txids(
-                        canonical_view
-                            .txs()
-                            .filter(|canonical_tx| !canonical_tx.pos.is_confirmed())
-                            .map(|canonical_tx| canonical_tx.txid),
-                    );
-                }
-            }
-
-            let update = client.sync(request, scan_options.parallel_requests)?;
-
-            (
-                chain
-                    .lock()
-                    .unwrap()
-                    .apply_update(update.chain_update.expect("request has chain tip"))?,
-                graph.lock().unwrap().apply_update(update.tx_update),
-            )
-        }
-    };
-
-    println!();
-
-    // We persist the changes
-    let mut db = db.lock().unwrap();
-    db.append(&ChangeSet {
-        local_chain: local_chain_changeset,
-        tx_graph: indexed_tx_graph_changeset.tx_graph,
-        indexer: indexed_tx_graph_changeset.indexer,
-        ..Default::default()
-    })?;
-    Ok(())
-}