Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog info is also documented on the [GitHub releases](https://github.com/bi
page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.

## [Unreleased]
- Fixed `create_tx --send_all` to reject multiple recipients instead of silently using only the first one

## [3.0.0]

Expand Down
12 changes: 9 additions & 3 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,12 @@ pub fn handle_offline_wallet_subcommand(
tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
} else {
return Err(Error::Generic(
"Wallet can only be drain to a single output".to_string(),
"Wallet can only be drained to a single output".to_string(),
));
}
} else {
return Err(Error::Generic(
"Wallet can only be drain to a single output".to_string(),
"Wallet can only be drained to a single output".to_string(),
));
}
} else {
Expand Down Expand Up @@ -540,7 +540,13 @@ pub fn handle_offline_wallet_subcommand(
let mut tx_builder = wallet.build_tx();

if send_all {
tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
if let [recipient] = recipients.as_slice() {
tx_builder.drain_wallet().drain_to(recipient.0.clone());
} else {
return Err(Error::Generic(
"Wallet can only be drained to a single output".to_string(),
));
}
} else {
let recipients = recipients
.into_iter()
Expand Down
89 changes: 89 additions & 0 deletions tests/send_all_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::process::Command;

fn run_bdk_cli(args: &str) -> (bool, String, String) {
let output = Command::new("cargo")
.args(format!("run --quiet -- {}", args).split_whitespace())
.output()
.expect("Failed to execute command");

let success = output.status.success();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();

(success, stdout, stderr)
}

#[test]
fn test_send_all_rejects_multiple_recipients() {
let test_dir = std::env::temp_dir().join("bdk-cli-test-send-all");
let _ = std::fs::remove_dir_all(&test_dir);
std::fs::create_dir_all(&test_dir).unwrap();
let datadir = test_dir.to_str().unwrap();

// Step 1: Generate a key
let (success, stdout, stderr) = run_bdk_cli("key generate");
assert!(success, "Step 1 failed - Key generation: {}", stderr);

let key_json: serde_json::Value =
serde_json::from_str(&stdout).expect("Failed to parse key output");
let xprv = key_json["xprv"].as_str().expect("No xprv in output");

// Step 2: Derive receive descriptor
let (success, stdout, stderr) =
run_bdk_cli(&format!("key derive --path m/84h/1h/0h/0 --xprv {}", xprv));
assert!(success, "Step 2 failed - Derive receive: {}", stderr);

let recv_json: serde_json::Value =
serde_json::from_str(&stdout).expect("Failed to parse derive output");
let recv_xprv = recv_json["xprv"]
.as_str()
.expect("No xprv in derive output");
let recv_desc = format!("wpkh({})", recv_xprv);

// Step 3: Derive change descriptor
let (success, stdout, stderr) =
run_bdk_cli(&format!("key derive --path m/84h/1h/0h/1 --xprv {}", xprv));
assert!(success, "Step 3 failed - Derive change: {}", stderr);

let change_json: serde_json::Value =
serde_json::from_str(&stdout).expect("Failed to parse derive output");
let change_xprv = change_json["xprv"]
.as_str()
.expect("No xprv in derive output");
let change_desc = format!("wpkh({})", change_xprv);

// Step 4: Save wallet config
let (success, _, stderr) = run_bdk_cli(&format!(
"--datadir {} wallet --wallet test config --database-type sqlite -e {} -i {}",
datadir, recv_desc, change_desc
));
assert!(success, "Step 4 failed - Wallet config: {}", stderr);

// Step 5: Try create_tx with --send_all and MULTIPLE recipients
let addr1 = "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn";
let addr2 = "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc";

let (success, stdout, stderr) = run_bdk_cli(&format!(
"--datadir {} wallet --wallet test create_tx --send_all \
--to {}:0 \
--to {}:0",
datadir, addr1, addr2
));

assert!(
!success,
"create_tx with --send_all and multiple recipients should fail, but it succeeded.\nstdout: {}\nstderr: {}",
stdout, stderr
);

// Check that error message mentions single output
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("drained to a single output"),
"Expected error message about single output, got: {}",
combined
);

// Cleanup
let _ = std::fs::remove_dir_all(&test_dir);
}