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
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
- 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
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"
]
[[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]]
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"
checksum = "4955734f97b2baed3f36d16ae7c203fdde31ae85391ac44ee3cbcaf0886db5ce"
dependencies = [
"bdk_core",
- "bitcoin 0.32.5",
- "miniscript 12.3.0",
+ "bitcoin",
+ "miniscript",
"rusqlite",
"serde",
]
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",
[[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]]
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"
"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]]
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",
]
"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"
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"
"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"
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]]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a"
dependencies = [
- "bitcoin 0.32.5",
+ "bitcoin",
"serde",
"serde_json",
]
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"
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",
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"
[[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"
]
[[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"
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]]
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
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",
]
[[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"
"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]]
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"
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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
"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"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
- "js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
- "wasm-bindgen",
]
[[package]]
"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"
[[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"
]
[[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"
[[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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
"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"
"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"
[[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",
]
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
- "autocfg 1.4.0",
+ "autocfg",
"scopeguard",
]
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"
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",
]
[[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]]
"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"
]
[[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",
]
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",
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
[[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",
]
[[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"
"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]]
[[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]]
[[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",
]
[[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",
]
[[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"
"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"
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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
]
[[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"
"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"
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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
"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",
"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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
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"
[[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]]
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"
"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"
[[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",
[[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",
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"
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"
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"
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"
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"
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"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
"synstructure",
]
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
]
[[package]]
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.98",
+ "syn",
"synstructure",
]
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",
]
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"]
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
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
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:
## 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/)
+++ /dev/null
-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
- )
- }
-}
-msrv="1.57.0"
+msrv="1.75.0"
-// 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.
/// 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.
/// 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.
#[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")]
/// 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.
#[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,
#[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,
}
#[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.
#[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.
/// 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.
/// 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
+// );
+// }
+// }
+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),
#[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),
#[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),
}
-// 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
///
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!({
}))
}
}
- 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| {
Ok(serde_json::to_value(transactions)?)
}
- GetBalance => Ok(json!({"satoshi": wallet.balance()})),
+ Balance => Ok(json!({"satoshi": wallet.balance()})),
CreateTx {
recipients,
send_all,
}
if let Some(utxos) = utxos {
- tx_builder.add_utxos(&utxos[..]);
+ tx_builder.add_utxos(&utxos[..]).unwrap();
}
if let Some(unspendable) = unspendable {
}
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();
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(
}
if let Some(utxos) = utxos {
- tx_builder.add_utxos(&utxos[..]);
+ tx_builder.add_utxos(&utxos[..]).unwrap();
}
if let Some(unspendable) = unspendable {
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)?;
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,
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),}))
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 {
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>>()?;
Ok(acc)
},
)?;
- Ok(json!({ "psbt": final_psbt.to_string() }))
+ Ok(json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }))
}
}
}
/// 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 }))
}
}
}
#[cfg(any(
feature = "electrum",
feature = "esplora",
- feature = "compact_filters",
+ feature = "cbf",
feature = "rpc"
))]
pub(crate) fn is_final(psbt: &Psbt) -> Result<(), Error> {
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))
- }
}
}
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")]
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)]
-// 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() {}
+++ /dev/null
-// 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),
- }
- }
-}
-// 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
//! 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> {
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();
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)?)
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.
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(),
);
});
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()))?;
}
}
/// 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")]
#[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)
}
+++ /dev/null
-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(),
- }
- }
-}
-// 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