source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+[[package]]
+name = "assert_cmd"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
+dependencies = [
+ "anstyle",
+ "bstr",
+ "libc",
+ "predicates",
+ "predicates-core",
+ "predicates-tree",
+ "wait-timeout",
+]
+
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
[[package]]
name = "aws-lc-rs"
version = "1.17.1"
name = "bdk-cli"
version = "3.0.0"
dependencies = [
+ "assert_cmd",
"bdk_bip322",
"bdk_bitcoind_rpc",
"bdk_electrum",
"env_logger",
"log",
"payjoin",
+ "predicates",
"reqwest 0.13.4",
"serde",
"serde_json",
"shlex 1.3.0",
"tap",
+ "tempfile",
"thiserror 2.0.18",
"tokio",
"toml 1.1.2+spec-1.1.0",
"generic-array",
]
+[[package]]
+name = "bstr"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde_core",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.3"
"thiserror 2.0.18",
]
+[[package]]
+name = "difflib"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
+
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+[[package]]
+name = "float-cmp"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "foreign-types"
version = "0.3.2"
"tempfile",
]
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
"windows-sys 0.61.2",
]
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "once_cell"
version = "1.21.4"
"zerocopy",
]
+[[package]]
+name = "predicates"
+version = "3.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
+dependencies = [
+ "anstyle",
+ "difflib",
+ "float-cmp",
+ "normalize-line-endings",
+ "predicates-core",
+ "regex",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
+dependencies = [
+ "predicates-core",
+ "termtree",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
"winapi-util",
]
+[[package]]
+name = "termtree"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
+
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "walkdir"
version = "2.5.0"
--- /dev/null
+// Copyright (c) 2020-2026 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.
+
+//! bdk-cli Integration Test Framework
+//!
+//! This modules performs the necessary integration test for bdk-cli
+//! The tests can be run using `cargo test`
+
+mod common;
+
+// --- KEY COMMAND TESTS ---
+mod test_key {
+ use crate::common::BdkCli;
+ use predicates::prelude::*;
+ use serde_json::Value;
+
+ #[test]
+ fn test_cli_key_generate() {
+ // compile binary
+ let cli = BdkCli::new("testnet", None);
+
+ cli.key_cmd(&["generate"])
+ .assert()
+ .success()
+ .stdout(predicate::str::contains("\"xprv\":"))
+ .stdout(predicate::str::contains("\"mnemonic\":"))
+ .stdout(predicate::str::contains("\"fingerprint\":"));
+ }
+
+ #[test]
+ fn test_cli_key_derive() {
+ let cli = BdkCli::new("testnet", None);
+
+ let generate_output = cli
+ .key_cmd(&["generate"])
+ .output()
+ .expect("Failed to execute generate command");
+ assert!(generate_output.status.success(), "Generate command failed");
+
+ let generate_json: Value =
+ serde_json::from_slice(&generate_output.stdout).expect("Invalid JSON");
+ let xprv = generate_json["xprv"].as_str().expect("Missing XPRV");
+
+ let mut cmd = cli.key_cmd(&[
+ "derive",
+ "--xprv",
+ xprv,
+ "--derivation_path",
+ "m/84'/1'/0'/0",
+ ]);
+
+ cmd.assert()
+ .success()
+ .stdout(predicate::str::contains("\"xprv\":"))
+ .stdout(predicate::str::contains("\"xpub\":"));
+ }
+
+ #[test]
+ fn test_cli_key_restore() {
+ // Generate key
+ let cli = BdkCli::new("testnet", None);
+
+ // Execute the command and capture the output
+ let generate_cmd = cli
+ .key_cmd(&["generate"])
+ .output()
+ .expect("Failed to execute generate command");
+ assert!(generate_cmd.status.success(), "Generate command failed");
+
+ // Parse the JSON to extract the mnemonic
+ let generate_json: Value =
+ serde_json::from_slice(&generate_cmd.stdout).expect("Failed to parse JSON");
+
+ let mnemonic = generate_json["mnemonic"]
+ .as_str()
+ .expect("Mnemonic missing");
+ let xprv = generate_json["xprv"].as_str().expect("XPRV missing");
+ let finger_print = generate_json["fingerprint"]
+ .as_str()
+ .expect("Fingerprint missing");
+
+ // Restore using the mnemonic
+ let output_restore = cli.key_cmd(&["restore", "--mnemonic", mnemonic])
+ .output()
+ .expect("Failed to execute restore command");
+ assert!(output_restore.status.success(), "Restore command failed");
+
+ // Parse the JSON from the restore command
+ let restore_json: Value =
+ serde_json::from_slice(&output_restore.stdout).expect("Failed to parse JSON");
+
+ let restored_xprv = restore_json["xprv"]
+ .as_str()
+ .expect("Restored XPRV missing");
+ let restored_fingerprint = restore_json["fingerprint"]
+ .as_str()
+ .expect("Restored fingerprint missing");
+
+ // Assert that the restored data exactly matches the generated data
+ assert_eq!(
+ xprv, restored_xprv,
+ "The restored XPRV does not match the generated XPRV!"
+ );
+
+ assert_eq!(
+ finger_print, restored_fingerprint,
+ "The restored fingerprint does not match the generated fingerprint!"
+ );
+ }
+}
--- /dev/null
+// Copyright (c) 2020-2026 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.
+
+//! bdk-cli Integration Test Framework
+//!
+//! This module provides the necessary helper methods for tests
+//!
+use assert_cmd::Command;
+use std::path::PathBuf;
+
+/// The bdk-cli command struct
+#[allow(dead_code)]
+#[derive(Debug)]
+pub struct BdkCli {
+ pub network: String,
+ pub datadir: Option<PathBuf>,
+ pub verbosity: bool,
+ pub recv_desc: Option<String>,
+ pub change_desc: Option<String>,
+}
+
+impl BdkCli {
+ /// Construct a new test environment configuration
+ pub fn new(network: &str, datadir: Option<PathBuf>) -> Self {
+ Self {
+ network: network.to_string(),
+ datadir,
+ verbosity: false,
+ recv_desc: None,
+ change_desc: None,
+ }
+ }
+
+ /// Creates the base assert_cmd::Command with the global flags pre-loaded
+ fn build_base_cmd(&self) -> Command {
+ let mut cmd = Command::cargo_bin("bdk-cli").expect("bdk-cli binary must compile");
+
+ cmd.arg("--network").arg(&self.network);
+
+ if let Some(dir) = &self.datadir {
+ cmd.arg("--datadir").arg(dir);
+ }
+
+ if self.verbosity {
+ cmd.arg("--verbose");
+ }
+
+ cmd
+ }
+
+ /// Returns a pre-configured Command builder for `key` operations
+ pub fn key_cmd(&self, args: &[&str]) -> Command {
+ let mut cmd = self.build_base_cmd();
+ cmd.arg("key");
+ cmd.args(args);
+ cmd
+ }
+
+ /// Returns a pre-configured Command builder for `wallet` operations
+ pub fn wallet_cmd(&self, args: &[&str]) -> Command {
+ let mut cmd = self.build_base_cmd();
+ cmd.arg("wallet");
+
+ // Automatically inject descriptors if they are set in the helper state
+ if let Some(recv) = &self.recv_desc {
+ cmd.arg("--descriptor").arg(recv);
+ }
+
+ cmd.args(args);
+ cmd
+ }
+}
+++ /dev/null
-// 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
-// <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.
-
-//! bdk-cli Integration Test Framework
-//!
-//! This modules performs the necessary integration test for bdk-cli
-//! The tests can be run using `cargo test`
-
-#[cfg(feature = "rpc")]
-mod test {
- use serde_json::{Value, json};
- use std::convert::From;
- use std::env::temp_dir;
- use std::path::PathBuf;
- use std::process::Command;
-
- /// Testing errors for integration tests
- #[allow(dead_code)]
- #[derive(Debug)]
- enum IntTestError {
- // IO error
- IO(std::io::Error),
- // Command execution error
- CmdExec(String),
- // Json Data error
- JsonData(String),
- }
-
- impl From<std::io::Error> for IntTestError {
- fn from(e: std::io::Error) -> Self {
- IntTestError::IO(e)
- }
- }
-
- // Helper function
- // Runs a system command with given args
- #[allow(dead_code)]
- fn run_cmd_with_args(cmd: &str, args: &[&str]) -> Result<serde_json::Value, IntTestError> {
- let output = Command::new(cmd).args(args).output().unwrap();
- let mut value = output.stdout;
- let error = output.stderr;
- if value.is_empty() {
- return Err(IntTestError::CmdExec(String::from_utf8(error).unwrap()));
- }
- value.pop(); // remove `\n` at end
- let output_string = std::str::from_utf8(&value).unwrap();
- let json_value: serde_json::Value = match serde_json::from_str(output_string) {
- Ok(value) => value,
- Err(_) => json!(output_string), // bitcoin-cli will sometime return raw string
- };
- Ok(json_value)
- }
-
- // Helper Function
- // Transforms a json value to string
- #[allow(dead_code)]
- fn value_to_string(value: &Value) -> Result<String, IntTestError> {
- match value {
- Value::Bool(bool) => match bool {
- true => Ok("true".to_string()),
- false => Ok("false".to_string()),
- },
- Value::Number(n) => Ok(n.to_string()),
- Value::String(s) => Ok(s.to_string()),
- _ => Err(IntTestError::JsonData(
- "Value parsing not implemented for this type".to_string(),
- )),
- }
- }
-
- // Helper Function
- // Extracts value from a given json object and key
- #[allow(dead_code)]
- fn get_value(json: &Value, key: &str) -> Result<String, IntTestError> {
- let map = json
- .as_object()
- .ok_or(IntTestError::JsonData("Json is not an object".to_string()))?;
- let value = map
- .get(key)
- .ok_or(IntTestError::JsonData("Invalid key".to_string()))?
- .to_owned();
- let string_value = value_to_string(&value)?;
- Ok(string_value)
- }
-
- /// The bdk-cli command struct
- /// Use it to perform all bdk-cli operations
- #[allow(dead_code)]
- #[derive(Debug)]
- struct BdkCli {
- target: String,
- network: String,
- verbosity: bool,
- recv_desc: Option<String>,
- chang_desc: Option<String>,
- node_datadir: Option<PathBuf>,
- }
-
- impl BdkCli {
- /// Construct a new [`BdkCli`] struct
- fn new(
- network: &str,
- node_datadir: Option<PathBuf>,
- verbosity: bool,
- features: &[&str],
- ) -> Result<Self, IntTestError> {
- // Build bdk-cli with given features
- let mut feat = "--features=".to_string();
- for item in features {
- feat.push_str(item);
- feat.push(',');
- }
- feat.pop(); // remove the last comma
- let _build = Command::new("cargo").args(["build", &feat]).output()?;
-
- let mut bdk_cli = Self {
- target: "./target/debug/bdk-cli".to_string(),
- network: network.to_string(),
- verbosity,
- recv_desc: None,
- chang_desc: None,
- node_datadir,
- };
-
- println!("BDK-CLI Config : {bdk_cli:#?}");
- let bdk_master_key = bdk_cli.key_exec(&["generate"])?;
- let bdk_xprv = get_value(&bdk_master_key, "xprv")?;
-
- let bdk_recv_desc =
- bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/0", "--xprv", &bdk_xprv])?;
- let bdk_recv_desc = get_value(&bdk_recv_desc, "xprv")?;
- let bdk_recv_desc = format!("wpkh({bdk_recv_desc})");
-
- let bdk_chng_desc =
- bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/1", "--xprv", &bdk_xprv])?;
- let bdk_chng_desc = get_value(&bdk_chng_desc, "xprv")?;
- let bdk_chng_desc = format!("wpkh({bdk_chng_desc})");
-
- bdk_cli.recv_desc = Some(bdk_recv_desc);
- bdk_cli.chang_desc = Some(bdk_chng_desc);
-
- Ok(bdk_cli)
- }
-
- /// Execute bdk-cli wallet commands with given args
- fn wallet_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
- // Check if data directory is specified
- let mut wallet_args = if let Some(datadir) = &self.node_datadir {
- let datadir = datadir.as_os_str().to_str().unwrap();
- ["--network", &self.network, "--datadir", datadir, "wallet"].to_vec()
- } else {
- ["--network", &self.network, "wallet"].to_vec()
- };
-
- if self.verbosity {
- wallet_args.push("-v");
- }
-
- wallet_args.push("-d");
- wallet_args.push(self.recv_desc.as_ref().unwrap());
- wallet_args.push("-c");
- wallet_args.push(self.chang_desc.as_ref().unwrap());
-
- for arg in args {
- wallet_args.push(arg);
- }
- run_cmd_with_args(&self.target, &wallet_args)
- }
-
- /// Execute bdk-cli key commands with given args
- fn key_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
- let mut key_args = ["key"].to_vec();
- for arg in args {
- key_args.push(arg);
- }
- run_cmd_with_args(&self.target, &key_args)
- }
-
- /// Execute bdk-cli node command
- fn node_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
- // Check if data directory is specified
- let mut node_args = if let Some(datadir) = &self.node_datadir {
- let datadir = datadir.as_os_str().to_str().unwrap();
- ["--network", &self.network, "--datadir", datadir, "node"].to_vec()
- } else {
- ["--network", &self.network, "node"].to_vec()
- };
-
- for arg in args {
- node_args.push(arg);
- }
- run_cmd_with_args(&self.target, &node_args)
- }
- }
-
- // Run A Basic wallet operation test, with given feature
- #[cfg(test)]
- #[allow(dead_code)]
- fn basic_wallet_ops(feature: &str) {
- // Create a temporary directory for testing env
- let mut test_dir = std::env::current_dir().unwrap();
- test_dir.push("bdk-testing");
-
- let test_dir = temp_dir();
- // let test_dir = test_temp_dir.into_path().to_path_buf();
-
- // Create bdk-cli instance
- let bdk_cli = BdkCli::new("regtest", Some(test_dir), false, &[feature]).unwrap();
-
- // Generate 101 blocks
- bdk_cli.node_exec(&["generate", "101"]).unwrap();
-
- // Get a bdk address
- let bdk_addr_json = bdk_cli.wallet_exec(&["get_new_address"]).unwrap();
- let bdk_addr = get_value(&bdk_addr_json, "address").unwrap();
-
- // Send coins from core to bdk
- bdk_cli
- .node_exec(&["sendtoaddress", &bdk_addr, "1000000000"])
- .unwrap();
-
- bdk_cli.node_exec(&["generate", "1"]).unwrap();
-
- // Sync the bdk wallet
- bdk_cli.wallet_exec(&["sync"]).unwrap();
-
- // Get the balance
- let balance_json = bdk_cli.wallet_exec(&["get_balance"]).unwrap();
- let confirmed_balance = balance_json
- .as_object()
- .unwrap()
- .get("satoshi")
- .unwrap()
- .as_object()
- .unwrap()
- .get("confirmed")
- .unwrap()
- .as_u64()
- .unwrap();
- assert_eq!(confirmed_balance, 1000000000u64);
- }
-}