]> Untitled Git - bdk-cli/commitdiff
refactor: remove rpc, cbf, cleanup features
authorSteve Myers <steve@notmandatory.org>
Thu, 13 Feb 2025 05:26:54 +0000 (23:26 -0600)
committerVihiga Tyonum <withtvpeter@gmail.com>
Tue, 18 Feb 2025 23:06:33 +0000 (00:06 +0100)
- fix repl

14 files changed:
.github/workflows/cont_integration.yml
Cargo.lock
Cargo.toml
README.md
build.rs [deleted file]
clippy.toml
src/commands.rs
src/error.rs
src/handlers.rs
src/main.rs
src/nodes.rs [deleted file]
src/utils.rs
src/wasm.rs [deleted file]
tests/integration.rs

index a8912c2947f9211130fda7e89beb26f989e8aab5..7ce1143fffe903751dce84b2f2f57078d423c8f1 100644 (file)
@@ -10,24 +10,15 @@ jobs:
     strategy:
       matrix:
         rust:
-          - 1.65.0 # STABLE
-          - 1.57.0 # MSRV
+          - stable # STABLE
+          - 1.75.0 # MSRV
         features:
           - default
-          - electrum,sqlite-db
-          - electrum,key-value-db
+          - electrum,sqlite
           - electrum
-          - esplora-ureq,sqlite-db
-          - esplora-reqwest,sqlite-db
-          - compiler,sqlite-db
-          #- compact_filters,sqlite-db # Compact Filters temporarily disabled
-          - rpc,sqlite-db
-          - reserves,electrum,sqlite-db
-          - electrum,verify,sqlite-db
-          # regtest-* features are experimental and not fully usable
-          - regtest-bitcoin,sqlite-db
-          - regtest-electrum,sqlite-db
-
+          - esplora,sqlite
+          - esplora
+          - compiler,sqlite
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -48,17 +39,6 @@ jobs:
           profile: minimal
           override: true
           components: rustfmt, clippy
-      - name: Pin dependencies for MSRV
-        if: matrix.rust == '1.57.0'
-        run: |
-          cargo update
-          cargo update -p log --precise 0.4.18
-          cargo update -p hashlink --precise 0.8.0
-          cargo update -p tempfile --precise 3.6.0
-          cargo update -p base64ct --precise 1.5.3
-          cargo update -p cc --precise 1.0.81
-          cargo update -p tokio --precise 1.29.1
-          cargo update -p flate2 --precise 1.0.26
       - name: Build
         run: cargo build --no-default-features --features repl,${{ matrix.features }} --locked
       - name: Clippy
@@ -91,7 +71,7 @@ jobs:
       - run: sudo apt-get update || exit 1
       - run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
       - name: Set default toolchain
-        run: rustup default 1.65.0
+        run: rustup default stable
       - name: Set profile
         run: rustup set profile minimal
       - name: Add target wasm32
index a6766652f6e76c08c7f00e000d04a3143542d3d1..961375da66fe93021f80173d4bd63379ab469014 100644 (file)
@@ -17,18 +17,6 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 
-[[package]]
-name = "aes"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
-dependencies = [
- "cfg-if",
- "cipher",
- "cpufeatures",
- "opaque-debug",
-]
-
 [[package]]
 name = "ahash"
 version = "0.8.11"
@@ -51,46 +39,70 @@ dependencies = [
 ]
 
 [[package]]
-name = "anyhow"
-version = "1.0.95"
+name = "anstream"
+version = "0.6.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
 
 [[package]]
-name = "arrayvec"
-version = "0.7.6"
+name = "anstyle"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
 
 [[package]]
-name = "async-trait"
-version = "0.1.86"
+name = "anstyle-parse"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.98",
+ "utf8parse",
 ]
 
 [[package]]
-name = "atty"
-version = "0.2.14"
+name = "anstyle-query"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
 dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
-name = "autocfg"
-version = "0.1.8"
+name = "anstyle-wincon"
+version = "3.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
 dependencies = [
- "autocfg 1.4.0",
+ "anstyle",
+ "once_cell",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-trait"
+version = "0.1.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -167,109 +179,36 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
-[[package]]
-name = "base64ct"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
-
-[[package]]
-name = "bdk"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fc1fc1a92e0943bfbcd6eb7d32c1b2a79f2f1357eb1e2eee9d7f36d6d7ca44a"
-dependencies = [
- "async-trait",
- "bdk-macros",
- "bitcoin 0.30.2",
- "getrandom 0.2.15",
- "js-sys",
- "log",
- "miniscript 10.2.0",
- "rand 0.8.5",
- "serde",
- "serde_json",
- "tokio",
-]
-
 [[package]]
 name = "bdk-cli"
 version = "0.27.1"
 dependencies = [
- "base64 0.13.1",
- "bdk-macros",
- "bdk-reserves",
  "bdk_bitcoind_rpc",
  "bdk_electrum",
  "bdk_esplora",
+ "bdk_kyoto",
  "bdk_wallet",
  "clap",
- "dirs-next",
- "electrsd",
+ "dirs",
  "env_logger",
- "fd-lock",
- "getrandom 0.2.15",
- "hwi",
- "js-sys",
  "log",
- "rand 0.6.5",
- "regex",
- "rustyline",
- "secp256k1 0.22.2",
- "serde",
  "serde_json",
+ "shlex",
  "thiserror 2.0.11",
  "tokio",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wasm-logger",
- "zeroize",
- "zip",
-]
-
-[[package]]
-name = "bdk-macros"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81c1980e50ae23bb6efa9283ae8679d6ea2c6fa6a99fe62533f65f4a25a1a56c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "bdk-reserves"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72f152e0fa9aeaf05ca4b1ca622b1a156992c123268e343c5b3987c3227b6707"
-dependencies = [
- "bdk",
- "bitcoinconsensus",
- "log",
 ]
 
 [[package]]
 name = "bdk_bitcoind_rpc"
-version = "0.17.1"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ac1a1da33a22949c3e2b3a715431eaed50c2c9278c8cdac06f0254b7659291f"
+checksum = "f96e987f8736f34c1628743684f66b31faeda72f3bc86b60314197f2d8cb9731"
 dependencies = [
  "bdk_core",
- "bitcoin 0.32.5",
+ "bitcoin",
  "bitcoincore-rpc",
 ]
 
-[[package]]
-name = "bdk_chain"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "163b064557cee078e8ee5dd2c88944204506f7b2b1524f78e8fcba38c346da7b"
-dependencies = [
- "bitcoin 0.32.5",
- "miniscript 12.3.0",
-]
-
 [[package]]
 name = "bdk_chain"
 version = "0.21.1"
@@ -277,8 +216,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4955734f97b2baed3f36d16ae7c203fdde31ae85391ac44ee3cbcaf0886db5ce"
 dependencies = [
  "bdk_core",
- "bitcoin 0.32.5",
- "miniscript 12.3.0",
+ "bitcoin",
+ "miniscript",
  "rusqlite",
  "serde",
 ]
@@ -289,16 +228,16 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b545aea1efc090e4f71f1dd5468090d9f54c3de48002064c04895ef811fbe0b2"
 dependencies = [
- "bitcoin 0.32.5",
+ "bitcoin",
  "hashbrown 0.14.5",
  "serde",
 ]
 
 [[package]]
 name = "bdk_electrum"
-version = "0.20.1"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b272d5a3228799f7c917255fe26e788f6c29dd4a084a342d274a44352bbc0915"
+checksum = "cb2ac12acbf8f263c59d74ceeeed015e0fd00982aa8e71824f63d102999be040"
 dependencies = [
  "bdk_core",
  "electrum-client",
@@ -306,15 +245,25 @@ dependencies = [
 
 [[package]]
 name = "bdk_esplora"
-version = "0.15.0"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "089babab213bbb32518bad79a7313ebb4c85a52c18c8b558402dfa810c27de3f"
+checksum = "3d7fdf5efbebabc0c0bb46c0348ef0d4db505856c7d6c5d50cebba1e5eda5fe4"
 dependencies = [
  "async-trait",
- "bdk_chain 0.16.0",
+ "bdk_core",
  "esplora-client",
  "futures",
- "miniscript 12.3.0",
+ "miniscript",
+]
+
+[[package]]
+name = "bdk_kyoto"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "669e8d613e93c400ae82596404732fbf521cee41a3c8b96a6c011a4dea21a5dc"
+dependencies = [
+ "bdk_wallet",
+ "kyoto-cbf",
 ]
 
 [[package]]
@@ -323,21 +272,15 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a13c947be940d32a91b876fc5223a6d839a40bc219496c5c78af74714b1b3f7"
 dependencies = [
- "bdk_chain 0.21.1",
+ "bdk_chain",
  "bip39",
- "bitcoin 0.32.5",
- "miniscript 12.3.0",
- "rand_core 0.6.4",
+ "bitcoin",
+ "miniscript",
+ "rand_core",
  "serde",
  "serde_json",
 ]
 
-[[package]]
-name = "bech32"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
-
 [[package]]
 name = "bech32"
 version = "0.11.0"
@@ -363,34 +306,32 @@ dependencies = [
  "regex",
  "rustc-hash",
  "shlex",
- "syn 2.0.98",
+ "syn",
  "which",
 ]
 
 [[package]]
-name = "bip39"
-version = "2.1.0"
+name = "bip324"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387"
+checksum = "b443a76f86143c093b211628be683ee592a097d316db6b90f723ed816bde1a49"
 dependencies = [
- "bitcoin_hashes 0.13.0",
- "serde",
- "unicode-normalization",
+ "bitcoin",
+ "bitcoin_hashes 0.15.0",
+ "chacha20-poly1305",
+ "rand",
+ "tokio",
 ]
 
 [[package]]
-name = "bitcoin"
-version = "0.30.2"
+name = "bip39"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462"
+checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387"
 dependencies = [
- "base64 0.13.1",
- "bech32 0.9.1",
- "bitcoin-private",
- "bitcoin_hashes 0.12.0",
- "hex_lit",
- "secp256k1 0.27.0",
+ "bitcoin_hashes 0.13.0",
  "serde",
+ "unicode-normalization",
 ]
 
 [[package]]
@@ -401,14 +342,14 @@ checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026"
 dependencies = [
  "base58ck",
  "base64 0.21.7",
- "bech32 0.11.0",
+ "bech32",
  "bitcoin-internals 0.3.0",
- "bitcoin-io",
+ "bitcoin-io 0.1.3",
  "bitcoin-units",
  "bitcoin_hashes 0.14.0",
  "hex-conservative 0.2.1",
  "hex_lit",
- "secp256k1 0.29.1",
+ "secp256k1",
  "serde",
 ]
 
@@ -427,6 +368,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bitcoin-internals"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b854212e29b96c8f0fe04cab11d57586c8f3257de0d146c76cb3b42b3eb9118"
+
 [[package]]
 name = "bitcoin-io"
 version = "0.1.3"
@@ -434,10 +381,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
 
 [[package]]
-name = "bitcoin-private"
-version = "0.1.0"
+name = "bitcoin-io"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
+checksum = "26792cd2bf245069a1c5acb06aa7ad7abe1de69b507c90b490bca81e0665d0ee"
+dependencies = [
+ "bitcoin-internals 0.4.0",
+]
 
 [[package]]
 name = "bitcoin-units"
@@ -449,16 +399,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "bitcoin_hashes"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501"
-dependencies = [
- "bitcoin-private",
- "serde",
-]
-
 [[package]]
 name = "bitcoin_hashes"
 version = "0.13.0"
@@ -475,19 +415,19 @@ version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
 dependencies = [
- "bitcoin-io",
+ "bitcoin-io 0.1.3",
  "hex-conservative 0.2.1",
  "serde",
 ]
 
 [[package]]
-name = "bitcoinconsensus"
-version = "0.19.0-3"
+name = "bitcoin_hashes"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a8aa43b5cd02f856cb126a9af819e77b8910fdd74dd1407be649f2f5fe3a1b5"
+checksum = "e0982261c82a50d89d1a411602afee0498b3e0debe3d36693f0c661352809639"
 dependencies = [
- "cc",
- "libc",
+ "bitcoin-io 0.2.0",
+ "hex-conservative 0.3.0",
 ]
 
 [[package]]
@@ -509,7 +449,7 @@ version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a"
 dependencies = [
- "bitcoin 0.32.5",
+ "bitcoin",
  "serde",
  "serde_json",
 ]
@@ -526,15 +466,6 @@ version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
 
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "bumpalo"
 version = "3.17.0"
@@ -553,32 +484,11 @@ version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
 
-[[package]]
-name = "bzip2"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
-dependencies = [
- "bzip2-sys",
- "libc",
-]
-
-[[package]]
-name = "bzip2-sys"
-version = "0.1.11+1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
-]
-
 [[package]]
 name = "cc"
-version = "1.2.13"
+version = "1.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
+checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
 dependencies = [
  "jobserver",
  "libc",
@@ -601,13 +511,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "cipher"
-version = "0.3.0"
+name = "chacha20-poly1305"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
-dependencies = [
- "generic-array",
-]
+checksum = "8ac8be588b1de2b7f1537ed39ba453a388d2cce60ce78ef5db449f71bebe58ba"
 
 [[package]]
 name = "clang-sys"
@@ -622,62 +529,43 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.2.25"
+version = "4.5.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
 dependencies = [
- "atty",
- "bitflags 1.3.2",
+ "clap_builder",
  "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
+dependencies = [
+ "anstream",
+ "anstyle",
  "clap_lex",
- "indexmap 1.9.3",
- "once_cell",
  "strsim",
- "termcolor",
- "textwrap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "3.2.25"
+version = "4.5.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
+checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
 dependencies = [
  "heck",
- "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
-dependencies = [
- "os_str_bytes",
-]
-
-[[package]]
-name = "clipboard-win"
-version = "4.5.0"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
-dependencies = [
- "error-code",
- "str-buf",
- "winapi",
-]
-
-[[package]]
-name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags 1.3.2",
-]
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
 
 [[package]]
 name = "cmake"
@@ -689,10 +577,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "constant_time_eq"
-version = "0.1.5"
+name = "colorchoice"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
 name = "core-foundation"
@@ -711,109 +599,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
-name = "corepc-client"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb85495f0577f4765ea2ece0a69003d38acdddba2ac06bdde180ac2b969a9a63"
-dependencies = [
- "bitcoin 0.32.5",
- "corepc-types",
- "jsonrpc",
- "log",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "corepc-node"
-version = "0.5.0"
+name = "dirs"
+version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "887faf7fdf38a5b99b3efb69e18e27fb9de77c812c5c48956a8076c7f9076b99"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
 dependencies = [
- "anyhow",
- "corepc-client",
- "log",
- "serde_json",
- "tempfile",
- "which",
+ "dirs-sys",
 ]
 
 [[package]]
-name = "corepc-types"
+name = "dirs-sys"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282d9bd7fd9d944471a0c0ad44fd1581acd87a79739652c5455ffdae25177db6"
-dependencies = [
- "bitcoin 0.32.5",
- "bitcoin-internals 0.3.0",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crc32fast"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
-[[package]]
-name = "digest"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
-dependencies = [
- "block-buffer",
- "crypto-common",
- "subtle",
-]
-
-[[package]]
-name = "dirs-next"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
-dependencies = [
- "cfg-if",
- "dirs-sys-next",
-]
-
-[[package]]
-name = "dirs-sys-next"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
 dependencies = [
  "libc",
+ "option-ext",
  "redox_users",
- "winapi",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -824,7 +627,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -839,34 +642,17 @@ version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
 
-[[package]]
-name = "electrsd"
-version = "0.31.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaa7f6661c5890f51f09f54fb225cd37058202cf0ba53dbbd92a4374fe035904"
-dependencies = [
- "bitcoin_hashes 0.14.0",
- "corepc-client",
- "corepc-node",
- "electrum-client",
- "log",
- "minreq",
- "nix 0.25.1",
- "which",
- "zip",
-]
-
 [[package]]
 name = "electrum-client"
-version = "0.22.0"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d627e4feaf3009c10c8a0eb06d6ceb4ce1ff861849157fb35e8155d9706babb6"
+checksum = "a8ed4d35bb98a55540bb5b735731486febddf9cc9b6e96f5b3fd2536eed81a4e"
 dependencies = [
- "bitcoin 0.32.5",
+ "bitcoin",
  "byteorder",
  "libc",
  "log",
- "rustls 0.23.22",
+ "rustls 0.23.23",
  "serde",
  "serde_json",
  "webpki-roots",
@@ -883,29 +669,33 @@ dependencies = [
 ]
 
 [[package]]
-name = "endian-type"
-version = "0.1.2"
+name = "env_filter"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+dependencies = [
+ "log",
+ "regex",
+]
 
 [[package]]
 name = "env_logger"
-version = "0.7.1"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
 dependencies = [
- "atty",
+ "anstream",
+ "anstyle",
+ "env_filter",
  "humantime",
  "log",
- "regex",
- "termcolor",
 ]
 
 [[package]]
 name = "equivalent"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
 name = "errno"
@@ -917,28 +707,19 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
-[[package]]
-name = "error-code"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
-dependencies = [
- "libc",
- "str-buf",
-]
-
 [[package]]
 name = "esplora-client"
-version = "0.8.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69c6d27ef4ff21019edd98aa92199757e10a88065bbfcef6bb750ca6ec5e4a45"
+checksum = "d0da3c186d286e046253ccdc4bb71aa87ef872e4eff2045947c0c4fe3d2b2efc"
 dependencies = [
- "bitcoin 0.32.5",
+ "bitcoin",
  "hex-conservative 0.2.1",
  "log",
  "minreq",
  "reqwest",
  "serde",
+ "tokio",
 ]
 
 [[package]]
@@ -959,27 +740,6 @@ version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
-[[package]]
-name = "fd-lock"
-version = "3.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
-dependencies = [
- "cfg-if",
- "libc",
- "windows-sys 0.28.0",
-]
-
-[[package]]
-name = "flate2"
-version = "1.0.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
-dependencies = [
- "crc32fast",
- "miniz_oxide",
-]
-
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -1016,12 +776,6 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
 
-[[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
-
 [[package]]
 name = "futures"
 version = "0.3.31"
@@ -1078,7 +832,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -1111,16 +865,6 @@ dependencies = [
  "slab",
 ]
 
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.2.15"
@@ -1128,10 +872,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
  "cfg-if",
- "js-sys",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "wasm-bindgen",
 ]
 
 [[package]]
@@ -1170,19 +912,13 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.7.1",
+ "indexmap",
  "slab",
  "tokio",
  "tokio-util",
  "tracing",
 ]
 
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
 [[package]]
 name = "hashbrown"
 version = "0.14.5"
@@ -1210,18 +946,15 @@ dependencies = [
 
 [[package]]
 name = "heck"
-version = "0.4.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
 [[package]]
 name = "hex-conservative"
@@ -1239,19 +972,19 @@ dependencies = [
 ]
 
 [[package]]
-name = "hex_lit"
-version = "0.1.1"
+name = "hex-conservative"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
+checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
+dependencies = [
+ "arrayvec",
+]
 
 [[package]]
-name = "hmac"
-version = "0.12.1"
+name = "hex_lit"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
-dependencies = [
- "digest",
-]
+checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
 
 [[package]]
 name = "home"
@@ -1298,25 +1031,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "humantime"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
-dependencies = [
- "quick-error",
-]
-
-[[package]]
-name = "hwi"
-version = "0.10.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d7c8b63c5d878746cc6217ed4dd8112c803a9dc5063da4c69228e00b300dbc8"
-dependencies = [
- "bdk_wallet",
- "bitcoin 0.32.5",
- "pyo3",
- "serde",
- "serde_json",
-]
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
 name = "hyper"
@@ -1470,7 +1187,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -1494,16 +1211,6 @@ dependencies = [
  "icu_properties",
 ]
 
-[[package]]
-name = "indexmap"
-version = "1.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg 1.4.0",
- "hashbrown 0.12.3",
-]
-
 [[package]]
 name = "indexmap"
 version = "2.7.1"
@@ -1514,18 +1221,18 @@ dependencies = [
  "hashbrown 0.15.2",
 ]
 
-[[package]]
-name = "indoc"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
-
 [[package]]
 name = "ipnet"
 version = "2.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
 
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
 [[package]]
 name = "itertools"
 version = "0.12.1"
@@ -1552,10 +1259,11 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.56"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
@@ -1571,6 +1279,18 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "kyoto-cbf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6943c874dd9f43175b3751d091d11f43a0d4c9a9bc10751c0f19a70c1862d64e"
+dependencies = [
+ "bip324",
+ "bitcoin",
+ "rusqlite",
+ "tokio",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.5.0"
@@ -1638,7 +1358,7 @@ version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
- "autocfg 1.4.0",
+ "autocfg",
  "scopeguard",
 ]
 
@@ -1654,24 +1374,6 @@ version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
-[[package]]
-name = "memoffset"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
-dependencies = [
- "autocfg 1.4.0",
-]
-
-[[package]]
-name = "memoffset"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
-dependencies = [
- "autocfg 1.4.0",
-]
-
 [[package]]
 name = "mime"
 version = "0.3.17"
@@ -1684,33 +1386,22 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
-[[package]]
-name = "miniscript"
-version = "10.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d371924f9eb7aa860ab395baaaa0bcdfa81a32f330b538c4e2c04617b2722fe3"
-dependencies = [
- "bitcoin 0.30.2",
- "bitcoin-private",
- "serde",
-]
-
 [[package]]
 name = "miniscript"
 version = "12.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5bd3c9608217b0d6fa9c9c8ddd875b85ab72bd4311cfc8db35e1b5a08fc11f4d"
 dependencies = [
- "bech32 0.11.0",
- "bitcoin 0.32.5",
+ "bech32",
+ "bitcoin",
  "serde",
 ]
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
+checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
 dependencies = [
  "adler2",
 ]
@@ -1733,13 +1424,13 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "1.0.3"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
 dependencies = [
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.52.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1759,42 +1450,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "nibble_vec"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
-dependencies = [
- "smallvec",
-]
-
-[[package]]
-name = "nix"
-version = "0.22.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
-dependencies = [
- "bitflags 1.3.2",
- "cc",
- "cfg-if",
- "libc",
- "memoffset 0.6.5",
-]
-
-[[package]]
-name = "nix"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
-dependencies = [
- "autocfg 1.4.0",
- "bitflags 1.3.2",
- "cfg-if",
- "libc",
- "memoffset 0.6.5",
- "pin-utils",
-]
-
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -1806,11 +1461,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "num_threads"
-version = "0.1.7"
+name = "num_cpus"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
 dependencies = [
+ "hermit-abi",
  "libc",
 ]
 
@@ -1829,17 +1485,11 @@ version = "1.20.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
 
-[[package]]
-name = "opaque-debug"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-
 [[package]]
 name = "openssl"
-version = "0.10.70"
+version = "0.10.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6"
+checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
 dependencies = [
  "bitflags 2.8.0",
  "cfg-if",
@@ -1858,7 +1508,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -1869,9 +1519,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.105"
+version = "0.9.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc"
+checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
 dependencies = [
  "cc",
  "libc",
@@ -1880,10 +1530,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "os_str_bytes"
-version = "6.6.1"
+name = "option-ext"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "parking_lot"
@@ -1908,349 +1558,101 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
-[[package]]
-name = "password-hash"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
-dependencies = [
- "base64ct",
- "rand_core 0.6.4",
- "subtle",
-]
-
 [[package]]
 name = "paste"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
-[[package]]
-name = "pbkdf2"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
-dependencies = [
- "digest",
- "hmac",
- "password-hash",
- "sha2",
-]
-
-[[package]]
-name = "percent-encoding"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "pkg-config"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
-
-[[package]]
-name = "portable-atomic"
-version = "1.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
-dependencies = [
- "zerocopy",
-]
-
-[[package]]
-name = "prettyplease"
-version = "0.2.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
-dependencies = [
- "proc-macro2",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.93"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "pyo3"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
-dependencies = [
- "cfg-if",
- "indoc",
- "libc",
- "memoffset 0.9.1",
- "parking_lot",
- "portable-atomic",
- "pyo3-build-config",
- "pyo3-ffi",
- "pyo3-macros",
- "unindent",
-]
-
-[[package]]
-name = "pyo3-build-config"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
-dependencies = [
- "once_cell",
- "target-lexicon",
-]
-
-[[package]]
-name = "pyo3-ffi"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
-dependencies = [
- "libc",
- "pyo3-build-config",
-]
-
-[[package]]
-name = "pyo3-macros"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
-dependencies = [
- "proc-macro2",
- "pyo3-macros-backend",
- "quote",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "pyo3-macros-backend"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
-dependencies = [
- "heck",
- "proc-macro2",
- "pyo3-build-config",
- "quote",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
-[[package]]
-name = "quote"
-version = "1.0.38"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "radix_trie"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
-dependencies = [
- "endian-type",
- "nibble_vec",
-]
-
-[[package]]
-name = "rand"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
-dependencies = [
- "autocfg 0.1.8",
- "libc",
- "rand_chacha 0.1.1",
- "rand_core 0.4.2",
- "rand_hc",
- "rand_isaac",
- "rand_jitter",
- "rand_os",
- "rand_pcg",
- "rand_xorshift",
- "winapi",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
-dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.4",
-]
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
-name = "rand_core"
-version = "0.3.1"
+name = "pin-project-lite"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-dependencies = [
- "rand_core 0.4.2",
-]
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 
 [[package]]
-name = "rand_core"
-version = "0.4.2"
+name = "pin-utils"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
-name = "rand_core"
-version = "0.6.4"
+name = "pkg-config"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.15",
-]
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
 
 [[package]]
-name = "rand_hc"
-version = "0.1.0"
+name = "ppv-lite86"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
 dependencies = [
- "rand_core 0.3.1",
+ "zerocopy",
 ]
 
 [[package]]
-name = "rand_isaac"
-version = "0.1.1"
+name = "prettyplease"
+version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
 dependencies = [
- "rand_core 0.3.1",
+ "proc-macro2",
+ "syn",
 ]
 
 [[package]]
-name = "rand_jitter"
-version = "0.1.4"
+name = "proc-macro2"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 dependencies = [
- "libc",
- "rand_core 0.4.2",
- "winapi",
+ "unicode-ident",
 ]
 
 [[package]]
-name = "rand_os"
-version = "0.1.3"
+name = "quote"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 dependencies = [
- "cloudabi",
- "fuchsia-cprng",
- "libc",
- "rand_core 0.4.2",
- "rdrand",
- "wasm-bindgen",
- "winapi",
+ "proc-macro2",
 ]
 
 [[package]]
-name = "rand_pcg"
-version = "0.1.2"
+name = "rand"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.4.2",
+ "libc",
+ "rand_chacha",
+ "rand_core",
 ]
 
 [[package]]
-name = "rand_xorshift"
-version = "0.1.1"
+name = "rand_chacha"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
- "rand_core 0.3.1",
+ "ppv-lite86",
+ "rand_core",
 ]
 
 [[package]]
-name = "rdrand"
-version = "0.4.0"
+name = "rand_core"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "rand_core 0.3.1",
+ "getrandom 0.2.15",
 ]
 
 [[package]]
@@ -2264,13 +1666,13 @@ dependencies = [
 
 [[package]]
 name = "redox_users"
-version = "0.4.6"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
 dependencies = [
  "getrandom 0.2.15",
  "libredox",
- "thiserror 1.0.69",
+ "thiserror 2.0.11",
 ]
 
 [[package]]
@@ -2345,15 +1747,14 @@ dependencies = [
 
 [[package]]
 name = "ring"
-version = "0.17.8"
+version = "0.17.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
 dependencies = [
  "cc",
  "cfg-if",
  "getrandom 0.2.15",
  "libc",
- "spin",
  "untrusted",
  "windows-sys 0.52.0",
 ]
@@ -2411,9 +1812,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.22"
+version = "0.23.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7"
+checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
 dependencies = [
  "aws-lc-rs",
  "log",
@@ -2462,28 +1863,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "rustyline"
-version = "9.0.0"
+name = "rustversion"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "790487c3881a63489ae77126f57048b42d62d3b2bafbf37453ea19eedb6340d6"
-dependencies = [
- "bitflags 1.3.2",
- "cfg-if",
- "clipboard-win",
- "dirs-next",
- "fd-lock",
- "libc",
- "log",
- "memchr",
- "nix 0.22.3",
- "radix_trie",
- "scopeguard",
- "smallvec",
- "unicode-segmentation",
- "unicode-width",
- "utf8parse",
- "winapi",
-]
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
 
 [[package]]
 name = "ryu"
@@ -2516,27 +1899,6 @@ dependencies = [
  "untrusted",
 ]
 
-[[package]]
-name = "secp256k1"
-version = "0.22.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a"
-dependencies = [
- "secp256k1-sys 0.5.2",
-]
-
-[[package]]
-name = "secp256k1"
-version = "0.27.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f"
-dependencies = [
- "bitcoin_hashes 0.12.0",
- "rand 0.8.5",
- "secp256k1-sys 0.8.1",
- "serde",
-]
-
 [[package]]
 name = "secp256k1"
 version = "0.29.1"
@@ -2544,29 +1906,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
 dependencies = [
  "bitcoin_hashes 0.14.0",
- "rand 0.8.5",
- "secp256k1-sys 0.10.1",
+ "rand",
+ "secp256k1-sys",
  "serde",
 ]
 
-[[package]]
-name = "secp256k1-sys"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "secp256k1-sys"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
-dependencies = [
- "cc",
-]
-
 [[package]]
 name = "secp256k1-sys"
 version = "0.10.1"
@@ -2616,7 +1960,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -2644,47 +1988,34 @@ dependencies = [
 ]
 
 [[package]]
-name = "sha1"
-version = "0.10.6"
+name = "shlex"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
-name = "sha2"
-version = "0.10.8"
+name = "signal-hook-registry"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
 dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
+ "libc",
 ]
 
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
 [[package]]
 name = "slab"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
 dependencies = [
- "autocfg 1.4.0",
+ "autocfg",
 ]
 
 [[package]]
 name = "smallvec"
-version = "1.13.2"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
 
 [[package]]
 name = "socket2"
@@ -2696,29 +2027,17 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "spin"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
-
 [[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
-[[package]]
-name = "str-buf"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
-
 [[package]]
 name = "strsim"
-version = "0.10.0"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "subtle"
@@ -2726,17 +2045,6 @@ version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
 [[package]]
 name = "syn"
 version = "2.0.98"
@@ -2762,7 +2070,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -2786,17 +2094,11 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "target-lexicon"
-version = "0.12.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
-
 [[package]]
 name = "tempfile"
-version = "3.16.0"
+version = "3.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
+checksum = "a40f762a77d2afa88c2d919489e390a12bdd261ed568e60cfa7e48d4e20f0d33"
 dependencies = [
  "cfg-if",
  "fastrand",
@@ -2806,21 +2108,6 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
-[[package]]
-name = "termcolor"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "textwrap"
-version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
-
 [[package]]
 name = "thiserror"
 version = "1.0.69"
@@ -2847,7 +2134,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -2858,27 +2145,9 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "time"
-version = "0.3.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82501a4c1c0330d640a6e176a3d6a204f5ec5237aca029029d21864a902e27b0"
-dependencies = [
- "itoa",
- "libc",
- "num_threads",
- "time-macros",
+ "syn",
 ]
 
-[[package]]
-name = "time-macros"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
-
 [[package]]
 name = "tinystr"
 version = "0.7.6"
@@ -2906,29 +2175,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.43.0"
+version = "1.38.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
+checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
 dependencies = [
  "backtrace",
  "bytes",
  "libc",
  "mio",
+ "num_cpus",
+ "parking_lot",
  "pin-project-lite",
+ "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "windows-sys 0.52.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "2.5.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -2997,12 +2269,6 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
-[[package]]
-name = "typenum"
-version = "1.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-
 [[package]]
 name = "unicode-ident"
 version = "1.0.16"
@@ -3018,24 +2284,6 @@ dependencies = [
  "tinyvec",
 ]
 
-[[package]]
-name = "unicode-segmentation"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
-
-[[package]]
-name = "unicode-width"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
-
-[[package]]
-name = "unindent"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
-
 [[package]]
 name = "untrusted"
 version = "0.9.0"
@@ -3109,48 +2357,48 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.79"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
 dependencies = [
  "cfg-if",
- "serde",
- "serde_json",
+ "once_cell",
+ "rustversion",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.79"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
 dependencies = [
  "bumpalo",
- "lazy_static",
  "log",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.29"
+version = "0.4.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
 dependencies = [
  "cfg-if",
  "js-sys",
+ "once_cell",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.79"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3158,39 +2406,31 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.79"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.79"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
-
-[[package]]
-name = "wasm-logger"
-version = "0.2.0"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
 dependencies = [
- "log",
- "wasm-bindgen",
- "web-sys",
+ "unicode-ident",
 ]
 
 [[package]]
 name = "web-sys"
-version = "0.3.56"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3230,34 +2470,12 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
-[[package]]
-name = "winapi-util"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
-dependencies = [
- "windows-sys 0.59.0",
-]
-
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
-[[package]]
-name = "windows-sys"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
-dependencies = [
- "windows_aarch64_msvc 0.28.0",
- "windows_i686_gnu 0.28.0",
- "windows_i686_msvc 0.28.0",
- "windows_x86_64_gnu 0.28.0",
- "windows_x86_64_msvc 0.28.0",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
@@ -3328,12 +2546,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
-
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
@@ -3346,12 +2558,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
-[[package]]
-name = "windows_i686_gnu"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
-
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
@@ -3370,12 +2576,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
-[[package]]
-name = "windows_i686_msvc"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
-
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
@@ -3388,12 +2588,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
-
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
@@ -3418,12 +2612,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
-
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
@@ -3487,7 +2675,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
  "synstructure",
 ]
 
@@ -3509,7 +2697,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
 ]
 
 [[package]]
@@ -3529,7 +2717,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn",
  "synstructure",
 ]
 
@@ -3558,54 +2746,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
-]
-
-[[package]]
-name = "zip"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
-dependencies = [
- "aes",
- "byteorder",
- "bzip2",
- "constant_time_eq",
- "crc32fast",
- "crossbeam-utils",
- "flate2",
- "hmac",
- "pbkdf2",
- "sha1",
- "time",
- "zstd",
-]
-
-[[package]]
-name = "zstd"
-version = "0.11.2+zstd.1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
-dependencies = [
- "zstd-safe",
-]
-
-[[package]]
-name = "zstd-safe"
-version = "5.0.2+zstd.1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
-dependencies = [
- "libc",
- "zstd-sys",
-]
-
-[[package]]
-name = "zstd-sys"
-version = "2.0.13+zstd.1.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
-dependencies = [
- "cc",
- "pkg-config",
+ "syn",
 ]
index 4dadc11cdbb8a88b97cef8f9e008f3f208730574..04aa35f108e514211beab7b195d87a6284ab8c0c 100644 (file)
@@ -12,95 +12,40 @@ readme = "README.md"
 license = "MIT"
 
 [dependencies]
-bdk_wallet={ version = "1.0.0", features = ["rusqlite", "keys-bip39"]}
-bdk-macros = "0.6"
-clap = { version = "3.2.22", features = ["derive"] }
-serde_json = "1.0"
+bdk_wallet = { version = "1.0.0", features = ["rusqlite", "keys-bip39"] }
+clap = { version = "4.5", features = ["derive","env"] }
+dirs = {  version = "6.0.0" }
+env_logger = "0.11.6"
 log = "0.4"
-zeroize = "1.8.1"
-dirs-next = "2.0"
-env_logger = "0.7"
-base64 = "^0.13"
+serde_json = "1.0"
 thiserror = "2.0.11"
-
+tokio = { version = "1", features = ["full"] }
 
 # Optional dependencies
-rustyline = { version = "~9.0", optional = true }
-fd-lock = { version = "=3.0.2", optional = true }
-regex = { version = "1", optional = true }
-bdk-reserves = {version = "0.29.0", optional = true}
-electrsd = { version= "0.31.0", optional = true}
-bdk_electrum ={ version = "0.20.1", optional = true}
-bdk_esplora ={ version = "0.15.0", features = ["async"], optional = true}
-bdk_bitcoind_rpc = {version = "0.17.1", optional = true}
-hwi = {version = "0.10.0", features = ["signer"], optional = true}
-
-
-# Platform-specific dependencies
-[target.'cfg(target_arch = "wasm32")'.dependencies]
-wasm-bindgen = { version = "=0.2.79", features = ["serde-serialize"] }
-wasm-bindgen-futures = { version = "0.4" }
-js-sys = "=0.3.56"
-wasm-logger = "0.2.0"
-secp256k1 = { version = "0.22.0", default-features = false }
-rand = { version = "^0.6", features = ["wasm-bindgen"] }
-serde = { version = "^1.0", features = ["derive"] }
-regex = { version = "1" }
-getrandom = "0.2"
-# zip is used by electrsd and versions after 0.6.3 don't work with our MSRV 1.57.0
-zip = { version = "=0.6.3", optional = true }
-
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }
+bdk_bitcoind_rpc = { version = "0.18.0", optional = true }
+bdk_electrum = { version = "0.21.0", optional = true }
+bdk_esplora = { version = "0.20.1", features = ["async-https", "tokio"], optional = true }
+bdk_kyoto = { version = "0.7.1", optional = true }
+shlex = {  version = "1.3.0", optional = true }
 
 [features]
-default = ["repl", "sqlite-db"]
+default = ["repl", "sqlite"]
 
 # To use the app in a REPL mode
-repl = ["regex", "rustyline", "fd-lock"]
+repl = ["shlex"]
 
 # Available database options
-# key-value-db = ["bdk/key-value-db"] //temporary disabled
-sqlite-db = ["bdk_wallet/rusqlite"]
+sqlite = ["bdk_wallet/rusqlite"]
 
 # Available blockchain client options
-rpc = ["bdk_bitcoind_rpc"]
+cbf = ["bdk_kyoto"]
 electrum = ["bdk_electrum"]
-esplora = []
-esplora-ureq = ["esplora"]
-async-interface = []
-esplora-reqwest = ["esplora"]
-
-# Compact Filters are temporarily diabled to avoid rockdb vulnerability.
-# Ref: https://github.com/bitcoindevkit/bdk-cli/issues/112
-#compact_filters = ["bdk/compact_filters"]
+esplora = ["bdk_esplora"]
+rpc = ["bdk_bitcoind_rpc"]
 
 # Use this to consensus verify transactions at sync time
 verify = []
 
-# Use hardware wallets to sign transactions
-hardware-signer = ["hwi"]
-
 # Extra utility tools
 # Compile policies
 compiler = []
-# Create/Verify proof of reserves as per BIP322
-reserves = ["bdk-reserves"]
-
-# Following features auto deploys a regtest node in the background.
-# Connects the bdk wallet with that node.
-# And allows a new `bdk-cli node <command>` to operate the background node.
-#
-# This is most useful for integrations testing as well as quick demo testing
-# by devs using bdk and various types of background nodes.
-regtest-node = ["electrsd", "zip"]
-regtest-bitcoin = ["regtest-node" , "rpc"]
-regtest-electrum = ["regtest-node", "electrum", "electrsd/electrs_0_8_10"]
-#TODO: Check why esplora in electrsd isn't working.
-#regtest-esplora-ureq = ["regtest-node", "esplora-ureq", "electrsd/esplora_a33e97e1"]
-#regtest-esplora-reqwest = ["regtest-node", "esplora-reqwest", "electrsd/esplora_a33e97e1"]
-
-# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
-# for libraries to explicitly include the "getrandom/js" feature, so we only do it when
-# necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
-dev-getrandom-wasm = ["getrandom/js"]
index fe95bd51513976cd9d3303076998841d2a438e11..bfe0fd055fd80de3645ecb113a100d467f68ca5d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -39,26 +39,21 @@ If you are considering using BDK in your own wallet project bdk-cli is a nice pl
 
 bdk-cli can be compiled with different features to suit your experimental needs.
   - Database Options
-     - `key-value-db` : Sets the wallet database to a `sled` db.
-     - `sqlite-db` : Sets the wallet database to a `sqlite3` db.
-  - Blockchain Options
-     - `rpc` : Connects the wallet to bitcoin core via RPC.
+     - `sqlite` : Sets the wallet database to a `sqlite3` db.
+  - Blockchain Client Options
+     - `esplora` : Connects the wallet to an esplora server.
      - `electrum` : Connects the wallet to an electrum server.
-     - `esplora-ureq` or `esplora-reqwest` : Connects the wallet to an esplora server synchronously or asynchronously.
   - Extra Utility Tools
      - `repl` : use bdk-cli as a [REPL](https://codewith.mu/en/tutorials/1.0/repl) shell (useful for quick manual testing of wallet operations).
      - `compiler` : opens up bdk-cli policy compiler commands.
-     - `verify` : uses `bitcoinconsensus` to verify transactions at every `sync` call of the wallet.
-     - `reserves` : opens up bdk-cli **Proof of Reserves** operation commands using the [bdk-reserves plugin](https://github.com/bitcoindevkit/bdk-reserves). (requires the `electrum` feature)
-   - Automated Node Backend
-     - `regtest-bitcoin` : Auto deploys a regtest `bitcoind` node, connects the wallet, and exposes core rpc commands via `bdk-cli node` subcommands.
-     - `regtest-electrum` : Auto deploys `electrsd` and connected `bitcoind` nodes, exposes core rpc commands via `bdk-cli node` and provides a wallet connected to the local `electrsd`.
     
-The `default` feature set is `repl` and `sqlite-db`. With the `default` features, `bdk-cli` can be used as an **air-gapped** wallet, and can do everything that doesn't require a network connection.
+The `default` feature set is `repl` and `sqlite`. With the `default` features, `bdk-cli` can be used as an **air-gapped** wallet, and can do everything that doesn't require a network connection.
 
 
 ## Install bdk-cli
+
 ### From source
+
 To install a dev version of `bdk-cli` from a local git repo with the `electrum` blockchain client enabled:
 
 ```shell
@@ -70,14 +65,14 @@ bdk-cli help # to verify it worked
 If no blockchain client feature is enabled online wallet commands `sync` and `broadcast` will be 
 disabled. To enable these commands a blockchain client feature such as `electrum` or another 
 blockchain client feature must be enabled. Below is an example of how to run the `bdk-cli` binary with
-the `esplora-ureq` blockchain client feature.
+the `esplora` blockchain client feature.
 
 ```shell
-RUST_LOG=debug cargo run --features esplora-ureq -- wallet --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" sync
+RUST_LOG=debug cargo run --features esplora -- wallet --client-type esplora --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" sync
 ```
 
-At most one blockchain feature can be enabled, available blockchain client features are:
-`electrum`, `esplora-ureq` (blocking), `esplora-reqwest` (async) and `rpc`.
+Available blockchain client features are:
+`electrum`, `esplora`.
 
 ### From crates.io
 You can install the binary for the latest tag of `bdk-cli` with online wallet features 
@@ -98,19 +93,13 @@ cargo run
 To sync a wallet to the default electrum server:
 
 ```shell
-cargo run --features electrum -- wallet --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" sync
-```
-
-To sync a wallet to a Bitcoin Core node (assuming a regtest node at 127.0.0.1:18443) using the core rpc:
-
-```shell
-cargo run --features rpc -- --network regtest wallet --node 127.0.0.1:18443 --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" sync
+cargo run --features electrum -- wallet -c electrum --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" sync
 ```
 
 To get a wallet balance with customized logging:
 
 ```shell
-RUST_LOG=debug,sled=info,rustls=info cargo run -- wallet --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" get_balance
+RUST_LOG=debug,rusqlite=info,rustls=info cargo run -- wallet --descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" get_balance
 ```
 
 To generate a new extended master key, suitable for use in a descriptor:
@@ -121,25 +110,7 @@ cargo run -- key generate
 
 ## Minimum Supported Rust Version (MSRV)
 
-This library should always compile with any valid combination of features on Rust **1.57.0**.
-
-To build with the MSRV you will need to pin the below dependency versions:
-
-```shell
-# log 0.4.19 has MSRV 1.60.0
-cargo update -p log --precise 0.4.18
-# required for sqlite, hashlink 0.8.2 has MSRV 1.61.0
-cargo update -p hashlink --precise 0.8.0
-# tempfile 3.7.x has MSRV 1.63.0
-cargo update -p tempfile --precise 3.6.0
-cargo update -p base64ct --precise 1.5.3
-# cc 1.0.82 is throwing error with rust 1.57.0, "error[E0599]: no method named `retain_mut`..."
-cargo update -p cc --precise 1.0.81
-# tokio 0.30.0 has MSRV 1.63.0
-cargo update -p tokio --precise 1.29.1
-# flate2 1.0.27 has MSRV 1.63.0+
-cargo update -p flate2 --precise 1.0.26
-```
+This library should always compile with any valid combination of features on Rust **1.75.0**.
 
 ## Resources
 Docs: [bitcoindevkit.org CLI Section](https://bitcoindevkit.org/bdk-cli/installation/)  
diff --git a/build.rs b/build.rs
deleted file mode 100644 (file)
index 74f1f14..0000000
--- a/build.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use std::env;
-
-fn main() {
-    let electrum = env::var_os("CARGO_FEATURE_ELECTRUM").map(|_| "electrum".to_string());
-    let esplora = env::var_os("CARGO_FEATURE_ESPLORA").map(|_| "esplora".to_string());
-    let compact_filters =
-        env::var_os("CARGO_FEATURE_COMPACT_FILTERS").map(|_| "compact_filters".to_string());
-    let rpc = env::var_os("CARGO_FEATURE_RPC").map(|_| "rpc".to_string());
-
-    let blockchain_features: Vec<String> = [electrum, esplora, compact_filters, rpc]
-        .iter()
-        .filter_map(|f| f.to_owned())
-        .collect();
-
-    if blockchain_features.len() > 1 {
-        panic!("At most one blockchain client feature can be enabled but these features were enabled: {:?}", blockchain_features)
-    }
-
-    let key_value_db =
-        env::var_os("CARGO_FEATURE_KEY_VALUE_DB").map(|_| "key-value-db".to_string());
-    let sqlite_db = env::var_os("CARGO_FEATURE_SQLITE_DB").map(|_| "sqlite-db".to_string());
-
-    let database_features: Vec<String> = [key_value_db, sqlite_db]
-        .iter()
-        .filter_map(|f| f.to_owned())
-        .collect();
-
-    if database_features.len() > 1 {
-        panic!(
-            "At most one database feature can be enabled but these features were enabled: {:?}",
-            database_features
-        )
-    }
-}
index 3f726dbda58b8454f7cd72cc054461b93c6d2a8a..0accddf4e24cc1dafaa1de76d7b82ff6b27398cd 100644 (file)
@@ -1 +1 @@
-msrv="1.57.0"
+msrv="1.75.0"
index 9212c20c4af464c813612c5bca75821345fec7db..f8754a8c104d9cbc18fe625e87079cc819740438 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
 //
 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 //! All subcommands are defined in the below enums.
 
 #![allow(clippy::large_enum_variant)]
-use clap::{AppSettings, Parser, Subcommand};
 
 use bdk_wallet::bitcoin::{
     bip32::{DerivationPath, Xpriv},
     Address, Network, OutPoint, ScriptBuf,
 };
+use clap::{value_parser, Args, Parser, Subcommand, ValueEnum};
 
-use crate::utils::{parse_address, parse_outpoint, parse_recipient};
 #[cfg(any(
-    feature = "compact_filters",
+    feature = "cbf",
     feature = "electrum",
     feature = "esplora",
     feature = "rpc"
 ))]
-use {crate::utils::parse_proxy_auth, clap::Args};
+use crate::utils::parse_proxy_auth;
+use crate::utils::{parse_address, parse_outpoint, parse_recipient};
 
-#[derive(PartialEq, Clone, Debug, Parser)]
 /// The BDK Command Line Wallet App
 ///
-/// bdk-cli is a light weight command line bitcoin wallet, powered by BDK.
+/// bdk-cli is a lightweight command line bitcoin wallet, powered by BDK.
 /// This app can be used as a playground as well as testing environment to simulate
 /// various wallet testing situations. If you are planning to use BDK in your wallet, bdk-cli
 /// is also a great intro tool to get familiar with the BDK API.
@@ -41,45 +40,31 @@ use {crate::utils::parse_proxy_auth, clap::Args};
 /// bdk-cli is also a fully functioning Bitcoin wallet with taproot support!
 ///
 /// For more information checkout <https://bitcoindevkit.org/>
-#[clap(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
-author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
+#[derive(PartialEq, Clone, Debug, Parser)]
+#[command(version, about, long_about = None)]
 pub struct CliOpts {
     /// Sets the network.
-    #[clap(
-        name = "NETWORK",
+    #[arg(
+        env = "NETWORK",
         short = 'n',
         long = "network",
         default_value = "testnet",
-        possible_values = &["bitcoin", "testnet", "signet", "regtest"]
+        value_parser = value_parser!(Network)
     )]
     pub network: Network,
     /// Sets the wallet data directory.
-    /// Default value : "~/.bdk-bitcoin
-    #[clap(name = "DATADIR", short = 'd', long = "datadir")]
+    /// Default value : ~/.bdk-bitcoin
+    #[arg(env = "DATADIR", short = 'd', long = "datadir")]
     pub datadir: Option<std::path::PathBuf>,
     /// Top level cli sub-commands.
-    #[clap(subcommand)]
+    #[command(subcommand)]
     pub subcommand: CliSubCommand,
 }
 
 /// Top level cli sub-commands.
 #[derive(Debug, Subcommand, Clone, PartialEq)]
-#[clap(rename_all = "snake")]
+#[command(rename_all = "snake")]
 pub enum CliSubCommand {
-    /// Node operation subcommands.
-    ///
-    /// These commands can be used to control the backend bitcoin-core node
-    /// launched automatically with the `regtest-*` feature sets. The commands issue
-    /// bitcoin-cli rpc calls on the daemon, in the background.
-    ///
-    /// Feel free to open a feature request issue in <https://github.com/bitcoindevkit/bdk-cli>
-    /// if you need extra rpc calls not covered in the command list.
-    #[cfg(feature = "regtest-node")]
-    #[clap(long_about = "Regtest Node mode")]
-    Node {
-        #[clap(subcommand)]
-        subcommand: NodeSubCommand,
-    },
     /// Wallet operations.
     ///
     /// bdk-cli wallet operations includes all the basic wallet level tasks.
@@ -87,9 +72,9 @@ pub enum CliSubCommand {
     /// needs backend like `sync` and `broadcast`, compile the binary with specific backend feature
     /// and use the configuration options below to configure for that backend.
     Wallet {
-        #[clap(flatten)]
+        #[command(flatten)]
         wallet_opts: WalletOpts,
-        #[clap(subcommand)]
+        #[command(subcommand)]
         subcommand: WalletSubCommand,
     },
     /// Key management operations.
@@ -108,10 +93,11 @@ pub enum CliSubCommand {
     #[clap(long_about = "Miniscript policy compiler")]
     Compile {
         /// Sets the spending policy to compile.
-        #[clap(name = "POLICY", required = true, index = 1)]
+        #[arg(env = "POLICY", required = true, index = 1)]
         policy: String,
         /// Sets the script type used to embed the compiled policy.
-        #[clap(name = "TYPE", short = 't', long = "type", default_value = "wsh", possible_values = &["sh","wsh", "sh-wsh"])]
+        #[arg(env = "TYPE", short = 't', long = "type", default_value = "wsh", possible_values = &["sh","wsh", "sh-wsh"]
+        )]
         script_type: String,
     },
     #[cfg(feature = "repl")]
@@ -120,49 +106,9 @@ pub enum CliSubCommand {
     /// REPL command loop can be used to make recurring callbacks to an already loaded wallet.
     /// This mode is useful for hands on live testing of wallet operations.
     Repl {
-        #[clap(flatten)]
+        #[command(flatten)]
         wallet_opts: WalletOpts,
     },
-    /// Proof of reserves operations.
-    ///
-    /// This can be used to produce and verify Proof of Reserves (similar to BIP 322) using bdk-cli.
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    ExternalReserves {
-        /// Sets the challenge message with which the proof was produced.
-        #[clap(name = "MESSAGE", required = true, index = 1)]
-        message: String,
-        /// Sets the proof in form of a PSBT to verify.
-        #[clap(name = "PSBT", required = true, index = 2)]
-        psbt: String,
-        /// Sets the number of block confirmations for UTXOs to be considered.
-        #[clap(name = "CONFIRMATIONS", required = true, index = 3)]
-        confirmations: usize,
-        /// Sets the addresses for which the proof was produced.
-        #[clap(name = "ADDRESSES", required = true, index = 4)]
-        addresses: Vec<String>,
-        #[clap(flatten)]
-        electrum_opts: ElectrumOpts,
-    },
-}
-
-/// Backend Node operation subcommands.
-#[derive(Debug, Subcommand, Clone, PartialEq, Eq)]
-#[clap(rename_all = "lower")]
-#[cfg(feature = "regtest-node")]
-pub enum NodeSubCommand {
-    /// Get info.
-    GetInfo,
-    /// Get new address from node's test wallet.
-    GetNewAddress,
-    /// Generate given number of blocks and fund the internal wallet with coinbases.
-    Generate { block_num: u64 },
-    /// Get Wallet balance.
-    GetBalance,
-    /// Send to an external wallet address.
-    SendToAddress { address: String, amount: u64 },
-    /// Execute any bitcoin-cli commands.
-    #[clap(external_subcommand)]
-    BitcoinCli(Vec<String>),
 }
 
 /// Wallet operation subcommands.
@@ -171,91 +117,118 @@ pub enum WalletSubCommand {
     #[cfg(any(
         feature = "electrum",
         feature = "esplora",
-        feature = "compact_filters",
+        feature = "cbf",
         feature = "rpc"
     ))]
-    #[clap(flatten)]
+    #[command(flatten)]
     OnlineWalletSubCommand(OnlineWalletSubCommand),
-    #[clap(flatten)]
+    #[command(flatten)]
     OfflineWalletSubCommand(OfflineWalletSubCommand),
 }
 
+#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
+pub enum DatabaseType {
+    /// Sqlite database
+    #[cfg(feature = "sqlite")]
+    Sqlite,
+}
+
+#[cfg(any(feature = "electrum", feature = "esplora",))]
+#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
+pub enum ClientType {
+    #[cfg(feature = "electrum")]
+    Electrum,
+    #[cfg(feature = "esplora")]
+    Esplora,
+}
+
 /// Config options wallet operations can take.
-#[derive(Debug, Parser, Clone, PartialEq, Eq)]
+#[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct WalletOpts {
     /// Selects the wallet to use.
-    #[clap(name = "WALLET_NAME", short = 'w', long = "wallet")]
+    #[arg(env = "WALLET_NAME", short = 'w', long = "wallet")]
     pub wallet: Option<String>,
     /// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects.
-    #[clap(name = "VERBOSE", short = 'v', long = "verbose")]
+    #[arg(env = "VERBOSE", short = 'v', long = "verbose")]
     pub verbose: bool,
     /// Sets the descriptor to use for the external addresses.
-    #[clap(name = "DESCRIPTOR", short = 'd', long = "descriptor", required = true)]
-    pub descriptor: String,
-    /// Sets the descriptor to use for internal addresses.
-    #[clap(name = "CHANGE_DESCRIPTOR", short = 'c', long = "change_descriptor")]
-    pub change_descriptor: Option<String>,
-    #[cfg(feature = "electrum")]
-    #[clap(flatten)]
-    pub electrum_opts: ElectrumOpts,
-    #[cfg(feature = "esplora")]
-    #[clap(flatten)]
-    pub esplora_opts: EsploraOpts,
-    #[cfg(feature = "compact_filters")]
-    #[clap(flatten)]
-    pub compactfilter_opts: CompactFilterOpts,
-    #[cfg(feature = "rpc")]
-    #[clap(flatten)]
-    pub rpc_opts: RpcOpts,
-    #[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))]
-    #[clap(flatten)]
-    pub proxy_opts: ProxyOpts,
+    #[arg(env = "EXT_DESCRIPTOR", short = 'e', long)]
+    pub ext_descriptor: Option<String>,
+    /// Sets the descriptor to use for internal/change addresses.
+    #[arg(env = "INT_DESCRIPTOR", short = 'i', long)]
+    pub int_descriptor: Option<String>,
+    #[cfg(any(feature = "electrum", feature = "esplora",))]
+    #[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)]
+    pub client_type: ClientType,
+    #[cfg(any(feature = "sqlite",))]
+    #[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)]
+    pub database_type: DatabaseType,
+    /// Sets the server url.
+    #[cfg(any(feature = "electrum", feature = "esplora",))]
+    #[arg(env = "SERVER_URL", short = 'u', long, required = true)]
+    pub url: String,
+    /// Electrum batch size.
+    #[cfg(any(feature = "electrum"))]
+    #[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")]
+    pub batch_size: usize,
+    /// Esplora parallel requests.
+    #[cfg(any(feature = "esplora"))]
+    #[arg(
+        env = "ESPLORA_PARALLEL_REQUESTS",
+        short = 'p',
+        long,
+        default_value = "5"
+    )]
+    pub parallel_requests: usize,
 }
 
 /// Options to configure a SOCKS5 proxy for a blockchain client connection.
-#[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))]
+#[cfg(any(feature = "cbf", feature = "electrum", feature = "esplora"))]
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct ProxyOpts {
     /// Sets the SOCKS5 proxy for a blockchain client.
-    #[clap(name = "PROXY_ADDRS:PORT", long = "proxy", short = 'p')]
+    #[arg(env = "PROXY_ADDRS:PORT", long = "proxy", short = 'p')]
     pub proxy: Option<String>,
 
     /// Sets the SOCKS5 proxy credential.
-    #[clap(name="PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)]
+    #[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)]
     pub proxy_auth: Option<(String, String)>,
 
     /// Sets the SOCKS5 proxy retries for the blockchain client.
-    #[clap(
-        name = "PROXY_RETRIES",
+    #[arg(
+        env = "PROXY_RETRIES",
         short = 'r',
         long = "retries",
         default_value = "5"
     )]
     pub retries: u8,
+
+    /// Sets the SOCKS5 proxy timeout for the blockchain client.
+    #[arg(env = "PROXY_TIMEOUT", short = 't', long = "timeout")]
+    pub timeout: Option<u8>,
 }
 
 /// Options to configure a BIP157 Compact Filter backend.
-#[cfg(feature = "compact_filters")]
+#[cfg(feature = "cbf")]
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct CompactFilterOpts {
     /// Sets the full node network address.
     #[clap(
-        name = "ADDRESS:PORT",
-        short = 'n',
-        long = "node",
+        env = "ADDRESS:PORT",
+        long = "cbf-node",
         default_value = "127.0.0.1:18444"
     )]
     pub address: Vec<String>,
 
     /// Sets the number of parallel node connections.
-    #[clap(name = "CONNECTIONS", long = "conn_count", default_value = "4")]
+    #[clap(name = "CONNECTIONS", long = "cbf-conn-count", default_value = "4")]
     pub conn_count: usize,
 
     /// Optionally skip initial `skip_blocks` blocks.
     #[clap(
-        name = "SKIP_BLOCKS",
+        env = "SKIP_BLOCKS",
         short = 'k',
-        long = "skip_blocks",
+        long = "cbf-skip-blocks",
         default_value = "0"
     )]
     pub skip_blocks: usize,
@@ -266,35 +239,29 @@ pub struct CompactFilterOpts {
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct RpcOpts {
     /// Sets the full node address for rpc connection.
-    #[clap(
-        name = "ADDRESS:PORT",
-        short = 'n',
-        long = "node",
+    #[arg(
+        env = "ADDRESS:PORT",
+        long = "rpc-node",
         default_value = "127.0.0.1:18443"
     )]
     pub address: String,
 
     /// Sets the rpc basic authentication.
-    #[clap(
-        name = "USER:PASSWD",
+    #[arg(
+        env = "USER:PASSWD",
         short = 'a',
-        long = "basic-auth",
+        long = "rpc-basic-auth",
         value_parser = parse_proxy_auth,
         default_value = "user:password",
     )]
     pub basic_auth: (String, String),
 
     /// Sets an optional cookie authentication.
-    #[clap(name = "COOKIE", long = "cookie")]
+    #[arg(name = "COOKIE", short = 'c', long = "rpc-cookie")]
     pub cookie: Option<String>,
 
     /// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
-    #[clap(
-        name = "RPC_START_TIME",
-        short = 's',
-        long = "start-time",
-        default_value = "0"
-    )]
+    #[arg(env = "RPC_START_TIME", long = "rpc-start-time", default_value = "0")]
     pub start_time: u64,
 }
 
@@ -302,26 +269,18 @@ pub struct RpcOpts {
 #[cfg(feature = "electrum")]
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct ElectrumOpts {
-    /// Sets the SOCKS5 proxy timeout for the Electrum client.
-    #[clap(name = "PROXY_TIMEOUT", short = 't', long = "timeout")]
-    pub timeout: Option<u8>,
     /// Sets the Electrum server to use.
-    #[clap(
-        name = "ELECTRUM_URL",
-        short = 's',
-        long = "server",
+    #[arg(
+        env = "ELECTRUM_URL",
+        short = 'e',
+        long = "electrum",
         default_value = "ssl://electrum.blockstream.info:60002"
     )]
     pub server: String,
 
     /// Stop searching addresses for transactions after finding an unused gap of this length.
-    #[clap(
-        name = "STOP_GAP",
-        long = "stop_gap",
-        short = 'g',
-        default_value = "10"
-    )]
-    pub stop_gap: usize,
+    #[arg(env = "ELECTRUM_BATCH_SIZE", long = "batch_size", default_value = "10")]
+    pub batch_size: usize,
 }
 
 /// Options to configure Esplora backend.
@@ -329,111 +288,109 @@ pub struct ElectrumOpts {
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct EsploraOpts {
     /// Use the esplora server if given as parameter.
-    #[clap(
-        name = "ESPLORA_URL",
+    #[arg(
+        env = "ESPLORA_URL",
         short = 's',
-        long = "server",
+        long = "esplora",
         default_value = "https://blockstream.info/testnet/api/"
     )]
     pub server: String,
 
     /// Socket timeout.
-    #[clap(name = "TIMEOUT", long = "timeout", default_value = "5")]
+    #[arg(env = "TIMEOUT", long = "timeout", default_value = "5")]
     pub timeout: u64,
 
-    /// Stop searching addresses for transactions after finding an unused gap of this length.
-    #[clap(
-        name = "STOP_GAP",
-        long = "stop_gap",
-        short = 'g',
-        default_value = "10"
-    )]
-    pub stop_gap: usize,
-
     /// Number of parallel requests sent to the esplora service.
-    #[clap(name = "CONCURRENCY", long = "conc", default_value = "4")]
+    #[arg(env = "CONCURRENCY", long = "esplora-conc", default_value = "4")]
     pub conc: u8,
 }
 
 /// Wallet subcommands that can be issued without a blockchain backend.
 #[derive(Debug, Subcommand, Clone, PartialEq)]
-#[clap(rename_all = "snake")]
+#[command(rename_all = "snake")]
 pub enum OfflineWalletSubCommand {
-    /// Generates a new external address.
-    GetNewAddress,
+    /// Get a new external address.
+    NewAddress,
+    /// Get the first unused external address.
+    UnusedAddress,
     /// Lists the available spendable UTXOs.
-    ListUnspent,
+    Unspent,
     /// Lists all the incoming and outgoing transactions of the wallet.
-    ListTransactions,
+    Transactions,
     /// Returns the current wallet balance.
-    GetBalance,
+    Balance,
     /// Creates a new unsigned transaction.
     CreateTx {
         /// Adds a recipient to the transaction.
         // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
         // Address and amount parsing is done at run time in handler function.
-        #[clap(name = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
+        #[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
         recipients: Vec<(ScriptBuf, u64)>,
         /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
-        #[clap(long = "send_all", short = 'a')]
+        #[arg(long = "send_all", short = 'a')]
         send_all: bool,
         /// Enables Replace-By-Fee (BIP125).
-        #[clap(long = "enable_rbf", short = 'r', default_value_t = true)]
+        #[arg(long = "enable_rbf", short = 'r', default_value_t = true)]
         enable_rbf: bool,
         /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
-        #[clap(long = "offline_signer")]
+        #[arg(long = "offline_signer")]
         offline_signer: bool,
         /// Selects which utxos *must* be spent.
-        #[clap(name = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+        #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
         utxos: Option<Vec<OutPoint>>,
         /// Marks a utxo as unspendable.
-        #[clap(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+        #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
         unspendable: Option<Vec<OutPoint>>,
         /// Fee rate to use in sat/vbyte.
-        #[clap(name = "SATS_VBYTE", short = 'f', long = "fee_rate")]
+        #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
         fee_rate: Option<f32>,
         /// Selects which policy should be used to satisfy the external descriptor.
-        #[clap(name = "EXT_POLICY", long = "external_policy")]
+        #[arg(env = "EXT_POLICY", long = "external_policy")]
         external_policy: Option<String>,
         /// Selects which policy should be used to satisfy the internal descriptor.
-        #[clap(name = "INT_POLICY", long = "internal_policy")]
+        #[arg(env = "INT_POLICY", long = "internal_policy")]
         internal_policy: Option<String>,
         /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
-        #[clap(
-            name = "ADD_STRING",
+        #[arg(
+            env = "ADD_STRING",
             long = "add_string",
             short = 's',
-            conflicts_with = "ADD_DATA"
+            conflicts_with = "add_data"
         )]
         add_string: Option<String>,
         /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
-        #[clap(
-            name = "ADD_DATA",
+        #[arg(
+            env = "ADD_DATA",
             long = "add_data",
             short = 'o',
-            conflicts_with = "ADD_STRING"
+            conflicts_with = "add_string"
         )]
         add_data: Option<String>, //base 64 econding
     },
     /// Bumps the fees of an RBF transaction.
     BumpFee {
         /// TXID of the transaction to update.
-        #[clap(name = "TXID", long = "txid")]
+        #[arg(env = "TXID", long = "txid")]
         txid: String,
         /// Allows the wallet to reduce the amount to the specified address in order to increase fees.
-        #[clap(name = "SHRINK_ADDRESS", long = "shrink", value_parser = parse_address)]
+        #[arg(env = "SHRINK_ADDRESS", long = "shrink", value_parser = parse_address)]
         shrink_address: Option<Address>,
         /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
-        #[clap(long = "offline_signer")]
+        #[arg(long = "offline_signer")]
         offline_signer: bool,
         /// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used.
-        #[clap(name = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+        #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
         utxos: Option<Vec<OutPoint>>,
         /// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees.
-        #[clap(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+        #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
         unspendable: Option<Vec<OutPoint>>,
         /// The new targeted fee rate in sat/vbyte.
-        #[clap(name = "SATS_VBYTE", short = 'f', long = "fee_rate")]
+        #[arg(
+            env = "SATS_VBYTE",
+            short = 'f',
+            long = "fee_rate",
+            default_value = "1.0"
+        )]
         fee_rate: f32,
     },
     /// Returns the available spending policies for the descriptor.
@@ -443,92 +400,78 @@ pub enum OfflineWalletSubCommand {
     /// Signs and tries to finalize a PSBT.
     Sign {
         /// Sets the PSBT to sign.
-        #[clap(name = "BASE64_PSBT", long = "psbt")]
+        #[arg(env = "BASE64_PSBT")]
         psbt: String,
         /// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor.
-        #[clap(name = "HEIGHT", long = "assume_height")]
+        #[arg(env = "HEIGHT", long = "assume_height")]
         assume_height: Option<u32>,
         /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided.
-        #[clap(name = "WITNESS", long = "trust_witness_utxo")]
+        #[arg(env = "WITNESS", long = "trust_witness_utxo")]
         trust_witness_utxo: Option<bool>,
     },
     /// Extracts a raw transaction from a PSBT.
     ExtractPsbt {
         /// Sets the PSBT to extract
-        #[clap(name = "BASE64_PSBT", long = "psbt")]
+        #[arg(env = "BASE64_PSBT")]
         psbt: String,
     },
     /// Finalizes a PSBT.
     FinalizePsbt {
         /// Sets the PSBT to finalize.
-        #[clap(name = "BASE64_PSBT", long = "psbt")]
+        #[arg(env = "BASE64_PSBT")]
         psbt: String,
         /// Assume the blockchain has reached a specific height.
-        #[clap(name = "HEIGHT", long = "assume_height")]
+        #[arg(env = "HEIGHT", long = "assume_height")]
         assume_height: Option<u32>,
         /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided.
-        #[clap(name = "WITNESS", long = "trust_witness_utxo")]
+        #[arg(env = "WITNESS", long = "trust_witness_utxo")]
         trust_witness_utxo: Option<bool>,
     },
     /// Combines multiple PSBTs into one.
     CombinePsbt {
         /// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT.
-        #[clap(name = "BASE64_PSBT", long = "psbt", required = true)]
+        #[arg(env = "BASE64_PSBT", required = true)]
         psbt: Vec<String>,
     },
 }
 
 /// Wallet subcommands that needs a blockchain backend.
 #[derive(Debug, Subcommand, Clone, PartialEq, Eq)]
-#[clap(rename_all = "snake")]
+#[command(rename_all = "snake")]
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
-    feature = "compact_filters",
+    feature = "cbf",
     feature = "rpc"
 ))]
 pub enum OnlineWalletSubCommand {
+    /// Full Scan with the chosen blockchain server.
+    FullScan {
+        /// Stop searching addresses for transactions after finding an unused gap of this length.
+        #[arg(env = "STOP_GAP", long = "scan-stop-gap", default_value = "20")]
+        stop_gap: usize,
+    },
     /// Syncs with the chosen blockchain server.
     Sync,
     /// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract.
     Broadcast {
         /// Sets the PSBT to sign.
-        #[clap(
-            name = "BASE64_PSBT",
+        #[arg(
+            env = "BASE64_PSBT",
             long = "psbt",
-            required_unless = "RAWTX",
-            conflicts_with = "RAWTX"
+            required_unless_present = "tx",
+            conflicts_with = "tx"
         )]
         psbt: Option<String>,
         /// Sets the raw transaction to broadcast.
-        #[clap(
-            name = "RAWTX",
+        #[arg(
+            env = "RAWTX",
             long = "tx",
-            required_unless = "BASE64_PSBT",
-            conflicts_with = "BASE64_PSBT"
+            required_unless_present = "psbt",
+            conflicts_with = "psbt"
         )]
         tx: Option<String>,
     },
-    /// Produce a proof of reserves.
-    #[cfg(feature = "reserves")]
-    ProduceProof {
-        /// Sets the message.
-        #[clap(name = "MESSAGE", long = "message")]
-        msg: String,
-    },
-    /// Verify a proof of reserves for our wallet.
-    #[cfg(feature = "reserves")]
-    VerifyProof {
-        /// Sets the PSBT to verify.
-        #[clap(name = "BASE64_PSBT", long = "psbt")]
-        psbt: String,
-        /// Sets the message to verify.
-        #[clap(name = "MESSAGE", long = "message")]
-        msg: String,
-        /// Sets the number of block confirmations for UTXOs to be considered.
-        #[clap(name = "CONFIRMATIONS", long = "confirmations", default_value = "6")]
-        confirmations: u32,
-    },
 }
 
 /// Subcommands for Key operations.
@@ -537,1166 +480,1114 @@ pub enum KeySubCommand {
     /// Generates new random seed mnemonic phrase and corresponding master extended key.
     Generate {
         /// Entropy level based on number of random seed mnemonic words.
-        #[clap(
-        name = "WORD_COUNT",
-        short = 'e',
-        long = "entropy",
-        default_value = "24",
-        possible_values = &["12","24"],
+        #[arg(
+            env = "WORD_COUNT",
+            short = 'e',
+            long = "entropy",
+            default_value = "12"
         )]
         word_count: usize,
         /// Seed password.
-        #[clap(name = "PASSWORD", short = 'p', long = "password")]
+        #[arg(env = "PASSWORD", short = 'p', long = "password")]
         password: Option<String>,
     },
     /// Restore a master extended key from seed backup mnemonic words.
     Restore {
         /// Seed mnemonic words, must be quoted (eg. "word1 word2 ...").
-        #[clap(name = "MNEMONIC", short = 'm', long = "mnemonic")]
+        #[arg(env = "MNEMONIC", short = 'm', long = "mnemonic")]
         mnemonic: String,
         /// Seed password.
-        #[clap(name = "PASSWORD", short = 'p', long = "password")]
+        #[arg(env = "PASSWORD", short = 'p', long = "password")]
         password: Option<String>,
     },
     /// Derive a child key pair from a master extended key and a derivation path string (eg. "m/84'/1'/0'/0" or "m/84h/1h/0h/0").
     Derive {
         /// Extended private key to derive from.
-        #[clap(name = "XPRV", short = 'x', long = "xprv")]
+        #[arg(env = "XPRV", short = 'x', long = "xprv")]
         xprv: Xpriv,
         /// Path to use to derive extended public key from extended private key.
-        #[clap(name = "PATH", short = 'p', long = "path")]
+        #[arg(env = "PATH", short = 'p', long = "path")]
         path: DerivationPath,
     },
-    #[cfg(feature = "hardware-signer")]
-    /// List the public descriptors of the available hardware wallets
-    Hardware {},
 }
 
 /// Subcommands available in REPL mode.
 #[cfg(any(feature = "repl", target_arch = "wasm32"))]
-#[derive(Debug, Parser, Clone, PartialEq)]
-#[clap(global_settings =&[AppSettings::NoBinaryName], rename_all = "lower")]
+#[derive(Debug, Parser)]
+#[command(rename_all = "lower", multicall = true)]
 pub enum ReplSubCommand {
     /// Execute wallet commands.
     Wallet {
-        #[clap(subcommand)]
+        #[command(subcommand)]
         subcommand: WalletSubCommand,
     },
     /// Execute key commands.
     Key {
-        #[clap(subcommand)]
+        #[command(subcommand)]
         subcommand: KeySubCommand,
     },
-    /// Execute node commands.
-    #[cfg(feature = "regtest-node")]
-    Node {
-        #[clap(subcommand)]
-        subcommand: NodeSubCommand,
-    },
     /// Exit REPL loop.
     Exit,
 }
 
-#[cfg(test)]
-mod test {
-    use super::*;
-    #[cfg(feature = "compiler")]
-    use crate::handlers::handle_compile_subcommand;
-    use crate::handlers::handle_key_subcommand;
-    use bdk_wallet::Wallet;
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    use {
-        crate::handlers::{handle_ext_reserves_subcommand, handle_online_wallet_subcommand},
-        bdk_electrum::electrum_client::Client,
-        bdk_electrum::BdkElectrumClient,
-        bdk_wallet::bitcoin::{consensus::Encodable, Psbt},
-    };
-
-    use bdk_wallet::bitcoin::bip32::{DerivationPath, Xpriv};
-    use bdk_wallet::bitcoin::{Address, Network, OutPoint};
-    use bdk_wallet::miniscript::bitcoin::network::Network::Testnet;
-    use std::str::{self, FromStr};
-
-    use super::OfflineWalletSubCommand::{BumpFee, CreateTx, GetNewAddress};
-    use super::WalletSubCommand::OfflineWalletSubCommand;
-    #[cfg(any(
-        feature = "electrum",
-        feature = "esplora",
-        feature = "compact_filters",
-        feature = "rpc"
-    ))]
-    use super::WalletSubCommand::OnlineWalletSubCommand;
-    #[cfg(feature = "repl")]
-    use regex::Regex;
-
-    #[test]
-    fn test_clap_args() {
-        use clap::CommandFactory;
-        CliOpts::command().debug_assert();
-    }
-
-    #[test]
-    fn test_parse_wallet_get_new_address() {
-        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
-                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    #[cfg(feature = "electrum")]
-                    electrum_opts: ElectrumOpts {
-                        timeout: None,
-                        server: "ssl://electrum.blockstream.info:60002".to_string(),
-                        stop_gap: 10,
-                    },
-                    #[cfg(feature = "esplora")]
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    #[cfg(feature = "compact_filters")]
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                    #[cfg(feature = "rpc")]
-                    rpc_opts: RpcOpts {
-                        address: "127.0.0.1:18443".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: None,
-                        start_time: 0,
-                    },
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "electrum")]
-    #[test]
-    fn test_parse_wallet_electrum() {
-        let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
-                            "--proxy", "127.0.0.1:9150", "--retries", "3", "--timeout", "10",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "--server","ssl://electrum.blockstream.info:50002",
-                            "--stop_gap", "20",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    electrum_opts: ElectrumOpts {
-                        timeout: Some(10),
-                        server: "ssl://electrum.blockstream.info:50002".to_string(),
-                        stop_gap: 20
-                    },
-                    proxy_opts: ProxyOpts{
-                        proxy: Some("127.0.0.1:9150".to_string()),
-                        proxy_auth: None,
-                        retries: 3,
-                    },
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "esplora-ureq")]
-    #[test]
-    fn test_parse_wallet_esplora() {
-        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
-                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "--server", "https://blockstream.info/api/",
-                            "--timeout", "10",
-                            "--stop_gap", "20",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/api/".to_string(),
-                        timeout: 10,
-                        stop_gap: 20,
-                        conc: 4,
-                    },
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    }
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "esplora-reqwest")]
-    #[test]
-    fn test_parse_wallet_esplora() {
-        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
-                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "--server", "https://blockstream.info/api/",
-                            "--conc", "10",
-                            "--stop_gap", "20",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/api/".to_string(),
-                        conc: 10,
-                        stop_gap: 20,
-                        timeout: 5,
-                    },
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    }
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "rpc")]
-    #[test]
-    fn test_parse_wallet_rpc() {
-        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
-                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "--node", "125.67.89.101:56678",
-                            "--basic-auth", "user:password",
-                            "--cookie", "/home/user/.bitcoin/regtest/.cookie",
-                            "--start-time", "123456",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    rpc_opts: RpcOpts {
-                        address: "125.67.89.101:56678".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: Some("/home/user/.bitcoin/regtest/.cookie".to_string()),
-                        start_time: 123456,
-                    },
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "compact_filters")]
-    #[test]
-    fn test_parse_wallet_compact_filters() {
-        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
-                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "--proxy", "127.0.0.1:9005",
-                            "--proxy_auth", "random_user:random_passwd",
-                            "--node", "127.0.0.1:18444",
-                            "--conn_count", "4",
-                            "--skip_blocks", "5",
-                            "get_new_address"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 5,
-                    },
-                    proxy_opts: ProxyOpts{
-                        proxy: Some("127.0.0.1:9005".to_string()),
-                        proxy_auth: Some(("random_user".to_string(), "random_passwd".to_string())),
-                        retries: 5,
-                    }
-                },
-                subcommand: OfflineWalletSubCommand(GetNewAddress),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(any(
-        feature = "electrum",
-        feature = "esplora",
-        feature = "compact_filters",
-        feature = "rpc"
-    ))]
-    #[test]
-    fn test_parse_wallet_sync() {
-        let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "sync"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: None,
-                    #[cfg(feature = "electrum")]
-                    electrum_opts: ElectrumOpts {
-                        timeout: None,
-                        server: "ssl://electrum.blockstream.info:60002".to_string(),
-                        stop_gap: 10,
-                    },
-                    #[cfg(feature = "esplora")]
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    #[cfg(feature = "compact_filters")]
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                    #[cfg(feature = "rpc")]
-                    rpc_opts: RpcOpts {
-                        address: "127.0.0.1:18443".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: None,
-                        start_time: 0,
-                    },
-                },
-                subcommand: OnlineWalletSubCommand(Sync),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[test]
-    fn test_parse_wallet_create_tx() {
-        let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456", "--to", "mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910",
-                            "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
-                            "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
-                            "--add_string","Hello BDK",
-                           ];
-
-        let cli_opts = CliOpts::parse_from(&cli_args);
-
-        let script1 = Address::from_str("n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ")
-            .unwrap()
-            .assume_checked()
-            .script_pubkey();
-
-        let script2 = Address::from_str("mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf")
-            .unwrap()
-            .assume_checked()
-            .script_pubkey();
-
-        let outpoint1 = OutPoint::from_str(
-            "87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
-        )
-        .unwrap();
-        let outpoint2 = OutPoint::from_str(
-            "87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
-        )
-        .unwrap();
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    #[cfg(feature = "electrum")]
-                    electrum_opts: ElectrumOpts {
-                        timeout: None,
-                        server: "ssl://electrum.blockstream.info:60002".to_string(),
-                        stop_gap: 10,
-                    },
-                    #[cfg(feature = "esplora")]
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    #[cfg(feature = "compact_filters")]
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                    #[cfg(feature = "rpc")]
-                    rpc_opts: RpcOpts {
-                        address: "127.0.0.1:18443".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: None,
-                        start_time: 0,
-                    },
-                },
-                subcommand: WalletSubCommand::OfflineWalletSubCommand(CreateTx {
-                    recipients: vec![(script1, 123456), (script2, 78910)],
-                    send_all: false,
-                    enable_rbf: true,
-                    offline_signer: false,
-                    utxos: Some(vec!(outpoint1, outpoint2)),
-                    unspendable: None,
-                    fee_rate: None,
-                    external_policy: None,
-                    internal_policy: None,
-                    add_data: None,
-                    add_string: Some("Hello BDK".to_string()),
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[test]
-    fn test_parse_wallet_bump_fee() {
-        let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
-                            "bump_fee", "--fee_rate", "6.1",
-                            "--txid","35aab0d0213f8996f9e236a28630319b93109754819e8abf48a0835708d33506",
-                            "--shrink","tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt"];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
-                    #[cfg(feature = "electrum")]
-                    electrum_opts: ElectrumOpts {
-                        timeout: None,
-                        server: "ssl://electrum.blockstream.info:60002".to_string(),
-                        stop_gap: 10,
-                    },
-                    #[cfg(feature = "esplora")]
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    #[cfg(feature = "compact_filters")]
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    #[cfg(feature = "rpc")]
-                    rpc_opts: RpcOpts {
-                        address: "127.0.0.1:18443".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: None,
-                        start_time: 0,
-                    },
-                    #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    }
-                },
-                subcommand: OfflineWalletSubCommand(BumpFee {
-                    txid: "35aab0d0213f8996f9e236a28630319b93109754819e8abf48a0835708d33506".to_string(),
-                    shrink_address: Some(Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap().assume_checked()),
-                    offline_signer: false,
-                    utxos: None,
-                    unspendable: None,
-                    fee_rate: 6.1
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(any(
-        feature = "electrum",
-        feature = "esplora",
-        feature = "compact_filters",
-        feature = "rpc"
-    ))]
-    #[test]
-    fn test_parse_wallet_broadcast() {
-        let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "broadcast",
-                            "--psbt", "cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA="];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
-                    change_descriptor: None,
-                    #[cfg(feature = "electrum")]
-                    electrum_opts: ElectrumOpts {
-                        timeout: None,
-                        server: "ssl://electrum.blockstream.info:60002".to_string(),
-                        stop_gap: 10,
-                    },
-                    #[cfg(feature = "esplora")]
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    #[cfg(feature = "compact_filters")]
-                    compactfilter_opts: CompactFilterOpts{
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
-                    proxy_opts: ProxyOpts{
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                    #[cfg(feature = "rpc")]
-                    rpc_opts: RpcOpts {
-                        address: "127.0.0.1:18443".to_string(),
-                        basic_auth: ("user".to_string(), "password".to_string()),
-                        cookie: None,
-                        start_time: 0,
-                    },
-                },
-                subcommand: OnlineWalletSubCommand(Broadcast {
-                    psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),
-                    tx: None
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[test]
-    fn test_parse_wrong_network() {
-        let cli_args = vec!["repl", "--network", "badnet", "wallet",
-                            "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
-                            "sync"];
-
-        let cli_opts = CliOpts::from_iter_safe(&cli_args);
-        assert!(cli_opts.is_err());
-    }
-
-    #[test]
-    fn test_key_generate() {
-        let network = Testnet;
-        let key_generate_cmd = KeySubCommand::Generate {
-            word_count: 12,
-            password: Some("test123".to_string()),
-        };
-
-        let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
-        let result_obj = result.as_object().unwrap();
-
-        let mnemonic = result_obj.get("mnemonic").unwrap().as_str().unwrap();
-        let mnemonic: Vec<&str> = mnemonic.split(' ').collect();
-        let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
-
-        assert_eq!(mnemonic.len(), 12);
-        assert_eq!(&xprv[0..4], "tprv");
-    }
-
-    #[test]
-    fn test_key_restore() {
-        let network = Testnet;
-        let key_generate_cmd = KeySubCommand::Restore {
-            mnemonic: "payment battle unit sword token broccoli era violin purse trip blood hire"
-                .to_string(),
-            password: Some("test123".to_string()),
-        };
-
-        let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
-        let result_obj = result.as_object().unwrap();
-
-        let fingerprint = result_obj.get("fingerprint").unwrap().as_str().unwrap();
-        let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
-
-        assert_eq!(&fingerprint, &"828af366");
-        assert_eq!(&xprv, &"tprv8ZgxMBicQKsPd18TeiFknZKqaZFwpdX9tvvKh8eeHSSPBQi5g9xPHztBg411o78G8XkrhQb6Q1cVvBJ1a9xuFHpmWgvQsvkJkNxBjfGoqhK");
-    }
-
-    #[test]
-    fn test_key_derive() {
-        let network = Testnet;
-        let key_generate_cmd = KeySubCommand::Derive {
-            xprv: Xpriv::from_str("tprv8ZgxMBicQKsPfQjJy8ge2cvBfDjLxJSkvNLVQiw7BQ5gTjKadG2rrcQB5zjcdaaUTz5EDNJaS77q4DzjqjogQBfMsaXFFNP3UqoBnwt2kyT").unwrap(),
-            path: DerivationPath::from_str("m/84'/1'/0'/0").unwrap(),
-        };
-
-        let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
-        let result_obj = result.as_object().unwrap();
-
-        let xpub = result_obj.get("xpub").unwrap().as_str().unwrap();
-        let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
-
-        assert_eq!(&xpub, &"[566844c5/84'/1'/0'/0]tpubDFeqiDkfwR1tAhPxsXSZMfEmfpDhwhLyhLKZgmeBvuBkZQusoWeL62oGg2oTNGcENeKdwuGepAB85eMvyLemabYe9PSqv6cr5mFXktHc3Ka/*");
-        assert_eq!(&xprv, &"[566844c5/84'/1'/0'/0]tprv8ixoZoiRo3LDHENAysmxxFaf6nhmnNA582inQFbtWdPMivf7B7pjuYBQVuLC5bkM7tJZEDbfoivENsGZPBnQg1n52Kuc1P8X2Ei3XJuJX7c/*");
-    }
-
-    #[cfg(feature = "compiler")]
-    #[test]
-    fn test_parse_compile() {
-        let cli_args = vec![
-            "bdk-cli",
-            "compile",
-            "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))",
-            "--type",
-            "sh-wsh",
-        ];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Testnet,
-            datadir: None,
-            subcommand: CliSubCommand::Compile {
-                policy: "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(),
-                script_type: "sh-wsh".to_string(),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(feature = "compiler")]
-    #[test]
-    fn test_compile() {
-        let result = handle_compile_subcommand(
-            Network::Testnet,
-            "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(),
-            "sh-wsh".to_string(),
-        )
-        .unwrap();
-        let result_obj = result.as_object().unwrap();
-
-        let descriptor = result_obj.get("descriptor").unwrap().as_str().unwrap();
-        assert_eq!(
-            &descriptor,
-            &"sh(wsh(thresh(3,pk(Alice),s:pk(Bob),s:pk(Carol),snl:older(2))))#rmef3s78"
-        );
-    }
-
-    #[cfg(all(feature = "reserves", feature = "compact_filters"))]
-    #[test]
-    fn test_parse_produce_proof() {
-        let message = "Those coins belong to Satoshi Nakamoto";
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "wallet",
-            "--descriptor",
-            "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
-            "produce_proof",
-            "--message",
-            message.clone(),
-        ];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
-                        .to_string(),
-                    change_descriptor: None,
-                    compactfilter_opts: CompactFilterOpts {
-                        address: vec!["127.0.0.1:18444".to_string()],
-                        conn_count: 4,
-                        skip_blocks: 0,
-                    },
-                    proxy_opts: ProxyOpts {
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                },
-                subcommand: OnlineWalletSubCommand(ProduceProof {
-                    msg: message.to_string(),
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(all(feature = "reserves", feature = "esplora-ureq"))]
-    #[test]
-    fn test_parse_verify_proof_internal() {
-        let psbt = r#"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"#;
-        let message = "Those coins belong to Satoshi Nakamoto";
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "wallet",
-            "--descriptor",
-            "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
-            "verify_proof",
-            "--psbt",
-            psbt.clone(),
-            "--message",
-            message.clone(),
-        ];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
-                        .to_string(),
-                    change_descriptor: None,
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    proxy_opts: ProxyOpts {
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                },
-                subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof {
-                    psbt: psbt.to_string(),
-                    msg: message.to_string(),
-                    confirmations: 6,
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(all(feature = "reserves", feature = "esplora-ureq"))]
-    #[test]
-    fn test_parse_verify_proof_internal_confirmation() {
-        let psbt = r#"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"#;
-        let message = "Those coins belong to Satoshi Nakamoto";
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "wallet",
-            "--descriptor",
-            "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
-            "verify_proof",
-            "--psbt",
-            psbt.clone(),
-            "--message",
-            message.clone(),
-            "--confirmations",
-            "0",
-        ];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::Wallet {
-                wallet_opts: WalletOpts {
-                    wallet: None,
-                    verbose: false,
-                    descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
-                        .to_string(),
-                    change_descriptor: None,
-                    esplora_opts: EsploraOpts {
-                        server: "https://blockstream.info/testnet/api/".to_string(),
-                        timeout: 5,
-                        stop_gap: 10,
-                        conc: 4,
-                    },
-                    proxy_opts: ProxyOpts {
-                        proxy: None,
-                        proxy_auth: None,
-                        retries: 5,
-                    },
-                },
-                subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof {
-                    psbt: psbt.to_string(),
-                    msg: message.to_string(),
-                    confirmations: 0,
-                }),
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    #[test]
-    fn test_parse_verify_proof_external() {
-        let psbt = r#"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"#.to_string();
-        let address = "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d".to_string();
-        let message = "Those coins belong to Satoshi Nakamoto".to_string();
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "external_reserves",
-            &message,
-            &psbt,
-            "6",
-            &address,
-            "--server",
-            "ssl://electrum.blockstream.info:60002",
-        ];
-
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let expected_cli_opts = CliOpts {
-            network: Network::Bitcoin,
-            datadir: None,
-            subcommand: CliSubCommand::ExternalReserves {
-                message,
-                psbt,
-                confirmations: 6,
-                addresses: [address].to_vec(),
-                electrum_opts: ElectrumOpts {
-                    timeout: None,
-                    server: "ssl://electrum.blockstream.info:60002".to_string(),
-                    stop_gap: 10,
-                },
-            },
-        };
-
-        assert_eq!(expected_cli_opts, cli_opts);
-    }
-
-    /// Encodes a partially signed transaction as base64 and returns the  bytes of the resulting string.
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    fn encode_psbt(psbt: Psbt) -> Vec<u8> {
-        let serialized_psbt = psbt.serialize();
-        let base64_psbt = base64::encode(serialized_psbt);
-
-        base64_psbt.as_bytes().to_vec()
-    }
-
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    #[test]
-    fn test_proof_of_reserves_wallet() {
-        let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string();
-        let message = "Those coins belong to Satoshi Nakamoto";
-
-        let client = Client::new("ssl://electrum.blockstream.info:60002").unwrap();
-        let blockchain = BdkElectrumClient::new(client);
-        let mut wallet = Wallet::create_single(descriptor)
-            .network(Network::Testnet)
-            .create_wallet_no_persist()
-            .unwrap();
-
-        let scan_request = wallet.start_full_scan();
-
-        let update = blockchain.full_scan(scan_request, 50, 1, true).unwrap();
-        wallet.apply_update(update).unwrap();
-
-        let balance = wallet.balance();
-
-        let addr = wallet.reveal_next_address(bdk_wallet::KeychainKind::External);
-
-        assert_eq!(
-            "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d",
-            addr.to_string()
-        );
-
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "wallet",
-            "--descriptor",
-            &descriptor,
-            "produce_proof",
-            "--message",
-            message.clone(),
-        ];
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let wallet_subcmd = match cli_opts.subcommand {
-            CliSubCommand::Wallet {
-                wallet_opts: _,
-                subcommand: OnlineWalletSubCommand(online_subcommand),
-            } => online_subcommand,
-            _ => panic!("unexpected subcommand"),
-        };
-        let result = handle_online_wallet_subcommand(wallet, &blockchain, wallet_subcmd).unwrap();
-        let psbt: Psbt =
-            serde_json::from_str(&result.as_object().unwrap().get("psbt").unwrap().to_string())
-                .unwrap();
-        let psbt = encode_psbt(psbt);
-        let psbt = str::from_utf8(&psbt).unwrap();
-        assert_eq!(format!("{}", psbt), "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");
-
-        let psbt_b64 = &result
-            .as_object()
-            .unwrap()
-            .get("psbt_base64")
-            .unwrap()
-            .to_string();
-        assert_eq!(&format!("{}", psbt), psbt_b64.trim_matches('\"'));
-
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "wallet",
-            "--descriptor",
-            &descriptor,
-            "verify_proof",
-            "--psbt",
-            psbt,
-            "--message",
-            message.clone(),
-        ];
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let wallet_subcmd = match cli_opts.subcommand {
-            CliSubCommand::Wallet {
-                wallet_opts: _,
-                subcommand: OnlineWalletSubCommand(online_subcommand),
-            } => online_subcommand,
-            _ => panic!("unexpected subcommand"),
-        };
-        let result = handle_online_wallet_subcommand(wallet, &blockchain, wallet_subcmd).unwrap();
-        let spendable = result
-            .as_object()
-            .unwrap()
-            .get("spendable")
-            .unwrap()
-            .as_u64()
-            .unwrap();
-        assert_eq!(spendable, balance.trusted_spendable().to_sat());
-    }
-
-    #[cfg(all(feature = "reserves", feature = "electrum"))]
-    #[test]
-    fn test_proof_of_reserves_veryfy() {
-        let message = "Those coins belong to Satoshi Nakamoto";
-        let address = "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d";
-        let psbt = "cHNidP8BAKcBAAAAA9A7AuVMuwIrkAuXtKDqw3BruovK2PH7E90MzCbyaAMlAAAAAAD/////sq+79fxJ+5F8951Xcbp8N5VYVJoesG1zd3AxStcGwPYAAAAAAP/////AUS4n/SkO9SSvIv8MI2Xdm3xdn77Sud6uX5PyqCganAEAAAAA/////wGwrQEAAAAAABl2qRSff9CW037SwOP38M/JJL7vT/zraIisAAAAAAABAQoAAAAAAAAAAAFRAQcAAAEBHxAnAAAAAAAAFgAU7OUmVxBT2oZFTJ4GZ3nq+dFQLmoiAgMrBVgHi+w4aUqEkz1lkwPiV12ufpFoWRFFQRW/1kSH40gwRQIhAPgByvkajQrNeQDSGik2gnxpo/P/owiEHR+0nWefkXurAiBgrAlDvwuTiaGEEWQW/Kd7L7u7YOQnqvrd46DR0A8yPgEBBwABCGwCSDBFAiEA+AHK+RqNCs15ANIaKTaCfGmj8/+jCIQdH7SdZ5+Re6sCIGCsCUO/C5OJoYQRZBb8p3svu7tg5Ceq+t3joNHQDzI+ASEDKwVYB4vsOGlKhJM9ZZMD4lddrn6RaFkRRUEVv9ZEh+MAAQEfoIYBAAAAAAAWABTs5SZXEFPahkVMngZneer50VAuaiICAysFWAeL7DhpSoSTPWWTA+JXXa5+kWhZEUVBFb/WRIfjRzBEAiBSfiX0qP7vR+2Qx/mRJS8pwma8nTfOWKerzo6c0iSAfwIgEfX4Wt7YXd8MkKUEY627GWYCmKfMsJGcIC0U1wgc1vUBAQcAAQhrAkcwRAIgUn4l9Kj+70ftkMf5kSUvKcJmvJ03zlinq86OnNIkgH8CIBH1+Fre2F3fDJClBGOtuxlmApinzLCRnCAtFNcIHNb1ASEDKwVYB4vsOGlKhJM9ZZMD4lddrn6RaFkRRUEVv9ZEh+MAAA==";
-
-        let cli_args = vec![
-            "bdk-cli",
-            "--network",
-            "bitcoin",
-            "external_reserves",
-            message,
-            psbt,
-            "6",
-            address,
-            address, // passing the address twice on purpose, to test passing of multiple addresses
-            "--server",
-            "ssl://electrum.blockstream.info:60002",
-        ];
-        let cli_opts = CliOpts::from_iter(&cli_args);
-
-        let (message, psbt, confirmations, addresses, electrum_opts) = match cli_opts.subcommand {
-            CliSubCommand::ExternalReserves {
-                message,
-                psbt,
-                confirmations,
-                addresses,
-                electrum_opts,
-            } => (message, psbt, confirmations, addresses, electrum_opts),
-            _ => panic!("unexpected subcommand"),
-        };
-        let result = handle_ext_reserves_subcommand(
-            Network::Bitcoin,
-            message,
-            psbt,
-            confirmations,
-            addresses,
-            electrum_opts,
-        )
-        .unwrap();
-        let spendable = result
-            .as_object()
-            .unwrap()
-            .get("spendable")
-            .unwrap()
-            .as_u64()
-            .unwrap();
-        assert!(spendable > 0);
-    }
-
-    #[cfg(feature = "repl")]
-    #[test]
-    fn test_regex_double_quotes() {
-        let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX).unwrap();
-        let line = r#"restore -m "word1 word2 word3" -p 'test! 123 -test' "#;
-        let split_line: Vec<&str> = split_regex
-            .captures_iter(&line)
-            .map(|c| {
-                c.get(1)
-                    .or_else(|| c.get(2))
-                    .or_else(|| c.get(3))
-                    .unwrap()
-                    .as_str()
-            })
-            .collect();
-        assert_eq!(
-            vec!(
-                "restore",
-                "-m",
-                "word1 word2 word3",
-                "-p",
-                "test! 123 -test"
-            ),
-            split_line
-        );
-    }
-
-    #[cfg(feature = "repl")]
-    #[test]
-    fn test_regex_single_quotes() {
-        let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX).unwrap();
-        let line = r#"restore -m 'word1 word2 word3' -p "test *123 -test" "#;
-        let split_line: Vec<&str> = split_regex
-            .captures_iter(&line)
-            .map(|c| {
-                c.get(1)
-                    .or_else(|| c.get(2))
-                    .or_else(|| c.get(3))
-                    .unwrap()
-                    .as_str()
-            })
-            .collect();
-        assert_eq!(
-            vec!(
-                "restore",
-                "-m",
-                "word1 word2 word3",
-                "-p",
-                "test *123 -test"
-            ),
-            split_line
-        );
-    }
-}
+// #[cfg(test)]
+// mod test {
+//     use super::*;
+//     #[cfg(feature = "compiler")]
+//     use crate::handlers::handle_compile_subcommand;
+//     use crate::handlers::handle_key_subcommand;
+//     use bdk_wallet::bitcoin::bip32::{DerivationPath, Xpriv};
+//     use bdk_wallet::bitcoin::{Address, Network, OutPoint};
+//     use bdk_wallet::miniscript::bitcoin::network::Network::Testnet;
+//     use std::str::{self, FromStr};
+//
+//     use super::OfflineWalletSubCommand::{BumpFee, CreateTx, GetNewAddress};
+//     #[cfg(any(
+//         feature = "electrum",
+//         feature = "esplora",
+//         feature = "cbf",
+//         feature = "rpc"
+//     ))]
+//     use super::OnlineWalletSubCommand::{Broadcast, Sync};
+//     use super::WalletSubCommand::OfflineWalletSubCommand;
+//     #[cfg(any(
+//         feature = "electrum",
+//         feature = "esplora",
+//         feature = "cbf",
+//         feature = "rpc"
+//     ))]
+//     use super::WalletSubCommand::OnlineWalletSubCommand;
+//     #[cfg(feature = "repl")]
+//     use regex::Regex;
+//
+//     #[test]
+//     fn test_clap_args() {
+//         use clap::CommandFactory;
+//         CliOpts::command().debug_assert();
+//     }
+//
+//     #[test]
+//     fn test_parse_wallet_get_new_address() {
+//         let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
+//                             "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "get_new_address"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     #[cfg(feature = "electrum")]
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: None,
+//                         server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                         stop_gap: 10,
+//                     },
+//                     #[cfg(feature = "esplora")]
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     #[cfg(feature = "cbf")]
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                     #[cfg(feature = "rpc")]
+//                     rpc_opts: RpcOpts {
+//                         address: "127.0.0.1:18443".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: None,
+//                         start_time: 0,
+//                     },
+//                 },
+//                 subcommand: OfflineWalletSubCommand(GetNewAddress),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(feature = "electrum")]
+//     #[test]
+//     fn test_parse_wallet_electrum() {
+//         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
+//                             "--proxy", "127.0.0.1:9150", "--retries", "3", "--timeout", "10",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "--server","ssl://electrum.blockstream.info:50002",
+//                             "--stop_gap", "20",
+//                             "get_new_address"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: Some(10),
+//                         server: "ssl://electrum.blockstream.info:50002".to_string(),
+//                         stop_gap: 20
+//                     },
+//                     proxy_opts: ProxyOpts{
+//                         proxy: Some("127.0.0.1:9150".to_string()),
+//                         proxy_auth: None,
+//                         retries: 3,
+//                     },
+//                 },
+//                 subcommand: OfflineWalletSubCommand(GetNewAddress),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(feature = "esplora")]
+//     #[test]
+//     fn test_parse_wallet_esplora() {
+//         let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
+//                             "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "--server", "https://blockstream.info/api/",
+//                             "--conc", "10",
+//                             "--stop_gap", "20",
+//                             "get_new_address"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/api/".to_string(),
+//                         conc: 10,
+//                         stop_gap: 20,
+//                         timeout: 5,
+//                     },
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     }
+//                 },
+//                 subcommand: OfflineWalletSubCommand(GetNewAddress),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(feature = "rpc")]
+//     #[test]
+//     fn test_parse_wallet_rpc() {
+//         let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
+//                             "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "--node", "125.67.89.101:56678",
+//                             "--basic-auth", "user:password",
+//                             "--cookie", "/home/user/.bitcoin/regtest/.cookie",
+//                             "--start-time", "123456",
+//                             "get_new_address"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     rpc_opts: RpcOpts {
+//                         address: "125.67.89.101:56678".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: Some("/home/user/.bitcoin/regtest/.cookie".to_string()),
+//                         start_time: 123456,
+//                     },
+//                 },
+//                 subcommand: OfflineWalletSubCommand(GetNewAddress),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(feature = "cbf")]
+//     #[test]
+//     fn test_parse_wallet_compact_filters() {
+//         let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
+//                             "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "--proxy", "127.0.0.1:9005",
+//                             "--proxy_auth", "random_user:random_passwd",
+//                             "--node", "127.0.0.1:18444",
+//                             "--conn_count", "4",
+//                             "--skip_blocks", "5",
+//                             "get_new_address"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 5,
+//                     },
+//                     proxy_opts: ProxyOpts{
+//                         proxy: Some("127.0.0.1:9005".to_string()),
+//                         proxy_auth: Some(("random_user".to_string(), "random_passwd".to_string())),
+//                         retries: 5,
+//                     }
+//                 },
+//                 subcommand: OfflineWalletSubCommand(GetNewAddress),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(any(
+//         feature = "electrum",
+//         feature = "esplora",
+//         feature = "cbf",
+//         feature = "rpc"
+//     ))]
+//     #[test]
+//     fn test_parse_wallet_sync() {
+//         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "sync"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: None,
+//                     #[cfg(feature = "electrum")]
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: None,
+//                         server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                         stop_gap: 10,
+//                     },
+//                     #[cfg(feature = "esplora")]
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     #[cfg(feature = "cbf")]
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                     #[cfg(feature = "rpc")]
+//                     rpc_opts: RpcOpts {
+//                         address: "127.0.0.1:18443".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: None,
+//                         start_time: 0,
+//                     },
+//                 },
+//                 subcommand: OnlineWalletSubCommand(Sync),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[test]
+//     fn test_parse_wallet_create_tx() {
+//         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456", "--to", "mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910",
+//                             "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
+//                             "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
+//                             "--add_string","Hello BDK",
+//                            ];
+//
+//         let cli_opts = CliOpts::parse_from(&cli_args);
+//
+//         let script1 = Address::from_str("n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ")
+//             .unwrap()
+//             .assume_checked()
+//             .script_pubkey();
+//
+//         let script2 = Address::from_str("mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf")
+//             .unwrap()
+//             .assume_checked()
+//             .script_pubkey();
+//
+//         let outpoint1 = OutPoint::from_str(
+//             "87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
+//         )
+//         .unwrap();
+//         let outpoint2 = OutPoint::from_str(
+//             "87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
+//         )
+//         .unwrap();
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     #[cfg(feature = "electrum")]
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: None,
+//                         server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                         stop_gap: 10,
+//                     },
+//                     #[cfg(feature = "esplora")]
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     #[cfg(feature = "cbf")]
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                     #[cfg(feature = "rpc")]
+//                     rpc_opts: RpcOpts {
+//                         address: "127.0.0.1:18443".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: None,
+//                         start_time: 0,
+//                     },
+//                 },
+//                 subcommand: WalletSubCommand::OfflineWalletSubCommand(CreateTx {
+//                     recipients: vec![(script1, 123456), (script2, 78910)],
+//                     send_all: false,
+//                     enable_rbf: true,
+//                     offline_signer: false,
+//                     utxos: Some(vec!(outpoint1, outpoint2)),
+//                     unspendable: None,
+//                     fee_rate: None,
+//                     external_policy: None,
+//                     internal_policy: None,
+//                     add_data: None,
+//                     add_string: Some("Hello BDK".to_string()),
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[test]
+//     fn test_parse_wallet_bump_fee() {
+//         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+//                             "bump_fee", "--fee_rate", "6.1",
+//                             "--txid","35aab0d0213f8996f9e236a28630319b93109754819e8abf48a0835708d33506",
+//                             "--shrink","tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt"];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+//                     #[cfg(feature = "electrum")]
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: None,
+//                         server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                         stop_gap: 10,
+//                     },
+//                     #[cfg(feature = "esplora")]
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     #[cfg(feature = "cbf")]
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     #[cfg(feature = "rpc")]
+//                     rpc_opts: RpcOpts {
+//                         address: "127.0.0.1:18443".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: None,
+//                         start_time: 0,
+//                     },
+//                     #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     }
+//                 },
+//                 subcommand: OfflineWalletSubCommand(BumpFee {
+//                     txid: "35aab0d0213f8996f9e236a28630319b93109754819e8abf48a0835708d33506".to_string(),
+//                     shrink_address: Some(Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap().assume_checked()),
+//                     offline_signer: false,
+//                     utxos: None,
+//                     unspendable: None,
+//                     fee_rate: 6.1
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(any(
+//         feature = "electrum",
+//         feature = "esplora",
+//         feature = "cbf",
+//         feature = "rpc"
+//     ))]
+//     #[test]
+//     fn test_parse_wallet_broadcast() {
+//         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "broadcast",
+//                             "--psbt", "cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA="];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+//                     change_descriptor: None,
+//                     #[cfg(feature = "electrum")]
+//                     electrum_opts: ElectrumOpts {
+//                         timeout: None,
+//                         server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                         stop_gap: 10,
+//                     },
+//                     #[cfg(feature = "esplora")]
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     #[cfg(feature = "cbf")]
+//                     cbf_opts: CompactFilterOpts{
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
+//                     proxy_opts: ProxyOpts{
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                     #[cfg(feature = "rpc")]
+//                     rpc_opts: RpcOpts {
+//                         address: "127.0.0.1:18443".to_string(),
+//                         basic_auth: ("user".to_string(), "password".to_string()),
+//                         cookie: None,
+//                         start_time: 0,
+//                     },
+//                 },
+//                 subcommand: OnlineWalletSubCommand(Broadcast {
+//                     psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),
+//                     tx: None
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[test]
+//     fn test_parse_wrong_network() {
+//         let cli_args = vec!["repl", "--network", "badnet", "wallet",
+//                             "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+//                             "sync"];
+//
+//         let cli_opts = CliOpts::from_iter_safe(&cli_args);
+//         assert!(cli_opts.is_err());
+//     }
+//
+//     #[test]
+//     fn test_key_generate() {
+//         let network = Testnet;
+//         let key_generate_cmd = KeySubCommand::Generate {
+//             word_count: 12,
+//             password: Some("test123".to_string()),
+//         };
+//
+//         let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
+//         let result_obj = result.as_object().unwrap();
+//
+//         let mnemonic = result_obj.get("mnemonic").unwrap().as_str().unwrap();
+//         let mnemonic: Vec<&str> = mnemonic.split(' ').collect();
+//         let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
+//
+//         assert_eq!(mnemonic.len(), 12);
+//         assert_eq!(&xprv[0..4], "tprv");
+//     }
+//
+//     #[test]
+//     fn test_key_restore() {
+//         let network = Testnet;
+//         let key_generate_cmd = KeySubCommand::Restore {
+//             mnemonic: "payment battle unit sword token broccoli era violin purse trip blood hire"
+//                 .to_string(),
+//             password: Some("test123".to_string()),
+//         };
+//
+//         let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
+//         let result_obj = result.as_object().unwrap();
+//
+//         let fingerprint = result_obj.get("fingerprint").unwrap().as_str().unwrap();
+//         let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
+//
+//         assert_eq!(&fingerprint, &"828af366");
+//         assert_eq!(&xprv, &"tprv8ZgxMBicQKsPd18TeiFknZKqaZFwpdX9tvvKh8eeHSSPBQi5g9xPHztBg411o78G8XkrhQb6Q1cVvBJ1a9xuFHpmWgvQsvkJkNxBjfGoqhK");
+//     }
+//
+//     #[test]
+//     fn test_key_derive() {
+//         let network = Testnet;
+//         let key_generate_cmd = KeySubCommand::Derive {
+//             xprv: Xpriv::from_str("tprv8ZgxMBicQKsPfQjJy8ge2cvBfDjLxJSkvNLVQiw7BQ5gTjKadG2rrcQB5zjcdaaUTz5EDNJaS77q4DzjqjogQBfMsaXFFNP3UqoBnwt2kyT").unwrap(),
+//             path: DerivationPath::from_str("m/84'/1'/0'/0").unwrap(),
+//         };
+//
+//         let result = handle_key_subcommand(network, key_generate_cmd).unwrap();
+//         let result_obj = result.as_object().unwrap();
+//
+//         let xpub = result_obj.get("xpub").unwrap().as_str().unwrap();
+//         let xprv = result_obj.get("xprv").unwrap().as_str().unwrap();
+//
+//         assert_eq!(&xpub, &"[566844c5/84'/1'/0'/0]tpubDFeqiDkfwR1tAhPxsXSZMfEmfpDhwhLyhLKZgmeBvuBkZQusoWeL62oGg2oTNGcENeKdwuGepAB85eMvyLemabYe9PSqv6cr5mFXktHc3Ka/*");
+//         assert_eq!(&xprv, &"[566844c5/84'/1'/0'/0]tprv8ixoZoiRo3LDHENAysmxxFaf6nhmnNA582inQFbtWdPMivf7B7pjuYBQVuLC5bkM7tJZEDbfoivENsGZPBnQg1n52Kuc1P8X2Ei3XJuJX7c/*");
+//     }
+//
+//     #[cfg(feature = "compiler")]
+//     #[test]
+//     fn test_parse_compile() {
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "compile",
+//             "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))",
+//             "--type",
+//             "sh-wsh",
+//         ];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Testnet,
+//             datadir: None,
+//             subcommand: CliSubCommand::Compile {
+//                 policy: "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(),
+//                 script_type: "sh-wsh".to_string(),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(feature = "compiler")]
+//     #[test]
+//     fn test_compile() {
+//         let result = handle_compile_subcommand(
+//             Network::Testnet,
+//             "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(),
+//             "sh-wsh".to_string(),
+//         )
+//         .unwrap();
+//         let result_obj = result.as_object().unwrap();
+//
+//         let descriptor = result_obj.get("descriptor").unwrap().as_str().unwrap();
+//         assert_eq!(
+//             &descriptor,
+//             &"sh(wsh(thresh(3,pk(Alice),s:pk(Bob),s:pk(Carol),snl:older(2))))#rmef3s78"
+//         );
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "cbf"))]
+//     #[test]
+//     fn test_parse_produce_proof() {
+//         let message = "Those coins belong to Satoshi Nakamoto";
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "wallet",
+//             "--descriptor",
+//             "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
+//             "produce_proof",
+//             "--message",
+//             message.clone(),
+//         ];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
+//                         .to_string(),
+//                     change_descriptor: None,
+//                     cbf_opts: CompactFilterOpts {
+//                         address: vec!["127.0.0.1:18444".to_string()],
+//                         conn_count: 4,
+//                         skip_blocks: 0,
+//                     },
+//                     proxy_opts: ProxyOpts {
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                 },
+//                 subcommand: OnlineWalletSubCommand(ProduceProof {
+//                     msg: message.to_string(),
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "esplora-ureq"))]
+//     #[test]
+//     fn test_parse_verify_proof_internal() {
+//         let psbt = r#"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"#;
+//         let message = "Those coins belong to Satoshi Nakamoto";
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "wallet",
+//             "--descriptor",
+//             "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
+//             "verify_proof",
+//             "--psbt",
+//             psbt.clone(),
+//             "--message",
+//             message.clone(),
+//         ];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
+//                         .to_string(),
+//                     change_descriptor: None,
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     proxy_opts: ProxyOpts {
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                 },
+//                 subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof {
+//                     psbt: psbt.to_string(),
+//                     msg: message.to_string(),
+//                     confirmations: 6,
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "esplora-ureq"))]
+//     #[test]
+//     fn test_parse_verify_proof_internal_confirmation() {
+//         let psbt = r#"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"#;
+//         let message = "Those coins belong to Satoshi Nakamoto";
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "wallet",
+//             "--descriptor",
+//             "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
+//             "verify_proof",
+//             "--psbt",
+//             psbt.clone(),
+//             "--message",
+//             message.clone(),
+//             "--confirmations",
+//             "0",
+//         ];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::Wallet {
+//                 wallet_opts: WalletOpts {
+//                     wallet: None,
+//                     verbose: false,
+//                     descriptor: "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
+//                         .to_string(),
+//                     change_descriptor: None,
+//                     esplora_opts: EsploraOpts {
+//                         server: "https://blockstream.info/testnet/api/".to_string(),
+//                         timeout: 5,
+//                         stop_gap: 10,
+//                         conc: 4,
+//                     },
+//                     proxy_opts: ProxyOpts {
+//                         proxy: None,
+//                         proxy_auth: None,
+//                         retries: 5,
+//                     },
+//                 },
+//                 subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof {
+//                     psbt: psbt.to_string(),
+//                     msg: message.to_string(),
+//                     confirmations: 0,
+//                 }),
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "electrum"))]
+//     #[test]
+//     fn test_parse_verify_proof_external() {
+//         let psbt = r#"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"#.to_string();
+//         let address = "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d".to_string();
+//         let message = "Those coins belong to Satoshi Nakamoto".to_string();
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "external_reserves",
+//             &message,
+//             &psbt,
+//             "6",
+//             &address,
+//             "--server",
+//             "ssl://electrum.blockstream.info:60002",
+//         ];
+//
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let expected_cli_opts = CliOpts {
+//             network: Network::Bitcoin,
+//             datadir: None,
+//             subcommand: CliSubCommand::ExternalReserves {
+//                 message,
+//                 psbt,
+//                 confirmations: 6,
+//                 addresses: [address].to_vec(),
+//                 electrum_opts: ElectrumOpts {
+//                     timeout: None,
+//                     server: "ssl://electrum.blockstream.info:60002".to_string(),
+//                     stop_gap: 10,
+//                 },
+//             },
+//         };
+//
+//         assert_eq!(expected_cli_opts, cli_opts);
+//     }
+//
+//     /// Encodes a partially signed transaction as base64 and returns the  bytes of the resulting string.
+//     #[cfg(all(feature = "reserves", feature = "electrum"))]
+//     fn encode_psbt(psbt: Psbt) -> Vec<u8> {
+//         let mut encoded = Vec::<u8>::new();
+//         psbt.consensus_encode(&mut encoded).unwrap();
+//         let base64_psbt = base64::encode(&encoded);
+//
+//         base64_psbt.as_bytes().to_vec()
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "electrum"))]
+//     #[test]
+//     fn test_proof_of_reserves_wallet() {
+//         let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string();
+//         let message = "Those coins belong to Satoshi Nakamoto";
+//
+//         let client = Client::new("ssl://electrum.blockstream.info:60002").unwrap();
+//         let blockchain = ElectrumBlockchain::from(client);
+//         let wallet = Wallet::new(
+//             &descriptor,
+//             None,
+//             Network::Testnet,
+//             MemoryDatabase::default(),
+//         )
+//         .unwrap();
+//
+//         wallet.sync(&blockchain, SyncOptions::default()).unwrap();
+//         let balance = wallet.get_balance().unwrap();
+//
+//         let addr = wallet
+//             .get_address(bdk_wallet::wallet::AddressIndex::New)
+//             .unwrap();
+//         assert_eq!(
+//             "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d",
+//             addr.to_string()
+//         );
+//
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "wallet",
+//             "--descriptor",
+//             &descriptor,
+//             "produce_proof",
+//             "--message",
+//             message.clone(),
+//         ];
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let wallet_subcmd = match cli_opts.subcommand {
+//             CliSubCommand::Wallet {
+//                 wallet_opts: _,
+//                 subcommand: OnlineWalletSubCommand(online_subcommand),
+//             } => online_subcommand,
+//             _ => panic!("unexpected subcommand"),
+//         };
+//         let result = handle_online_wallet_subcommand(&wallet, &blockchain, wallet_subcmd).unwrap();
+//         let psbt: PartiallySignedTransaction =
+//             serde_json::from_str(&result.as_object().unwrap().get("psbt").unwrap().to_string())
+//                 .unwrap();
+//         let psbt = encode_psbt(psbt);
+//         let psbt = str::from_utf8(&psbt).unwrap();
+//         assert_eq!(format!("{}", psbt), "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");
+//
+//         let psbt_b64 = &result
+//             .as_object()
+//             .unwrap()
+//             .get("psbt_base64")
+//             .unwrap()
+//             .to_string();
+//         assert_eq!(&format!("{}", psbt), psbt_b64.trim_matches('\"'));
+//
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "wallet",
+//             "--descriptor",
+//             &descriptor,
+//             "verify_proof",
+//             "--psbt",
+//             psbt,
+//             "--message",
+//             message.clone(),
+//         ];
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let wallet_subcmd = match cli_opts.subcommand {
+//             CliSubCommand::Wallet {
+//                 wallet_opts: _,
+//                 subcommand: OnlineWalletSubCommand(online_subcommand),
+//             } => online_subcommand,
+//             _ => panic!("unexpected subcommand"),
+//         };
+//         let result = handle_online_wallet_subcommand(&wallet, &blockchain, wallet_subcmd).unwrap();
+//         let spendable = result
+//             .as_object()
+//             .unwrap()
+//             .get("spendable")
+//             .unwrap()
+//             .as_u64()
+//             .unwrap();
+//         assert_eq!(spendable, balance.get_spendable());
+//     }
+//
+//     #[cfg(all(feature = "reserves", feature = "electrum"))]
+//     #[test]
+//     fn test_proof_of_reserves_veryfy() {
+//         let message = "Those coins belong to Satoshi Nakamoto";
+//         let address = "tb1qanjjv4cs20dgv32vncrxw702l8g4qtn2m9wn7d";
+//         let psbt = "cHNidP8BAKcBAAAAA9A7AuVMuwIrkAuXtKDqw3BruovK2PH7E90MzCbyaAMlAAAAAAD/////sq+79fxJ+5F8951Xcbp8N5VYVJoesG1zd3AxStcGwPYAAAAAAP/////AUS4n/SkO9SSvIv8MI2Xdm3xdn77Sud6uX5PyqCganAEAAAAA/////wGwrQEAAAAAABl2qRSff9CW037SwOP38M/JJL7vT/zraIisAAAAAAABAQoAAAAAAAAAAAFRAQcAAAEBHxAnAAAAAAAAFgAU7OUmVxBT2oZFTJ4GZ3nq+dFQLmoiAgMrBVgHi+w4aUqEkz1lkwPiV12ufpFoWRFFQRW/1kSH40gwRQIhAPgByvkajQrNeQDSGik2gnxpo/P/owiEHR+0nWefkXurAiBgrAlDvwuTiaGEEWQW/Kd7L7u7YOQnqvrd46DR0A8yPgEBBwABCGwCSDBFAiEA+AHK+RqNCs15ANIaKTaCfGmj8/+jCIQdH7SdZ5+Re6sCIGCsCUO/C5OJoYQRZBb8p3svu7tg5Ceq+t3joNHQDzI+ASEDKwVYB4vsOGlKhJM9ZZMD4lddrn6RaFkRRUEVv9ZEh+MAAQEfoIYBAAAAAAAWABTs5SZXEFPahkVMngZneer50VAuaiICAysFWAeL7DhpSoSTPWWTA+JXXa5+kWhZEUVBFb/WRIfjRzBEAiBSfiX0qP7vR+2Qx/mRJS8pwma8nTfOWKerzo6c0iSAfwIgEfX4Wt7YXd8MkKUEY627GWYCmKfMsJGcIC0U1wgc1vUBAQcAAQhrAkcwRAIgUn4l9Kj+70ftkMf5kSUvKcJmvJ03zlinq86OnNIkgH8CIBH1+Fre2F3fDJClBGOtuxlmApinzLCRnCAtFNcIHNb1ASEDKwVYB4vsOGlKhJM9ZZMD4lddrn6RaFkRRUEVv9ZEh+MAAA==";
+//
+//         let cli_args = vec![
+//             "bdk-cli",
+//             "--network",
+//             "bitcoin",
+//             "external_reserves",
+//             message,
+//             psbt,
+//             "6",
+//             address,
+//             address, // passing the address twice on purpose, to test passing of multiple addresses
+//             "--server",
+//             "ssl://electrum.blockstream.info:60002",
+//         ];
+//         let cli_opts = CliOpts::from_iter(&cli_args);
+//
+//         let (message, psbt, confirmations, addresses, electrum_opts) = match cli_opts.subcommand {
+//             CliSubCommand::ExternalReserves {
+//                 message,
+//                 psbt,
+//                 confirmations,
+//                 addresses,
+//                 electrum_opts,
+//             } => (message, psbt, confirmations, addresses, electrum_opts),
+//             _ => panic!("unexpected subcommand"),
+//         };
+//         let result = handle_ext_reserves_subcommand(
+//             Network::Bitcoin,
+//             message,
+//             psbt,
+//             confirmations,
+//             addresses,
+//             electrum_opts,
+//         )
+//         .unwrap();
+//         let spendable = result
+//             .as_object()
+//             .unwrap()
+//             .get("spendable")
+//             .unwrap()
+//             .as_u64()
+//             .unwrap();
+//         assert!(spendable > 0);
+//     }
+//
+//     #[cfg(feature = "repl")]
+//     #[test]
+//     fn test_regex_double_quotes() {
+//         let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX).unwrap();
+//         let line = r#"restore -m "word1 word2 word3" -p 'test! 123 -test' "#;
+//         let split_line: Vec<&str> = split_regex
+//             .captures_iter(&line)
+//             .map(|c| {
+//                 c.get(1)
+//                     .or_else(|| c.get(2))
+//                     .or_else(|| c.get(3))
+//                     .unwrap()
+//                     .as_str()
+//             })
+//             .collect();
+//         assert_eq!(
+//             vec!(
+//                 "restore",
+//                 "-m",
+//                 "word1 word2 word3",
+//                 "-p",
+//                 "test! 123 -test"
+//             ),
+//             split_line
+//         );
+//     }
+//
+//     #[cfg(feature = "repl")]
+//     #[test]
+//     fn test_regex_single_quotes() {
+//         let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX).unwrap();
+//         let line = r#"restore -m 'word1 word2 word3' -p "test *123 -test" "#;
+//         let split_line: Vec<&str> = split_regex
+//             .captures_iter(&line)
+//             .map(|c| {
+//                 c.get(1)
+//                     .or_else(|| c.get(2))
+//                     .or_else(|| c.get(3))
+//                     .unwrap()
+//                     .as_str()
+//             })
+//             .collect();
+//         assert_eq!(
+//             vec!(
+//                 "restore",
+//                 "-m",
+//                 "word1 word2 word3",
+//                 "-p",
+//                 "test *123 -test"
+//             ),
+//             split_line
+//         );
+//     }
+// }
index 776dc107502dbb952e4259ac5bee3293cebba60f..2f18854dcde51ca164054781aabd987c5cc41b73 100644 (file)
@@ -1,13 +1,12 @@
+use bdk_wallet::bitcoin::hex::HexToBytesError;
+use bdk_wallet::bitcoin::{base64, consensus};
 use thiserror::Error;
 
 #[derive(Debug, Error)]
 pub enum BDKCliError {
-    #[cfg(feature = "regtest-node")]
-    #[error("Anyhow error: {0}")]
-    Anyhow(#[from] electrsd::corepc_node::anyhow::Error),
-
+    
     #[error("BIP39 error: {0}")]
-    BIP39Errror(#[from] bdk_wallet::bip39::Error),
+    BIP39Error(#[from] bdk_wallet::bip39::Error),
 
     #[error("BIP32 error: {0}")]
     BIP32Error(#[from] bdk_wallet::bitcoin::bip32::Error),
@@ -21,9 +20,6 @@ pub enum BDKCliError {
 
     #[error("Create transaction error: {0}")]
     CreateTx(#[from] bdk_wallet::error::CreateTxError),
-    #[cfg(feature = "regtest-node")]
-    #[error("CoreRPC error: {0}")]
-    CoreRPCError(#[from] electrsd::corepc_client::client_sync::Error),
 
     #[error("Descriptor error: {0}")]
     DescriptorError(#[from] bdk_wallet::descriptor::error::Error),
@@ -58,15 +54,29 @@ pub enum BDKCliError {
     #[error("PsbtError: {0}")]
     PsbtError(#[from] bdk_wallet::bitcoin::psbt::Error),
 
-    #[error("Regex error: {0}")]
-    RegexError(#[from] regex::Error),
-
     #[error("Rusqlite error: {0}")]
     RusqliteError(#[from] bdk_wallet::rusqlite::Error),
 
-    #[error("Serde error: {0}")]
-    Serde(#[from] serde_json::Error),
+    #[error("Serde json error: {0}")]
+    SerdeJson(#[from] serde_json::Error),
+
+    #[error("Bitcoin consensus encoding error: {0}")]
+    Serde(#[from] consensus::encode::Error),
 
     #[error("Signer error: {0}")]
     SignerError(#[from] bdk_wallet::signer::SignerError),
+
+    #[cfg(feature = "electrum")]
+    #[error("Electrum error: {0}")]
+    Electrum(#[from] bdk_electrum::electrum_client::Error),
+
+    #[cfg(feature = "esplora")]
+    #[error("Esplora error: {0}")]
+    Esplora(#[from] bdk_esplora::esplora_client::Error),
+
+    #[error("Chain connect error: {0}")]
+    Chain(#[from] bdk_wallet::chain::local_chain::CannotConnectError),
+
+    #[error("Consensus decoding error: {0}")]
+    Hex(#[from] HexToBytesError),
 }
index 0fec6c680852e0edca5f58ad91fad399aacf510e..ae88d3cda6fc03dc71468ba1c8b057f7dbc751ec 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
 //
 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 //!
 //! This module describes all the command handling logic used by bdk-cli.
 
-use clap::Parser;
-use serde_json::json;
-use std::collections::BTreeMap;
-use std::convert::TryFrom;
-use std::str::FromStr;
-
 use crate::commands::OfflineWalletSubCommand::*;
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "cbf",
+    feature = "rpc"
+))]
+use crate::commands::OnlineWalletSubCommand::*;
 use crate::commands::*;
 use crate::error::BDKCliError as Error;
 use crate::utils::*;
-
-use bdk_macros::maybe_async;
 use bdk_wallet::bip39::{Language, Mnemonic};
 use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource};
-use bdk_wallet::bitcoin::consensus::encode::serialize_hex;
-use bdk_wallet::bitcoin::{
-    script::PushBytesBuf, secp256k1::Secp256k1, Amount, FeeRate, Network, Psbt, Sequence, Txid,
-};
+use bdk_wallet::bitcoin::consensus::encode::{serialize, serialize_hex};
+use bdk_wallet::bitcoin::script::PushBytesBuf;
+use bdk_wallet::bitcoin::Network;
+use bdk_wallet::bitcoin::{secp256k1::Secp256k1, Transaction, Txid};
+use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence};
 use bdk_wallet::descriptor::Segwitv0;
-use bdk_wallet::keys::{
-    bip39::WordCount, DerivableKey, DescriptorKey, DescriptorKey::Secret, ExtendedKey,
-    GeneratableKey, GeneratedKey,
-};
-use bdk_wallet::miniscript::miniscript;
+#[cfg(feature = "compiler")]
+use bdk_wallet::descriptor::{Descriptor, Legacy, Miniscript};
+use bdk_wallet::keys::bip39::WordCount;
+#[cfg(feature = "sqlite")]
 use bdk_wallet::rusqlite::Connection;
 use bdk_wallet::{KeychainKind, SignOptions, Wallet};
 
-#[cfg(any(
-    feature = "electrum",
-    feature = "esplora",
-    feature = "compact_filters",
-    feature = "rpc"
-))]
-use {
-    crate::commands::OnlineWalletSubCommand::*,
-    bdk_reserves::bdk::{
-        blockchain::{log_progress, Blockchain},
-        SyncOptions,
-    },
-    bdk_wallet::Wallet,
-};
-
-#[cfg(all(feature = "reserves", feature = "electrum"))]
-use {
-    bdk_electrum::electrum_client::{Client, ElectrumApi},
-    bdk_reserves::bdk::{bitcoin::psbt::PartiallySignedTransaction, blockchain::Capability},
-    bdk_wallet::bitcoin::Address,
-};
-
-#[cfg(feature = "reserves")]
-use bdk_reserves::reserves::{verify_proof, ProofOfReserves};
-#[cfg(feature = "hardware-signer")]
-use hwi::{
-    types::{HWIChain, HWIDescriptor},
-    HWIClient,
-};
+use bdk_wallet::keys::DescriptorKey::Secret;
+use bdk_wallet::keys::{DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey};
+use bdk_wallet::miniscript::miniscript;
+use std::collections::{BTreeMap, HashSet};
+use std::convert::TryFrom;
+use std::io::Write;
+
 #[cfg(feature = "compiler")]
-use {
-    bdk_wallet::descriptor::{Descriptor, Legacy, Miniscript},
-    bdk_wallet::miniscript::policy::Concrete,
-};
-#[cfg(feature = "repl")]
-use {
-    regex::Regex,
-    rustyline::{error::ReadlineError, Editor},
-};
+use bdk_wallet::miniscript::policy::Concrete;
+use serde_json::json;
+use std::str::FromStr;
+
+#[cfg(feature = "electrum")]
+use crate::utils::BlockchainClient::Electrum;
+#[cfg(feature = "esplora")]
+use crate::utils::BlockchainClient::Esplora;
+#[cfg(feature = "esplora")]
+use bdk_esplora::EsploraAsyncExt;
+use bdk_wallet::bitcoin::base64::prelude::*;
+use bdk_wallet::bitcoin::consensus::Decodable;
+use bdk_wallet::bitcoin::hex::FromHex;
 
 /// Execute an offline wallet sub-command
 ///
@@ -86,7 +67,7 @@ pub fn handle_offline_wallet_subcommand(
     offline_subcommand: OfflineWalletSubCommand,
 ) -> Result<serde_json::Value, Error> {
     match offline_subcommand {
-        GetNewAddress => {
+        NewAddress => {
             let addr = wallet.reveal_next_address(KeychainKind::External);
             if wallet_opts.verbose {
                 Ok(json!({
@@ -99,10 +80,23 @@ pub fn handle_offline_wallet_subcommand(
                 }))
             }
         }
-        ListUnspent => Ok(serde_json::to_value(
+        UnusedAddress => {
+            let addr = wallet.next_unused_address(KeychainKind::External);
+            if wallet_opts.verbose {
+                Ok(json!({
+                    "address": addr.address,
+                    "index": addr.index
+                }))
+            } else {
+                Ok(json!({
+                    "address": addr.address,
+                }))
+            }
+        }
+        Unspent => Ok(serde_json::to_value(
             wallet.list_unspent().collect::<Vec<_>>(),
         )?),
-        ListTransactions => {
+        Transactions => {
             let transactions: Vec<_> = wallet
                 .transactions()
                 .map(|tx| {
@@ -120,7 +114,7 @@ pub fn handle_offline_wallet_subcommand(
 
             Ok(serde_json::to_value(transactions)?)
         }
-        GetBalance => Ok(json!({"satoshi": wallet.balance()})),
+        Balance => Ok(json!({"satoshi": wallet.balance()})),
         CreateTx {
             recipients,
             send_all,
@@ -161,7 +155,7 @@ pub fn handle_offline_wallet_subcommand(
             }
 
             if let Some(utxos) = utxos {
-                tx_builder.add_utxos(&utxos[..]);
+                tx_builder.add_utxos(&utxos[..]).unwrap();
             }
 
             if let Some(unspendable) = unspendable {
@@ -169,7 +163,7 @@ pub fn handle_offline_wallet_subcommand(
             }
 
             if let Some(base64_data) = add_data {
-                let op_return_data = base64::decode(&base64_data).unwrap();
+                let op_return_data = BASE64_STANDARD.decode(&base64_data).unwrap();
                 tx_builder.add_data(&PushBytesBuf::try_from(op_return_data).unwrap());
             } else if let Some(string_data) = add_string {
                 let data = PushBytesBuf::try_from(string_data.as_bytes().to_vec()).unwrap();
@@ -188,7 +182,8 @@ pub fn handle_offline_wallet_subcommand(
 
             let psbt = tx_builder.finish()?;
 
-            let psbt_base64 = psbt.to_string();
+            let serialized_psbt = psbt.serialize();
+            let psbt_base64 = BASE64_STANDARD.encode(&serialized_psbt);
 
             if wallet_opts.verbose {
                 Ok(
@@ -223,7 +218,7 @@ pub fn handle_offline_wallet_subcommand(
             }
 
             if let Some(utxos) = utxos {
-                tx_builder.add_utxos(&utxos[..]);
+                tx_builder.add_utxos(&utxos[..]).unwrap();
             }
 
             if let Some(unspendable) = unspendable {
@@ -232,7 +227,10 @@ pub fn handle_offline_wallet_subcommand(
 
             let psbt = tx_builder.finish()?;
 
-            Ok(json!({"psbt": psbt.to_string(), "details": psbt}))
+            let serialized_psbt = psbt.serialize();
+            let psbt_base64 = BASE64_STANDARD.encode(serialized_psbt);
+
+            Ok(json!({"psbt": psbt_base64, "details": psbt}))
         }
         Policies => {
             let external_policy = wallet.policies(KeychainKind::External)?;
@@ -252,7 +250,7 @@ pub fn handle_offline_wallet_subcommand(
             assume_height,
             trust_witness_utxo,
         } => {
-            let psbt_bytes = base64::decode(psbt)?;
+            let psbt_bytes = BASE64_STANDARD.decode(psbt)?;
             let mut psbt = Psbt::deserialize(&psbt_bytes)?;
             let signopt = SignOptions {
                 assume_height,
@@ -262,14 +260,16 @@ pub fn handle_offline_wallet_subcommand(
             let finalized = wallet.sign(&mut psbt, signopt)?;
             if wallet_opts.verbose {
                 Ok(
-                    json!({"psbt": psbt.to_string(),"is_finalized": finalized, "serialized_psbt": psbt}),
+                    json!({"psbt": BASE64_STANDARD.encode(serialize(&psbt_bytes)),"is_finalized": finalized, "serialized_psbt": psbt}),
                 )
             } else {
-                Ok(json!({"psbt": psbt.to_string(),"is_finalized": finalized,}))
+                Ok(
+                    json!({"psbt": BASE64_STANDARD.encode(serialize(&psbt_bytes)),"is_finalized": finalized,}),
+                )
             }
         }
         ExtractPsbt { psbt } => {
-            let psbt_serialized = base64::decode(psbt)?;
+            let psbt_serialized = BASE64_STANDARD.decode(psbt)?;
             let psbt = Psbt::deserialize(&psbt_serialized)?;
             let raw_tx = psbt.extract_tx()?;
             Ok(json!({"raw_tx": serialize_hex(&raw_tx),}))
@@ -279,7 +279,7 @@ pub fn handle_offline_wallet_subcommand(
             assume_height,
             trust_witness_utxo,
         } => {
-            let psbt_bytes = base64::decode(psbt)?;
+            let psbt_bytes = BASE64_STANDARD.decode(psbt)?;
             let mut psbt: Psbt = Psbt::deserialize(&psbt_bytes)?;
 
             let signopt = SignOptions {
@@ -290,17 +290,19 @@ pub fn handle_offline_wallet_subcommand(
             let finalized = wallet.finalize_psbt(&mut psbt, signopt)?;
             if wallet_opts.verbose {
                 Ok(
-                    json!({ "psbt": psbt.to_string(),"is_finalized": finalized, "serialized_psbt": psbt}),
+                    json!({ "psbt": BASE64_STANDARD.encode(serialize(&psbt_bytes)),"is_finalized": finalized, "serialized_psbt": psbt}),
                 )
             } else {
-                Ok(json!({ "psbt": psbt.to_string(),"is_finalized": finalized,}))
+                Ok(
+                    json!({ "psbt": BASE64_STANDARD.encode(serialize(&psbt_bytes)),"is_finalized": finalized,}),
+                )
             }
         }
         CombinePsbt { psbt } => {
             let mut psbts = psbt
                 .iter()
                 .map(|s| {
-                    let psbt = base64::decode(s)?;
+                    let psbt = BASE64_STANDARD.decode(s)?;
                     Ok(Psbt::deserialize(&psbt)?)
                 })
                 .collect::<Result<Vec<_>, Error>>()?;
@@ -315,7 +317,7 @@ pub fn handle_offline_wallet_subcommand(
                     Ok(acc)
                 },
             )?;
-            Ok(json!({ "psbt": final_psbt.to_string() }))
+            Ok(json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }))
         }
     }
 }
@@ -323,86 +325,121 @@ pub fn handle_offline_wallet_subcommand(
 /// Execute an online wallet sub-command
 ///
 /// Online wallet sub-commands are described in [`OnlineWalletSubCommand`].
-#[maybe_async]
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
-    feature = "compact_filters",
+    feature = "cbf",
     feature = "rpc"
 ))]
-pub(crate) fn handle_online_wallet_subcommand(
-    wallet: Wallet,
-    blockchain: &B,
+pub(crate) async fn handle_online_wallet_subcommand(
+    wallet: &mut Wallet,
+    client: BlockchainClient,
     online_subcommand: OnlineWalletSubCommand,
 ) -> Result<serde_json::Value, Error> {
     match online_subcommand {
+        FullScan { stop_gap } => {
+            let request = wallet.start_full_scan().inspect({
+                let mut stdout = std::io::stdout();
+                let mut once = HashSet::<KeychainKind>::new();
+                move |k, spk_i, _| {
+                    if once.insert(k) {
+                        print!("\nScanning keychain [{:?}]", k);
+                    }
+                    print!(" {:<3}", spk_i);
+                    stdout.flush().expect("must flush");
+                }
+            });
+            match client {
+                #[cfg(feature = "electrum")]
+                Electrum { client, batch_size } => {
+                    // Populate the electrum client's transaction cache so it doesn't re-download transaction we
+                    // already have.
+                    client
+                        .populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
+
+                    let update = client.full_scan(request, stop_gap, batch_size, false)?;
+                    wallet.apply_update(update)?;
+                }
+                #[cfg(feature = "esplora")]
+                Esplora {
+                    client,
+                    parallel_requests,
+                } => {
+                    let update = client
+                        .full_scan(request, stop_gap, parallel_requests)
+                        .await
+                        .map_err(|e| *e)?;
+                    wallet.apply_update(update)?;
+                }
+            }
+            Ok(json!({}))
+        }
         Sync => {
-            maybe_await!(wallet.sync(
-                blockchain,
-                SyncOptions {
-                    progress: Some(Box::new(log_progress())),
+            let request = wallet
+                .start_sync_with_revealed_spks()
+                .inspect(|item, progress| {
+                    let pc = (100 * progress.consumed()) as f32 / progress.total() as f32;
+                    eprintln!("[ SCANNING {:03.0}% ] {}", pc, item);
+                });
+            match client {
+                #[cfg(feature = "electrum")]
+                Electrum { client, batch_size } => {
+                    // Populate the electrum client's transaction cache so it doesn't re-download transaction we
+                    // already have.
+                    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)?;
+                }
+                #[cfg(feature = "esplora")]
+                Esplora {
+                    client,
+                    parallel_requests,
+                } => {
+                    let update = client
+                        .sync(request, parallel_requests)
+                        .await
+                        .map_err(|e| *e)?;
+                    wallet.apply_update(update)?;
                 }
-            ))?;
+            }
             Ok(json!({}))
         }
         Broadcast { psbt, tx } => {
             let tx = match (psbt, tx) {
                 (Some(psbt), None) => {
-                    let psbt = base64::decode(&psbt).map_err(|e| Error::Generic(e.to_string()))?;
+                    let psbt = BASE64_STANDARD
+                        .decode(&psbt)
+                        .map_err(|e| Error::Generic(e.to_string()))?;
                     let psbt: Psbt = Psbt::deserialize(&psbt)?;
                     is_final(&psbt)?;
-                    psbt.extract_tx()
+                    psbt.extract_tx()?
+                }
+                (None, Some(tx)) => {
+                    let tx_bytes = Vec::<u8>::from_hex(&tx)?;
+                    Transaction::consensus_decode(&mut tx_bytes.as_slice())?
                 }
-                (None, Some(tx)) => Psbt::deserialize(&Vec::<u8>::from_hex(&tx)?),
                 (Some(_), Some(_)) => panic!("Both `psbt` and `tx` options not allowed"),
                 (None, None) => panic!("Missing `psbt` and `tx` option"),
             };
-            maybe_await!(blockchain.broadcast(&tx))?;
-            Ok(json!({ "txid": tx.txid() }))
-        }
-        #[cfg(feature = "reserves")]
-        ProduceProof { msg } => {
-            let mut psbt = maybe_await!(wallet.create_proof(&msg))?;
-
-            let _finalized = wallet.sign(
-                &mut psbt,
-                SignOptions {
-                    trust_witness_utxo: true,
-                    ..Default::default()
-                },
-            )?;
 
-            let psbt_b64 = psbt.to_string();
-
-            Ok(json!({ "psbt": psbt , "psbt_base64" : psbt_b64}))
-        }
-        #[cfg(feature = "reserves")]
-        VerifyProof {
-            psbt,
-            msg,
-            confirmations,
-        } => {
-            let psbt = base64::decode(&psbt)?;
-            let psbt: Psbt = Psbt::deserialize(&psbt)?;
-            let current_height = blockchain.get_height()?;
-            let max_confirmation_height = if confirmations == 0 {
-                None
-            } else {
-                if !blockchain
-                    .get_capabilities()
-                    .contains(&Capability::GetAnyTx)
-                {
-                    return Err(Error::Generic(
-                        "For validating a proof with a certain number of confirmations, we need a Blockchain with the GetAnyTx capability."
-                        .to_string()
-                    ));
-                }
-                Some(current_height - confirmations)
+            let txid = match client {
+                #[cfg(feature = "electrum")]
+                Electrum {
+                    client,
+                    batch_size: _,
+                } => client.transaction_broadcast(&tx)?,
+                #[cfg(feature = "esplora")]
+                Esplora {
+                    client,
+                    parallel_requests: _,
+                } => client
+                    .broadcast(&tx)
+                    .await
+                    .map(|()| tx.compute_txid().clone())?,
             };
-
-            let spendable =
-                maybe_await!(wallet.verify_proof(&psbt, &msg, max_confirmation_height))?;
-            Ok(json!({ "spendable": spendable }))
+            Ok(json!({ "txid": txid }))
         }
     }
 }
@@ -411,7 +448,7 @@ pub(crate) fn handle_online_wallet_subcommand(
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
-    feature = "compact_filters",
+    feature = "cbf",
     feature = "rpc"
 ))]
 pub(crate) fn is_final(psbt: &Psbt) -> Result<(), Error> {
@@ -502,18 +539,6 @@ pub(crate) fn handle_key_subcommand(
                 Err(Error::Generic("Invalid key variant".to_string()))
             }
         }
-        #[cfg(feature = "hardware-signer")]
-        KeySubCommand::Hardware {} => {
-            let chain = HWIChain::from(network);
-            let devices = HWIClient::enumerate().map_err(|e| Error::Generic(e.to_string()))?;
-            let descriptors = devices.iter().map(|device_| {
-                let device = device_.as_ref().map_err(|e| Error::Generic(e.to_string()))?;
-                let client = HWIClient::get_client(&device, true, chain.clone()).unwrap();
-                let descriptors: HWIDescriptor<String> = client.get_descriptors(None).map_err(|e|Error::Generic(e.to_string()))?;
-                Ok(json!({"device": device.model, "receiving": descriptors.receive[0].to_string(), "change": descriptors.internal[0]}))
-            }).collect::<Result<Vec<_>, Error>>()?;
-            Ok(json!(descriptors))
-        }
     }
 }
 
@@ -544,104 +569,96 @@ pub(crate) fn handle_compile_subcommand(
     Ok(json!({"descriptor": descriptor.to_string()}))
 }
 
-/// Handle Proof of Reserves commands
-///
-/// Proof of reserves options are described in [`CliSubCommand::ExternalReserves`].
-#[cfg(all(feature = "reserves", feature = "electrum"))]
-pub(crate) fn handle_ext_reserves_subcommand(
-    network: bdk_reserves::bdk::bitcoin::Network,
-    message: String,
-    psbt: String,
-    confirmations: usize,
-    addresses: Vec<String>,
-    electrum_opts: ElectrumOpts,
-) -> Result<serde_json::Value, Error> {
-    let psbt = base64::decode(&psbt)?;
-
-    let psbt: PartiallySignedTransaction = PartiallySignedTransaction::deserialize(&psbt)
-        .map_err(|e| Error::Generic(e.to_string()))?;
-    let client = Client::new(&electrum_opts.server).map_err(|e| Error::Generic(e.to_string()))?;
-
-    let current_block_height = client
-        .block_headers_subscribe()
-        .map(|data| data.height)
-        .map_err(|e| {
-            Error::Generic(format!(
-                "Failed to get block height from electrum server: {:?}",
-                e
-            ))
-        })?;
-    let max_confirmation_height = Some(current_block_height - confirmations);
-
-    let outpoints_per_addr = addresses
-        .iter()
-        .map(|address| {
-            let address = Address::from_str(address)?.assume_checked();
-            get_outpoints_for_address(address, &client, max_confirmation_height)
-        })
-        .collect::<Result<Vec<Vec<_>>, Error>>()
-        .map_err(|e| Error::Generic(e.to_string()))?;
-    let outpoints_combined = outpoints_per_addr
-        .iter()
-        .fold(Vec::new(), |mut outpoints, outs| {
-            outpoints.append(&mut outs.clone());
-            outpoints
-        });
-
-    let spendable = verify_proof(&psbt, &message, outpoints_combined, network)
-        .map_err(|e| Error::Generic(format!("{:?}", e)))?;
-
-    Ok(json!({ "spendable": spendable }))
-}
-
 /// The global top level handler.
-#[maybe_async]
-pub(crate) fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
+pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
     let network = cli_opts.network;
-    let home_dir = prepare_home_dir(cli_opts.datadir)?;
+
     let result = match cli_opts.subcommand {
-        #[cfg(feature = "regtest-node")]
-        CliSubCommand::Node { subcommand: cmd } => {
-            let backend = new_backend(&home_dir)?;
-            serde_json::to_string_pretty(&backend.exec_cmd(cmd)?)
-        }
         #[cfg(any(
             feature = "electrum",
             feature = "esplora",
-            feature = "compact_filters",
+            feature = "cbf",
             feature = "rpc"
         ))]
         CliSubCommand::Wallet {
             wallet_opts,
             subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
         } => {
-            let wallet_opts = maybe_descriptor_wallet_name(wallet_opts, cli_opts.network)?;
-            let database = open_database(&wallet_opts, &home_dir)?;
-            let backend = new_backend(&home_dir)?;
-            let blockchain = new_blockchain(network, &wallet_opts, &backend, &home_dir)?;
-            let wallet = new_wallet(network, &wallet_opts, database)?;
-            let result = maybe_await!(handle_online_wallet_subcommand(
-                &wallet,
-                &blockchain,
-                online_subcommand
-            ))?;
+            let blockchain_client = new_blockchain_client(&wallet_opts)?;
+            let network = cli_opts.network;
+            #[cfg(any(feature = "sqlite"))]
+            let result = {
+                let home_dir = prepare_home_dir(cli_opts.datadir)?;
+                let wallet_name = &wallet_opts.wallet;
+                let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
+                let mut persister = match &wallet_opts.database_type {
+                    #[cfg(feature = "sqlite")]
+                    DatabaseType::Sqlite => {
+                        let db_file = database_path.join("wallet.sqlite");
+                        let connection = Connection::open(db_file)?;
+                        log::debug!("Sqlite database opened successfully");
+                        connection
+                    }
+                };
+
+                let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
+                let result = handle_online_wallet_subcommand(
+                    &mut wallet,
+                    blockchain_client,
+                    online_subcommand,
+                )
+                .await?;
+                wallet.persist(&mut persister)?;
+                result
+            };
+            #[cfg(not(any(feature = "sqlite")))]
+            let result = {
+                let mut wallet = new_wallet(network, &wallet_opts)?;
+                handle_online_wallet_subcommand(&mut wallet, blockchain_client, online_subcommand)
+                    .await?
+            };
             serde_json::to_string_pretty(&result)
         }
         CliSubCommand::Wallet {
             wallet_opts,
             subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
         } => {
-            let wallet_opts = maybe_descriptor_wallet_name(wallet_opts, network)?;
-            let database = open_database(&wallet_opts, &home_dir)?;
-            let mut wallet = new_wallet(network, &wallet_opts, database)?;
-            let result =
-                handle_offline_wallet_subcommand(&mut wallet, &wallet_opts, offline_subcommand)?;
+            let network = cli_opts.network;
+            #[cfg(any(feature = "sqlite"))]
+            let result = {
+                let home_dir = prepare_home_dir(cli_opts.datadir)?;
+                let wallet_name = &wallet_opts.wallet;
+                let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
+                let mut persister = match &wallet_opts.database_type {
+                    #[cfg(feature = "sqlite")]
+                    DatabaseType::Sqlite => {
+                        let db_file = database_path.join("wallet.sqlite");
+                        let connection = Connection::open(db_file)?;
+                        log::debug!("Sqlite database opened successfully");
+                        connection
+                    }
+                };
+
+                let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
+                let result = handle_offline_wallet_subcommand(
+                    &mut wallet,
+                    &wallet_opts,
+                    offline_subcommand,
+                )?;
+                wallet.persist(&mut persister)?;
+                result
+            };
+            #[cfg(not(any(feature = "sqlite")))]
+            let result = {
+                let mut wallet = new_wallet(network, &wallet_opts)?;
+                handle_offline_wallet_subcommand(&mut wallet, &wallet_opts, offline_subcommand)?
+            };
             serde_json::to_string_pretty(&result)
         }
         CliSubCommand::Key {
             subcommand: key_subcommand,
         } => {
-            let result = handle_key_subcommand(cli_opts.network, key_subcommand)?;
+            let result = handle_key_subcommand(network, key_subcommand)?;
             serde_json::to_string_pretty(&result)
         }
         #[cfg(feature = "compiler")]
@@ -649,142 +666,136 @@ pub(crate) fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             policy,
             script_type,
         } => {
-            let result = handle_compile_subcommand(cli_opts.network, policy, script_type)?;
+            let result = handle_compile_subcommand(network, policy, script_type)?;
             serde_json::to_string_pretty(&result)
         }
         #[cfg(feature = "repl")]
         CliSubCommand::Repl { wallet_opts } => {
-            let wallet_opts = maybe_descriptor_wallet_name(wallet_opts, cli_opts.network)?;
-            let database = open_database(&wallet_opts, &home_dir)?;
-
-            let mut wallet = new_wallet(cli_opts.network, &wallet_opts, database)?;
-
-            let mut rl = Editor::<()>::new();
-
-            // if rl.load_history("history.txt").is_err() {
-            //     println!("No previous history.");
-            // }
+            let network = cli_opts.network;
+            #[cfg(any(feature = "sqlite"))]
+            let (mut wallet, mut persister) = {
+                let wallet_name = &wallet_opts.wallet;
+
+                let home_dir = prepare_home_dir(cli_opts.datadir)?;
+
+                let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
+
+                let mut persister = match &wallet_opts.database_type {
+                    #[cfg(feature = "sqlite")]
+                    DatabaseType::Sqlite => {
+                        let db_file = database_path.join("wallet.sqlite");
+                        let connection = Connection::open(db_file)?;
+                        log::debug!("Sqlite database opened successfully");
+                        connection
+                    }
+                };
+                let wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
+                (wallet, persister)
+            };
+            #[cfg(not(any(feature = "sqlite")))]
+            let mut wallet = new_wallet(network, &wallet_opts)?;
 
-            let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX)?;
+            loop {
+                let line = readline()?;
+                let line = line.trim();
+                if line.is_empty() {
+                    continue;
+                }
 
-            #[cfg(any(
-                feature = "electrum",
-                feature = "esplora",
-                feature = "compact_filters",
-                feature = "rpc"
-            ))]
-            let backend = new_backend(&home_dir)?;
+                let result = respond(network, &mut wallet, &wallet_opts, line).await;
+                #[cfg(any(feature = "sqlite"))]
+                wallet.persist(&mut persister)?;
 
-            loop {
-                let readline = rl.readline(">> ");
-                match readline {
-                    Ok(line) => {
-                        if line.trim() == "" {
-                            continue;
+                match result {
+                    Ok(quit) => {
+                        if quit {
+                            break;
                         }
-                        rl.add_history_entry(line.as_str());
-                        let split_line: Vec<&str> = split_regex
-                            .captures_iter(&line)
-                            .map(|c| {
-                                Ok(c.get(1)
-                                    .or_else(|| c.get(2))
-                                    .or_else(|| c.get(3))
-                                    .ok_or_else(|| Error::Generic("Invalid commands".to_string()))?
-                                    .as_str())
-                            })
-                            .collect::<Result<Vec<_>, Error>>()?;
-                        let repl_subcommand = ReplSubCommand::from_iter_safe(split_line);
-                        if let Err(err) = repl_subcommand {
-                            println!("{}", err);
-                            continue;
-                        }
-                        // if error will be printed above
-                        let repl_subcommand = repl_subcommand.unwrap();
-                        log::debug!("repl_subcommand = {:?}", repl_subcommand);
-
-                        let result = match repl_subcommand {
-                            #[cfg(feature = "regtest-node")]
-                            ReplSubCommand::Node { subcommand } => {
-                                match backend.exec_cmd(subcommand) {
-                                    Ok(result) => Ok(result),
-                                    Err(e) => Ok(serde_json::Value::String(e.to_string())),
-                                }
-                            }
-                            #[cfg(any(
-                                feature = "electrum",
-                                feature = "esplora",
-                                feature = "compact_filters",
-                                feature = "rpc"
-                            ))]
-                            ReplSubCommand::Wallet {
-                                subcommand:
-                                    WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
-                            } => {
-                                let blockchain = new_blockchain(
-                                    cli_opts.network,
-                                    &wallet_opts,
-                                    &backend,
-                                    &home_dir,
-                                )?;
-                                maybe_await!(handle_online_wallet_subcommand(
-                                    &wallet,
-                                    &blockchain,
-                                    online_subcommand,
-                                ))
-                            }
-                            ReplSubCommand::Wallet {
-                                subcommand:
-                                    WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
-                            } => handle_offline_wallet_subcommand(
-                                &mut wallet,
-                                &wallet_opts,
-                                offline_subcommand,
-                            ),
-                            ReplSubCommand::Key { subcommand } => {
-                                handle_key_subcommand(cli_opts.network, subcommand)
-                            }
-                            ReplSubCommand::Exit => break,
-                        };
-
-                        println!("{}", serde_json::to_string_pretty(&result?)?);
                     }
-                    Err(ReadlineError::Interrupted) => continue,
-                    Err(ReadlineError::Eof) => break,
                     Err(err) => {
-                        println!("{:?}", err);
-                        break;
+                        writeln!(std::io::stdout(), "{err}")
+                            .map_err(|e| Error::Generic(e.to_string()))?;
+                        std::io::stdout()
+                            .flush()
+                            .map_err(|e| Error::Generic(e.to_string()))?;
                     }
                 }
             }
+            Ok("".to_string())
+        }
+    };
+    result.map_err(|e| e.into())
+}
 
-            Ok("Exiting REPL".to_string())
+#[cfg(feature = "repl")]
+async fn respond(
+    network: Network,
+    wallet: &mut Wallet,
+    wallet_opts: &WalletOpts,
+    line: &str,
+) -> Result<bool, String> {
+    use clap::Parser;
+
+    let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;
+    let repl_subcommand = ReplSubCommand::try_parse_from(args).map_err(|e| e.to_string())?;
+    let response = match repl_subcommand {
+        #[cfg(any(
+            feature = "electrum",
+            feature = "esplora",
+            feature = "cbf",
+            feature = "rpc"
+        ))]
+        ReplSubCommand::Wallet {
+            subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
+        } => {
+            let blockchain = new_blockchain_client(&wallet_opts).map_err(|e| e.to_string())?;
+            let value = handle_online_wallet_subcommand(wallet, blockchain, online_subcommand)
+                .await
+                .map_err(|e| e.to_string())?;
+            Some(value)
         }
-        #[cfg(all(feature = "reserves", feature = "electrum"))]
-        CliSubCommand::ExternalReserves {
-            message,
-            psbt,
-            confirmations,
-            addresses,
-            electrum_opts,
+        ReplSubCommand::Wallet {
+            subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
         } => {
-            let result = handle_ext_reserves_subcommand(
-                cli_opts.network,
-                message,
-                psbt,
-                confirmations,
-                addresses,
-                electrum_opts,
-            )?;
-            serde_json::to_string_pretty(&result)
+            let value = handle_offline_wallet_subcommand(wallet, wallet_opts, offline_subcommand)
+                .map_err(|e| e.to_string())?;
+            Some(value)
+        }
+        ReplSubCommand::Key { subcommand } => {
+            let value = handle_key_subcommand(network, subcommand).map_err(|e| e.to_string())?;
+            Some(value)
         }
+        ReplSubCommand::Exit => None,
     };
-    result.map_err(|e| e.into())
+    if let Some(value) = response {
+        let value = serde_json::to_string_pretty(&value).map_err(|e| e.to_string())?;
+        writeln!(std::io::stdout(), "{}", value).map_err(|e| e.to_string())?;
+        std::io::stdout().flush().map_err(|e| e.to_string())?;
+        Ok(false)
+    } else {
+        writeln!(std::io::stdout(), "Exiting...").map_err(|e| e.to_string())?;
+        std::io::stdout().flush().map_err(|e| e.to_string())?;
+        Ok(true)
+    }
+}
+
+#[cfg(feature = "repl")]
+fn readline() -> Result<String, Error> {
+    write!(std::io::stdout(), "> ").map_err(|e| Error::Generic(e.to_string()))?;
+    std::io::stdout()
+        .flush()
+        .map_err(|e| Error::Generic(e.to_string()))?;
+    let mut buffer = String::new();
+    std::io::stdin()
+        .read_line(&mut buffer)
+        .map_err(|e| Error::Generic(e.to_string()))?;
+    Ok(buffer)
 }
 
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
-    feature = "compact_filters",
+    feature = "cbf",
     feature = "rpc"
 ))]
 #[cfg(test)]
index a3b82d17b45fcdd244a15c28319e3d47e1fb09ac..dc8849f479c118a8e3af3e590596deebd24750ae 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
 //
 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 mod commands;
 mod error;
 mod handlers;
-mod nodes;
 mod utils;
-#[cfg(target_arch = "wasm32")]
-mod wasm;
-
-use bitcoin::Network;
 
+use bdk_wallet::bitcoin::Network;
 use log::{debug, error, warn};
 
 use crate::commands::CliOpts;
-use crate::error::BDKCliError as Error;
 use crate::handlers::*;
-use bdk_macros::{maybe_async, maybe_await};
-use bdk_wallet::bitcoin;
 use clap::Parser;
 
-#[cfg(any(feature = "repl", target_arch = "wasm32"))]
-const REPL_LINE_SPLIT_REGEX: &str = r#""([^"]*)"|'([^']*)'|([\w\-]+)"#;
-
-#[maybe_async]
-#[cfg(not(target_arch = "wasm32"))]
-#[cfg_attr(feature = "async-interface", tokio::main)]
-fn main() {
+#[tokio::main]
+async fn main() {
     env_logger::init();
-
     let cli_opts: CliOpts = CliOpts::parse();
 
-    let network = cli_opts.network;
+    let network = &cli_opts.network;
     debug!("network: {:?}", network);
-    if network == Network::Bitcoin {
+    if network == &Network::Bitcoin {
         warn!("This is experimental software and not currently recommended for use on Bitcoin mainnet, proceed with caution.")
     }
 
-    match maybe_await!(handle_command(cli_opts)) {
+    match handle_command(cli_opts).await {
         Ok(result) => println!("{}", result),
         Err(e) => {
-            match e {
-                Error::ChecksumMismatch => error!("Descriptor checksum mismatch. Are you using a different descriptor for an already defined wallet name? (if you are not specifying the wallet name it is automatically named based on the descriptor)"),
-                e => error!("{}", e.to_string()),
-            }
-        },
+            error!("{}", e.to_string())
+        }
     }
 }
-
-// wasm32 requires a non-async main
-#[cfg(target_arch = "wasm32")]
-fn main() {}
diff --git a/src/nodes.rs b/src/nodes.rs
deleted file mode 100644 (file)
index d222e52..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! The Node structures
-//!
-//! This module defines containers for different backend clients.
-//! These Backends are auto-deployed in `regtest-*` features to spawn a blockchain
-//! interface of selected types, and connects the bdk-cli wallet to it.
-//!
-//! For more information check TODO: [Add readme section for `regtest-*` features.]
-
-#[cfg(feature = "regtest-node")]
-use {
-    crate::{commands::NodeSubCommand, error::BDKCliError as Error},
-    bdk_wallet::bitcoin::{Address, Amount},
-    electrsd::corepc_node::{Client, Node},
-    serde_json::Value,
-    std::{str::FromStr, sync::Arc},
-};
-
-#[allow(dead_code)]
-// Different regtest node types activated with `regtest-*` mode.
-// If `regtest-*` feature not activated, then default is `None`.
-pub enum Nodes {
-    None,
-    #[cfg(feature = "regtest-bitcoin")]
-    /// A bitcoin core backend. Wallet connected to it via RPC.
-    Bitcoin {
-        bitcoind: Arc<Node>,
-    },
-    #[cfg(feature = "regtest-electrum")]
-    /// An Electrum backend with an underlying bitcoin core
-    /// Wallet connected to it, via the electrum url.
-    Electrum {
-        bitcoind: Arc<Node>,
-        electrsd: Box<electrsd::ElectrsD>,
-    },
-    /// An Esplora backend with an underlying bitcoin core
-    /// Wallet connected to it, via the esplora url.
-    #[cfg(any(feature = "regtest-esplora-ureq", feature = "regtest-esplora-reqwest"))]
-    Esplora {
-        bitcoind: Arc<Node>,
-        esplorad: Box<electrsd::ElectrsD>,
-    },
-}
-
-#[cfg(feature = "regtest-node")]
-impl Nodes {
-    /// Execute a [`NodeSubCommand`] in the backend
-    pub fn exec_cmd(&self, cmd: NodeSubCommand) -> Result<serde_json::Value, Error> {
-        let client = self.get_client()?;
-        match cmd {
-            NodeSubCommand::GetInfo => Ok(serde_json::to_value(client.get_blockchain_info()?)?),
-            NodeSubCommand::GetNewAddress => Ok(serde_json::to_value(client.new_address()?)?),
-
-            NodeSubCommand::Generate { block_num } => {
-                let core_addrs = client.new_address()?;
-                let block_hashes = client.generate_to_address(block_num as usize, &core_addrs)?;
-                Ok(serde_json::to_value(block_hashes)?)
-            }
-
-            NodeSubCommand::GetBalance => Ok(serde_json::to_value(client.get_balance()?)?),
-
-            NodeSubCommand::SendToAddress { address, amount } => {
-                let address = Address::from_str(&address)?.assume_checked();
-                let amount = Amount::from_sat(amount);
-                let txid = client.send_to_address(&address, amount)?;
-                Ok(serde_json::to_value(&txid)?)
-            }
-
-            NodeSubCommand::BitcoinCli(args) => {
-                let cmd = &args[0];
-                let args = args[1..]
-                    .iter()
-                    .map(|arg| serde_json::Value::from_str(arg))
-                    .collect::<Result<Vec<Value>, _>>()?;
-                Ok(client.call::<Value>(cmd, &args)?)
-            }
-        }
-    }
-
-    // Expose the underlying RPC client.
-    pub fn get_client(&self) -> Result<&Client, Error> {
-        match self {
-            Self::None => Err(Error::Generic(
-                "No backend available. Cannot execute node commands".to_string(),
-            )),
-            #[cfg(feature = "regtest-bitcoin")]
-            Self::Bitcoin { bitcoind } => Ok(&bitcoind.client),
-            #[cfg(feature = "regtest-electrum")]
-            Self::Electrum { bitcoind, .. } => Ok(&bitcoind.client),
-            #[cfg(any(feature = "regtest-esplora-ureq", feature = "regtest-esplora-reqwest"))]
-            Self::Esplora { bitcoind, .. } => Ok(&bitcoind.client),
-        }
-    }
-}
index 40cd95a1ce6cc6d04efe5af741ef8ef824161021..6fd83be8c4a898e5fdc33c2851004d51e932b0ca 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
 //
 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@@ -9,61 +9,27 @@
 //! Utility Tools
 //!
 //! This module includes all the utility tools used by the App.
-use crate::commands::WalletOpts;
 use crate::error::BDKCliError as Error;
-use crate::nodes::Nodes;
-use bdk_wallet::bitcoin::secp256k1::Secp256k1;
-use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf};
-use bdk_wallet::{wallet_name_from_descriptor, PersistedWallet, Wallet};
-use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
-#[cfg(feature = "hardware-signer")]
-use {
-    bdk_wallet::{
-        signer::{SignerError, SignerOrdering},
-        KeychainKind,
-    },
-    hwi::{interface::HWIClient, signer::HWISigner, types::HWIChain},
-    std::sync::Arc,
-};
+#[cfg(any(feature = "sqlite"))]
+use std::path::{Path, PathBuf};
 
-#[cfg(all(feature = "reserves", feature = "electrum"))]
-use {
-    bdk_electrum::electrum_client::{Client, ElectrumApi},
-    bdk_reserves::bdk::blockchain::ConfigurableBlockchain,
-    bdk_wallet::bitcoin::TxOut,
-    electrsd::corepc_client::client_sync::v17::blockchain,
-};
+use crate::commands::WalletOpts;
+use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf};
 
-use bdk_wallet::rusqlite::{Connection, OpenFlags};
-#[cfg(feature = "esplora")]
-use {
-    bdk_bitcoind_rpc::bitcoincore_rpc::{self, Auth},
-    bdk_esplora::{esplora_client, EsploraAsyncExt},
-};
+#[cfg(any(feature = "electrum", feature = "esplora"))]
+use crate::commands::ClientType;
 
-/// Create a randomized wallet name from the descriptor checksum.
-/// If wallet options already includes a name, use that instead.
-pub(crate) fn maybe_descriptor_wallet_name(
-    wallet_opts: WalletOpts,
-    network: Network,
-) -> Result<WalletOpts, Error> {
-    if wallet_opts.wallet.is_some() {
-        return Ok(wallet_opts);
-    }
-    // Use deterministic wallet name derived from descriptor
-    let wallet_name = wallet_name_from_descriptor(
-        &wallet_opts.descriptor[..],
-        wallet_opts.change_descriptor.as_deref(),
-        network,
-        &Secp256k1::new(),
-    )?;
-    let mut wallet_opts = wallet_opts;
-    wallet_opts.wallet = Some(wallet_name);
+#[cfg(any(feature = "sqlite",))]
+use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
 
-    Ok(wallet_opts)
-}
+#[cfg(feature = "electrum")]
+use bdk_electrum;
+use bdk_wallet::Wallet;
+
+#[cfg(feature = "esplora")]
+use bdk_esplora;
 
 /// Parse the recipient (Address,Amount) argument from cli input.
 pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
@@ -79,6 +45,12 @@ pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
     Ok((addr.script_pubkey(), val))
 }
 
+#[cfg(any(
+    feature = "electrum",
+    feature = "cbf",
+    feature = "esplora",
+    feature = "rpc"
+))]
 /// Parse the proxy (Socket:Port) argument from the cli input.
 pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), Error> {
     let parts: Vec<_> = s.split(':').collect();
@@ -92,38 +64,6 @@ pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), Error> {
     Ok((user, passwd))
 }
 
-/// Fetch all the utxos, for a given address.
-#[cfg(all(feature = "reserves", feature = "electrum"))]
-pub fn get_outpoints_for_address(
-    address: Address,
-    client: &Client,
-    max_confirmation_height: Option<usize>,
-) -> Result<Vec<(OutPoint, TxOut)>, Error> {
-    let unspents = client
-        .script_list_unspent(&address.script_pubkey())
-        .map_err(|e| Error::Generic(e.to_string()))?;
-    unspents
-        .iter()
-        .filter(|utxo| {
-            utxo.height > 0 && utxo.height <= max_confirmation_height.unwrap_or(usize::MAX)
-        })
-        .map(|utxo| {
-            let tx = match client.transaction_get(&utxo.tx_hash) {
-                Ok(tx) => tx,
-                Err(e) => return Err(e).map_err(|e| Error::Generic(e.to_string()))?,
-            };
-
-            Ok((
-                OutPoint {
-                    txid: utxo.tx_hash,
-                    vout: utxo.tx_pos as u32,
-                },
-                tx.output[utxo.tx_pos].clone(),
-            ))
-        })
-        .collect()
-}
-
 /// Parse a outpoint (Txid:Vout) argument from cli input.
 pub(crate) fn parse_outpoint(s: &str) -> Result<OutPoint, Error> {
     Ok(OutPoint::from_str(s)?)
@@ -135,6 +75,7 @@ pub(crate) fn parse_address(address_str: &str) -> Result<Address, Error> {
     Ok(unchecked_address.assume_checked())
 }
 
+#[cfg(any(feature = "sqlite",))]
 /// Prepare bdk-cli home directory
 ///
 /// This function is called to check if [`crate::CliOpts`] datadir is set.
@@ -143,7 +84,7 @@ pub(crate) fn prepare_home_dir(home_path: Option<PathBuf>) -> Result<PathBuf, Er
     let dir = home_path.unwrap_or_else(|| {
         let mut dir = PathBuf::new();
         dir.push(
-            dirs_next::home_dir()
+            dirs::home_dir()
                 .ok_or_else(|| Error::Generic("home dir not found".to_string()))
                 .unwrap(),
         );
@@ -152,22 +93,6 @@ pub(crate) fn prepare_home_dir(home_path: Option<PathBuf>) -> Result<PathBuf, Er
     });
 
     if !dir.exists() {
-        log::info!("Creating home directory {}", dir.as_path().display());
-        std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
-    }
-
-    Ok(dir)
-}
-
-/// Prepare bdk_cli wallet directory.
-#[cfg(any(feature = "sqlite-db", feature = "compact_filters"))]
-fn prepare_wallet_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
-    let mut dir = home_path.to_owned();
-
-    dir.push(wallet_name);
-
-    if !dir.exists() {
-        log::info!("Creating wallet directory {}", dir.as_path().display());
         std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
     }
 
@@ -175,147 +100,23 @@ fn prepare_wallet_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Er
 }
 
 /// Prepare wallet database directory.
-#[cfg(feature = "sqlite-db")]
-fn prepare_wallet_db_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
-    let mut db_dir = prepare_wallet_dir(wallet_name, home_path)?;
-
-    db_dir.push("wallet.sqlite");
-
-    Ok(db_dir)
-}
-
-/// Prepare blockchain data directory (for compact filters).
-#[cfg(feature = "compact_filters")]
-fn prepare_bc_dir(wallet_name: &str, home_path: &Path) -> Result<PathBuf, Error> {
-    let mut bc_dir = prepare_wallet_dir(wallet_name, home_path)?;
-
-    bc_dir.push("compact_filters");
-
-    if !bc_dir.exists() {
-        log::info!(
-            "Creating blockchain directory {}",
-            bc_dir.as_path().display()
-        );
-        std::fs::create_dir(&bc_dir).map_err(|e| Error::Generic(e.to_string()))?;
-    }
-
-    Ok(bc_dir)
-}
-
-/// Create the global bitcoind directory.
-/// multiple wallets can access the same node datadir, and they will have separate
-/// wallet names in `<home_path>/bitcoind/regtest/wallets`.
-#[cfg(feature = "regtest-node")]
-pub(crate) fn prepare_bitcoind_datadir(home_path: &Path) -> Result<PathBuf, Error> {
+#[cfg(any(feature = "sqlite"))]
+pub(crate) fn prepare_wallet_db_dir(
+    wallet_name: &Option<String>,
+    home_path: &Path,
+) -> Result<PathBuf, Error> {
     let mut dir = home_path.to_owned();
-
-    dir.push("bitcoind");
-
-    if !dir.exists() {
-        log::info!("Creating node directory {}", dir.as_path().display());
-        std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
+    if let Some(wallet_name) = wallet_name {
+        dir.push(wallet_name);
     }
 
-    Ok(dir)
-}
-
-/// Create the global electrsd directory.
-/// multiple wallets can access the same node datadir, and they will have separate
-/// wallet names in `<home_path>/bitcoind/regtest/wallets`.
-#[cfg(feature = "regtest-electrum")]
-pub(crate) fn prepare_electrum_datadir(home_path: &Path) -> Result<PathBuf, Error> {
-    let mut dir = home_path.to_owned();
-
-    dir.push("electrsd");
-
     if !dir.exists() {
-        log::info!("Creating node directory {}", dir.as_path().display());
         std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
     }
 
     Ok(dir)
 }
 
-#[allow(unused_variables)]
-#[cfg(feature = "sqlite-db")]
-/// Open the wallet database.
-pub(crate) fn open_database(
-    wallet_opts: &WalletOpts,
-    home_path: &Path,
-) -> Result<Connection, Error> {
-    let wallet_name = wallet_opts.wallet.as_ref().expect("wallet name");
-    let database_path = prepare_wallet_db_dir(wallet_name, home_path)?;
-
-    let db_file = database_path.join("wallet.sqlite");
-
-    let database = Connection::open_with_flags(db_file, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
-
-    log::debug!("database opened successfully");
-    Ok(database)
-}
-
-/// Create a new backend node at given datadir.
-#[allow(dead_code)]
-pub(crate) fn new_backend(_datadir: &Path) -> Result<Nodes, Error> {
-    #[cfg(feature = "regtest-node")]
-    let bitcoind = {
-        // Configure node directory according to cli options
-        // nodes always have a persistent directory
-        let datadir = prepare_bitcoind_datadir(_datadir)?;
-        let mut bitcoind_conf = electrsd::corepc_node::Conf::default();
-        bitcoind_conf.staticdir = Some(datadir);
-        let bitcoind_exe =
-            electrsd::downloaded_exe_path().expect("We should always have downloaded path");
-        let node = electrsd::corepc_node::Node::with_conf(bitcoind_exe, &bitcoind_conf)?;
-        Arc::new(node)
-    };
-
-    #[cfg(feature = "regtest-bitcoin")]
-    let backend = {
-        Nodes::Bitcoin {
-            bitcoind: Arc::clone(&bitcoind),
-        }
-    };
-
-    #[cfg(feature = "regtest-electrum")]
-    let backend = {
-        // Configure node directory according to cli options
-        // nodes always have a persistent directory
-        let datadir = prepare_electrum_datadir(_datadir)?;
-        let mut elect_conf = electrsd::Conf::default();
-        elect_conf.staticdir = Some(datadir);
-        let elect_exe =
-            electrsd::downloaded_exe_path().expect("We should always have downloaded path");
-        let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf)?;
-        Nodes::Electrum {
-            bitcoind: Arc::clone(&bitcoind),
-            electrsd: Box::new(electrsd),
-        }
-    };
-
-    #[cfg(any(feature = "regtest-esplora-ureq", feature = "regtest-esplora-reqwest"))]
-    let backend = {
-        // Configure node directory according to cli options
-        // nodes always have a persistent directory
-        let datadir = prepare_electrum_datadir(_datadir)?;
-        let mut elect_conf = electrsd::Conf::default();
-        elect_conf.staticdir = Some(datadir);
-        elect_conf.http_enabled = true;
-        let elect_exe =
-            electrsd::downloaded_exe_path().expect("Electrsd downloaded binaries not found");
-        let electrsd = electrsd::ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap();
-        Nodes::Esplora {
-            bitcoind: Arc::clone(&bitcoind),
-            esplorad: Box::new(electrsd),
-        }
-    };
-
-    #[cfg(not(feature = "regtest-node"))]
-    let backend = Nodes::None;
-
-    Ok(backend)
-}
-
 #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc",))]
 pub(crate) enum BlockchainClient {
     #[cfg(feature = "electrum")]
@@ -333,143 +134,130 @@ pub(crate) enum BlockchainClient {
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
-    feature = "compact_filters",
-    feature = "rpc"
+    feature = "rpc",
+    feature = "cbf",
 ))]
-/// Create a new blockchain for a given [Nodes] if available
-/// or else create one from the wallet configuration options.
-pub(crate) fn new_blockchain(
-    wallet_opts: &WalletOpts,
-    _backend: &Nodes,
-) -> Result<BlockchainClient, Error> {
+pub(crate) enum BlockchainClient {
     #[cfg(feature = "electrum")]
-    let client = match _backend {
-        #[cfg(feature = "regtest-electrum")]
-        Nodes::Electrum { electrsd, .. } => {
-            let sock5 = Socks5Config::new(wallet_opts.proxy_opts.proxy.unwrap());
-
-            let electrum_config = ConfigBuilder::new()
-                .retry(wallet_opts.proxy_opts.retries)
-                .socks5(Some(sock5))
-                .timeout(wallet_opts.electrum_opts.timeout)
-                .validate_domain(true)
-                .build();
+    Electrum {
+        client: bdk_electrum::BdkElectrumClient<bdk_electrum::electrum_client::Client>,
+        batch_size: usize,
+    },
+    #[cfg(feature = "esplora")]
+    Esplora {
+        client: bdk_esplora::esplora_client::AsyncClient,
+        parallel_requests: usize,
+    },
+    // TODO rbf
+    // TODO cbf
+}
 
-            let client =
-                electrum_client::Client::from_config(&electrsd.electrum_url, electrum_config)
-                    .unwrap();
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "rpc",
+    feature = "cbf",
+))]
+/// Create a new blockchain from the wallet configuration options.
+pub(crate) fn new_blockchain_client(wallet_opts: &WalletOpts) -> Result<BlockchainClient, Error> {
+    let url = wallet_opts.url.as_str();
+    let client = match wallet_opts.client_type {
+        #[cfg(feature = "electrum")]
+        ClientType::Electrum => {
+            let client = bdk_electrum::electrum_client::Client::new(url)
+                .map(|client| bdk_electrum::BdkElectrumClient::new(client))?;
+            BlockchainClient::Electrum {
+                client,
+                batch_size: wallet_opts.batch_size,
+            }
         }
-
         #[cfg(feature = "esplora")]
-        Nodes::Esplora { esplorad, bitcoind } => {
-            let client = esplora_client::Builder::new(&esplorad.electrum_url.as_str())
-                .timeout(wallet_opts.esplora_opts.timeout)
-                .proxy(wallet_opts.proxy_opts.proxy.unwrap().as_str())
-                .build_blocking();
-
-            client
-        }
-    };
-
-    #[cfg(feature = "compact_filters")]
-    let config = {
-        return unimplemented!();
-        let mut peers = vec![];
-        for addrs in wallet_opts.compactfilter_opts.address.clone() {
-            for _ in 0..wallet_opts.compactfilter_opts.conn_count {
-                peers.push(BitcoinPeerConfig {
-                    address: addrs.clone(),
-                    socks5: wallet_opts.proxy_opts.proxy.clone(),
-                    socks5_credentials: wallet_opts.proxy_opts.proxy_auth.clone(),
-                })
+        ClientType::Esplora => {
+            let client = bdk_esplora::esplora_client::Builder::new(url).build_async()?;
+            BlockchainClient::Esplora {
+                client,
+                parallel_requests: wallet_opts.parallel_requests,
             }
         }
-
-        let wallet_name = wallet_opts.wallet.as_ref().expect("wallet name");
-        // CompactFilters(CompactFiltersBlockchainConfig {
-        //     peers,
-        //     network: _network,
-        //     storage_dir: prepare_bc_dir(wallet_name, _home_dir)?
-        //         .into_os_string()
-        //         .into_string()
-        //         .map_err(|_| Error::Generic("Internal OS_String conversion error".to_string()))?,
-        //     skip_blocks: Some(wallet_opts.compactfilter_opts.skip_blocks),
-        // })
-    };
-
-    #[cfg(feature = "rpc")]
-    let config = {
-        let (url, auth) = match _backend {
-            #[cfg(feature = "regtest-node")]
-            Nodes::Bitcoin { bitcoind } => (
-                bitcoind.params.rpc_socket.to_string(),
-                Auth::CookieFile(bitcoind.params.cookie_file.clone()),
-            ),
-            _ => {
-                let auth = if let Some(cookie) = &wallet_opts.rpc_opts.cookie {
-                    Auth::CookieFile(cookie.into())
-                } else {
-                    Auth::UserPass(
-                        wallet_opts.rpc_opts.basic_auth.0.clone(),
-                        wallet_opts.rpc_opts.basic_auth.1.clone(),
-                    )
-                };
-                (wallet_opts.rpc_opts.address.clone(), auth)
-            }
-        };
-        let wallet_name = wallet_opts
-            .wallet
-            .to_owned()
-            .expect("Wallet name should be available this level");
-
-        let rpc_url = "http://".to_string() + &url;
-
-        bdk_bitcoind_rpc::bitcoincore_rpc::Client::new(&rpc_url, auth).unwrap()
     };
+    Ok(client)
 }
 
-/// Create a new wallet from given wallet configuration options.
-pub(crate) fn new_wallet(
+#[cfg(any(feature = "sqlite",))]
+/// Create a new persisted wallet from given wallet configuration options.
+pub(crate) fn new_persisted_wallet<P: WalletPersister>(
     network: Network,
+    persister: &mut P,
     wallet_opts: &WalletOpts,
-    mut database: Connection,
-) -> Result<PersistedWallet<Connection>, Error> {
-    let descriptor = wallet_opts.descriptor.clone();
-    let change_descriptor = wallet_opts.change_descriptor.clone();
-    let wallet = Wallet::create(descriptor, change_descriptor.unwrap())
-        .network(network)
-        .create_wallet(&mut database)
-        .map_err(|e| Error::Generic(e.to_string()))?;
+) -> Result<PersistedWallet<P>, Error> {
+    let ext_descriptor = wallet_opts.ext_descriptor.clone();
+    let int_descriptor = wallet_opts.int_descriptor.clone();
+
+    let mut wallet_load_params = Wallet::load();
+    if ext_descriptor.is_some() {
+        wallet_load_params =
+            wallet_load_params.descriptor(KeychainKind::External, ext_descriptor.clone());
+    }
+    if int_descriptor.is_some() {
+        wallet_load_params =
+            wallet_load_params.descriptor(KeychainKind::Internal, int_descriptor.clone());
+    }
+    if ext_descriptor.is_some() || int_descriptor.is_some() {
+        wallet_load_params = wallet_load_params.extract_keys();
+    }
 
-    #[cfg(feature = "hardware-signer")]
-    let wallet = add_hardware_signers(wallet, network)?;
+    let wallet_opt = wallet_load_params
+        .check_network(network)
+        .load_wallet(persister)
+        .map_err(|_| Error::Generic("Can't load wallet".to_string()))?;
+
+    let wallet = match wallet_opt {
+        Some(wallet) => wallet,
+        None => match (ext_descriptor, int_descriptor) {
+            (Some(ext_descriptor), Some(int_descriptor)) => {
+                let wallet = Wallet::create(ext_descriptor, int_descriptor)
+                    .network(network)
+                    .create_wallet(persister)
+                    .map_err(|_| Error::Generic("Can't create wallet.".to_string()))?;
+                Ok(wallet)
+            }
+            (Some(ext_descriptor), None) => {
+                let wallet = Wallet::create_single(ext_descriptor)
+                    .network(network)
+                    .create_wallet(persister)
+                    .map_err(|_| Error::Generic("Can't create wallet.".to_string()))?;
+                Ok(wallet)
+            }
+            _ => Err(Error::Generic(
+                "An external descriptor is required.".to_string(),
+            )),
+        }?,
+    };
 
     Ok(wallet)
 }
 
-/// Add hardware wallets as signers to the wallet
-#[cfg(feature = "hardware-signer")]
-fn add_hardware_signers(
-    wallet: PersistedWallet<Connection>,
-    network: Network,
-) -> Result<PersistedWallet<Connection>, Error> {
-    let mut wallet = wallet;
-    let chain = HWIChain::from(network);
-    let devices = HWIClient::enumerate().map_err(|e| Error::Generic(e.to_string()))?;
-    for device in devices {
-        let device = device.map_err(|e| Error::Generic(e.to_string()))?;
-        // Creating a custom signer from the device
-        let custom_signer = HWISigner::from_device(&device, chain.clone())
-            .map_err(|e| Error::Generic(e.to_string()))?;
-
-        // Adding the hardware signer to the BDK wallet
-        wallet.add_signer(
-            KeychainKind::External,
-            SignerOrdering(200),
-            Arc::new(custom_signer),
-        );
-        println!("Added {} as a signer to the wallet.", device.model);
+#[cfg(not(any(feature = "sqlite",)))]
+/// Create a new non-persisted wallet from given wallet configuration options.
+pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result<Wallet, Error> {
+    let ext_descriptor = wallet_opts.ext_descriptor.clone();
+    let int_descriptor = wallet_opts.int_descriptor.clone();
+
+    match (ext_descriptor, int_descriptor) {
+        (Some(ext_descriptor), Some(int_descriptor)) => {
+            let wallet = Wallet::create(ext_descriptor, int_descriptor)
+                .network(network)
+                .create_wallet_no_persist()?;
+            Ok(wallet)
+        }
+        (Some(ext_descriptor), None) => {
+            let wallet = Wallet::create_single(ext_descriptor)
+                .network(network)
+                .create_wallet_no_persist()?;
+            Ok(wallet)
+        }
+        _ => Err(Error::Generic(
+            "An external descriptor is required.".to_string(),
+        )),
     }
-
-    Ok(wallet)
 }
diff --git a/src/wasm.rs b/src/wasm.rs
deleted file mode 100644 (file)
index 7bc0934..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-use crate::commands::*;
-use crate::error::BDKCliError as Error;
-use crate::handlers::*;
-use crate::nodes::Nodes;
-use crate::utils::*;
-use bdk_wallet::*;
-
-use bitcoin::*;
-
-use bdk_wallet::miniscript::{MiniscriptKey, Translator};
-use clap::Parser;
-use js_sys::Promise;
-use regex::Regex;
-use std::collections::HashMap;
-use std::ops::Deref;
-use std::path::PathBuf;
-use std::rc::Rc;
-use std::str::FromStr;
-use wasm_bindgen::prelude::*;
-use wasm_bindgen_futures::future_to_promise;
-
-#[cfg(feature = "compiler")]
-use bdk_wallet::keys::{GeneratableDefaultOptions, GeneratedKey};
-#[cfg(feature = "compiler")]
-use bdk_wallet::miniscript::{self, policy::Concrete, Descriptor, TranslatePk};
-#[cfg(feature = "compiler")]
-use serde::Deserialize;
-
-#[wasm_bindgen]
-pub struct WasmWallet {
-    wallet: Rc<Wallet>,
-    wallet_opts: Rc<WalletOpts>,
-    blockchain: Rc<AnyBlockchain>,
-    network: Network,
-}
-
-#[wasm_bindgen]
-pub fn log_init() {
-    wasm_logger::init(wasm_logger::Config::default());
-}
-
-#[wasm_bindgen]
-impl WasmWallet {
-    #[wasm_bindgen(constructor)]
-    pub fn new(network: String, wallet_opts: Vec<JsValue>) -> Result<WasmWallet, Error> {
-        fn new_inner(network: String, wallet_opts: Vec<JsValue>) -> Result<WasmWallet, Error> {
-            // Both open_database and new_blockchain need a home path to be passed
-            // in, even tho it won't be used
-            let dummy_home_dir = PathBuf::new();
-            let wallet_opts = wallet_opts
-                .into_iter()
-                .map(|a| a.as_string().expect("Invalid type"));
-            let wallet_opts: WalletOpts = WalletOpts::from_iter_safe(wallet_opts)?;
-            let network = Network::from_str(&network)?;
-            let wallet_opts = maybe_descriptor_wallet_name(wallet_opts, network)?;
-            let database = open_database(&wallet_opts, &dummy_home_dir)?;
-            let wallet = new_wallet(network, &wallet_opts, database)?;
-            let blockchain = new_blockchain(network, &wallet_opts, &Nodes::None, &dummy_home_dir)?;
-            Ok(WasmWallet {
-                wallet: Rc::new(wallet),
-                wallet_opts: Rc::new(wallet_opts),
-                blockchain: Rc::new(blockchain),
-                network,
-            })
-        }
-
-        new_inner(network, wallet_opts).map_err(|e| e.to_string().into())
-    }
-
-    pub fn run_command(&self, command: String) -> Promise {
-        let wallet = Rc::clone(&self.wallet);
-        let wallet_opts = Rc::clone(&self.wallet_opts);
-        let blockchain = Rc::clone(&self.blockchain);
-        let network = self.network;
-
-        async fn run_command_inner(
-            command: String,
-            wallet: Rc<Wallet>,
-            wallet_opts: Rc<WalletOpts>,
-            blockchain: Rc<AnyBlockchain>,
-            network: Network,
-        ) -> Result<serde_json::Value, Error> {
-            let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX)?;
-            let split_line: Vec<&str> = split_regex
-                .captures_iter(&command)
-                .map(|c| {
-                    Ok(c.get(1)
-                        .or_else(|| c.get(2))
-                        .or_else(|| c.get(3))
-                        .ok_or_else(|| "Invalid commands".to_string())?
-                        .as_str())
-                })
-                .collect::<Result<Vec<_>, String>>()?;
-            let repl_subcommand = ReplSubCommand::from_iter_safe(split_line)?;
-            log::debug!("repl_subcommand = {:?}", repl_subcommand);
-
-            let result = match repl_subcommand {
-                ReplSubCommand::Wallet {
-                    subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
-                } => {
-                    handle_online_wallet_subcommand(&wallet, blockchain.deref(), online_subcommand)
-                        .await?
-                }
-                ReplSubCommand::Wallet {
-                    subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
-                } => handle_offline_wallet_subcommand(&wallet, &wallet_opts, offline_subcommand)?,
-                ReplSubCommand::Key { subcommand } => handle_key_subcommand(network, subcommand)?,
-                ReplSubCommand::Exit => return Ok(serde_json::Value::Null),
-            };
-
-            Ok(result)
-        }
-
-        future_to_promise(async move {
-            run_command_inner(command, wallet, wallet_opts, blockchain, network)
-                .await
-                .map(|v| JsValue::from_serde(&v).expect("Serde serialization failed"))
-                .map_err(|e| e.to_string().into())
-        })
-    }
-}
-
-#[cfg(feature = "compiler")]
-struct AliasMap {
-    inner: HashMap<String, Alias>,
-}
-
-#[cfg(feature = "compiler")]
-impl Translator<String, String, Error> for AliasMap {
-    // Provides the translation public keys P -> Q
-    fn pk(&mut self, pk: &String) -> Result<String, Error> {
-        self.inner
-            .get(pk)
-            .map(|a| a.into_key())
-            .ok_or(Error::Generic("Couldn't map alias".to_string())) // Dummy Err
-    }
-
-    fn sha256(&mut self, sha256: &String) -> Result<String, Error> {
-        Ok(sha256.to_string())
-    }
-
-    fn hash256(&mut self, hash256: &String) -> Result<String, Error> {
-        Ok(hash256.to_string())
-    }
-
-    fn ripemd160(&mut self, ripemd160: &String) -> Result<String, Error> {
-        Ok(ripemd160.to_string())
-    }
-
-    fn hash160(&mut self, hash160: &String) -> Result<String, Error> {
-        Ok(hash160.to_string())
-    }
-}
-
-#[wasm_bindgen]
-#[cfg(feature = "compiler")]
-pub fn compile(policy: String, aliases: String, script_type: String) -> Result<JsValue, Error> {
-    fn compile_inner(
-        policy: String,
-        aliases: String,
-        script_type: String,
-    ) -> Result<String, Error> {
-        use std::collections::HashMap;
-        let aliases: HashMap<String, Alias> = serde_json::from_str(&aliases)?;
-        let mut aliases = AliasMap { inner: aliases };
-
-        let policy = Concrete::<String>::from_str(&policy)?;
-
-        let descriptor = match script_type.as_str() {
-            "sh" => Descriptor::new_sh(policy.compile()?)?,
-            "wsh" => Descriptor::new_wsh(policy.compile()?)?,
-            "sh-wsh" => Descriptor::new_sh_wsh(policy.compile()?)?,
-            _ => return Err(Error::Generic("InvalidScriptType".to_string())),
-        };
-
-        let descriptor: Result<Descriptor<String>, Error> = descriptor.translate_pk(&mut aliases);
-        let descriptor = descriptor?;
-
-        Ok(descriptor.to_string().into())
-    }
-
-    compile_inner(policy, aliases, script_type)
-        .map(|v| JsValue::from_serde(&v).expect("Serde serialization failed"))
-        .map_err(|e| e.to_string().into())
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(tag = "type", rename_all = "snake_case")]
-#[cfg(feature = "compiler")]
-enum Alias {
-    GenWif,
-    GenExt { extra: String },
-    Existing { extra: String },
-}
-
-#[cfg(feature = "compiler")]
-impl Alias {
-    fn into_key(&self) -> String {
-        match self {
-            Alias::GenWif => {
-                let generated: GeneratedKey<PrivateKey, miniscript::Legacy> =
-                    GeneratableDefaultOptions::generate_default().unwrap();
-
-                let mut key = generated.into_key();
-                key.network = Network::Testnet;
-
-                key.to_wif()
-            }
-            Alias::GenExt { extra: path } => {
-                let generated: GeneratedKey<bitcoin::bip32::Xpriv, miniscript::Legacy> =
-                    GeneratableDefaultOptions::generate_default().unwrap();
-
-                let mut xprv = generated.into_key();
-                xprv.network = Network::Testnet;
-
-                format!("{}{}", xprv, path)
-            }
-            Alias::Existing { extra } => extra.to_string(),
-        }
-    }
-}
index 01d768394430be46528741cb865eb46c41de0182..3faaf1f8f42bf65f1d2c569baea495457c89c49e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
 //
 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license