]> Untitled Git - bdk-cli/commitdiff
fix(cli): use valid envt variable names for env fallbacks
authorVihiga Tyonum <withtvpeter@gmail.com>
Fri, 2 Oct 2026 05:14:16 +0000 (06:14 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 2 Oct 2026 05:14:16 +0000 (06:14 +0100)
Several `#[arg(env = "...")]` attributes mistakenly used colons (e.g.,
`ADDRESS:SAT`) following value-shape documentation conventions. Because
colons are invalid in POSIX environment variable names, these fallbacks
could never actually be set by users.

Renamed these environment variables to use valid identifiers while
keeping the flag names and expected value shapes unchanged.

Also, format integration tests

src/commands.rs
src/handlers/dns/mod.rs
src/handlers/offline.rs
tests/common/mod.rs
tests/integration/init.rs
tests/integration/offline.rs
tests/integration/online.rs
tests/integration/proxy.rs

index 86826ffe1791dc2a4b34f5cc519527fcf0e70796..d921e7ce1632a9ef9252ffd791f24e13535df524 100644 (file)
@@ -271,7 +271,7 @@ pub struct WalletOpts {
     #[cfg(feature = "rpc")]
     /// Sets the rpc basic authentication.
     #[arg(
-        env = "USER:PASSWD",
+        env = "RPC_BASIC_AUTH",
         short = 'a',
         long,
         value_parser = parse_proxy_auth,
@@ -307,16 +307,17 @@ impl WalletOpts {
             )));
         }
         #[cfg(any(feature = "electrum", feature = "esplora"))]
-        if self.proxy_opts.proxy_auth.is_some() {
-            return Err(Error::Generic(format!(
-                "The {_backend} backend does not support --proxy_auth."
-            )));
-        }
-        #[cfg(any(feature = "electrum", feature = "esplora"))]
-        if self.proxy_opts.timeout.is_some() {
-            return Err(Error::Generic(format!(
-                "The {_backend} backend does not support --timeout."
-            )));
+        {
+            if self.proxy_opts.proxy_auth.is_some() {
+                return Err(Error::Generic(format!(
+                    "The {_backend} backend does not support --proxy_auth."
+                )));
+            }
+            if self.proxy_opts.timeout.is_some() {
+                return Err(Error::Generic(format!(
+                    "The {_backend} backend does not support --timeout."
+                )));
+            }
         }
         Ok(())
     }
@@ -330,16 +331,17 @@ impl WalletOpts {
     #[cfg(feature = "cbf")]
     pub(crate) fn reject_proxy_auth(&self, _backend: &str) -> Result<(), Error> {
         #[cfg(any(feature = "electrum", feature = "esplora"))]
-        if self.proxy_opts.proxy_auth.is_some() {
-            return Err(Error::Generic(format!(
-                "The {_backend} backend does not support --proxy_auth."
-            )));
-        }
-        #[cfg(any(feature = "electrum", feature = "esplora"))]
-        if self.proxy_opts.timeout.is_some() {
-            return Err(Error::Generic(format!(
-                "The {_backend} backend does not support --timeout."
-            )));
+        {
+            if self.proxy_opts.proxy_auth.is_some() {
+                return Err(Error::Generic(format!(
+                    "The {_backend} backend does not support --proxy_auth."
+                )));
+            }
+            if self.proxy_opts.timeout.is_some() {
+                return Err(Error::Generic(format!(
+                    "The {_backend} backend does not support --timeout."
+                )));
+            }
         }
         Ok(())
     }
@@ -350,12 +352,12 @@ impl WalletOpts {
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct ProxyOpts {
     /// Sets the SOCKS5 proxy for a blockchain client.
-    #[arg(env = "PROXY_ADDRS:PORT", long = "proxy")]
+    #[arg(env = "PROXY_ADDRS_PORT", long = "proxy")]
     pub proxy: Option<String>,
 
     /// Sets the SOCKS5 proxy credential.
     #[cfg(any(feature = "electrum", feature = "esplora"))]
-    #[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", value_parser = parse_proxy_auth)]
+    #[arg(env = "PROXY_USER_PASSWD", long="proxy_auth", value_parser = parse_proxy_auth)]
     pub proxy_auth: Option<(String, String)>,
 
     /// Sets the SOCKS5 proxy retries for the blockchain client.
index a57336906b87801078b375489d3dbc83201a3090..645672c11fa6d2f85745cc9d6b965ce8d7b2a574 100644 (file)
@@ -41,7 +41,7 @@ impl AsyncAppCommand<AppContext<Init>> for ResolveDnsRecipientCommand {
 /// Creates a new unsigned transaction from DNS payment instructions.
 #[derive(Parser, Debug, Clone, PartialEq)]
 pub struct CreateDnsTxCommand {
-    #[arg(env = "ADDRESS:SAT", long = "to", value_parser = parse_recipient)]
+    #[arg(env = "ADDRESS_SAT", long = "to", value_parser = parse_recipient)]
     pub recipients: Vec<(ScriptBuf, u64)>,
     #[arg(long = "to_dns", value_parser = parse_dns_recipient)]
     pub dns_recipients: Vec<(String, u64)>,
@@ -53,9 +53,9 @@ pub struct CreateDnsTxCommand {
     pub enable_rbf: bool,
     #[arg(long = "offline_signer")]
     pub offline_signer: bool,
-    #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+    #[arg(env = "MUST_SPEND_TXID_VOUT", long = "utxos", value_parser = parse_outpoint)]
     pub utxos: Option<Vec<OutPoint>>,
-    #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+    #[arg(env = "CANT_SPEND_TXID_VOUT", long = "unspendable", value_parser = parse_outpoint)]
     pub unspendable: Option<Vec<OutPoint>>,
     #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate", value_parser = parse_fee_rate)]
     pub fee_rate: Option<FeeRate>,
index 4b623b4948386e72ab9397720c663819182b57f3..15cdd26b2cb97001434154df4e18a892fe6a8dc6 100644 (file)
@@ -193,7 +193,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for BalanceCommand {
 #[derive(Debug, Parser, Clone, PartialEq)]
 pub struct CreateTxCommand {
     /// Adds a recipient to the transaction.
-    #[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
+    #[arg(env = "ADDRESS_SAT", long = "to", required = true, value_parser = parse_recipient)]
     pub recipients: Vec<(ScriptBuf, u64)>,
 
     /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
@@ -209,11 +209,11 @@ pub struct CreateTxCommand {
     pub offline_signer: bool,
 
     /// Selects which utxos *must* be spent.
-    #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+    #[arg(env = "MUST_SPEND_TXID_VOUT", long = "utxos", value_parser = parse_outpoint)]
     pub utxos: Option<Vec<OutPoint>>,
 
     /// Marks a utxo as unspendable.
-    #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+    #[arg(env = "CANT_SPEND_TXID_VOUT", long = "unspendable", value_parser = parse_outpoint)]
     pub unspendable: Option<Vec<OutPoint>>,
 
     /// Fee rate to use in sat/vbyte.
@@ -327,7 +327,7 @@ pub struct CreateSpTxCommand {
     /// 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.
-    #[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)]
+    #[arg(env = "ADDRESS_SAT", long = "to", required = false, value_parser = parse_recipient)]
     pub recipients: Option<Vec<(ScriptBuf, u64)>>,
     /// Parse silent payment recipients
     #[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)]
@@ -339,10 +339,10 @@ pub struct CreateSpTxCommand {
     #[arg(long = "offline_signer")]
     pub offline_signer: bool,
     /// Selects which utxos *must* be spent.
-    #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+    #[arg(env = "MUST_SPEND_TXID_VOUT", long = "utxos", value_parser = parse_outpoint)]
     pub utxos: Option<Vec<OutPoint>>,
     /// Marks a utxo as unspendable.
-    #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+    #[arg(env = "CANT_SPEND_TXID_VOUT", long = "unspendable", value_parser = parse_outpoint)]
     pub unspendable: Option<Vec<OutPoint>>,
     /// Fee rate to use in sat/vbyte.
     #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate", value_parser = parse_fee_rate)]
@@ -547,11 +547,11 @@ pub struct BumpFeeCommand {
     pub offline_signer: bool,
 
     /// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used.
-    #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+    #[arg(env = "MUST_SPEND_TXID_VOUT", long = "utxos", value_parser = parse_outpoint)]
     pub utxos: Option<Vec<OutPoint>>,
 
     /// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees.
-    #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+    #[arg(env = "CANT_SPEND_TXID_VOUT", long = "unspendable", value_parser = parse_outpoint)]
     pub unspendable: Option<Vec<OutPoint>>,
 
     /// The new targeted fee rate in sat/vbyte.
index 4322016671e39167b640d461e6f219392d7dae86..d38edf8b4a1d24ced7908726fdc72c708ee77d65 100644 (file)
@@ -40,14 +40,14 @@ impl BdkCli {
     pub 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);
+        cmd.args(["--network", &self.network]);
 
         if let Some(dir) = &self.datadir {
             cmd.arg("--datadir").arg(dir);
         }
 
         if let Some(url) = &self.server_url {
-            cmd.arg("--server").arg(url);
+            cmd.args(["--server", url]);
         }
 
         cmd
index 0a2af96f51bcdb451e69483b835e6f3d523119a7..4f57b4602bb4e7e6a3d679836682828dbdffc65e 100644 (file)
@@ -138,19 +138,13 @@ mod test_wallets {
 
             cli.build_base_cmd()
                 .arg("wallet")
-                .arg("--wallet")
-                .arg(wallet_name)
+                .args(["--wallet", wallet_name])
                 .arg("config")
-                .arg("--ext-descriptor")
-                .arg(ext_desc)
-                .arg("--int-descriptor")
-                .arg(int_desc)
-                .arg("--client-type")
-                .arg("rpc")
-                .arg("--database-type")
-                .arg("sqlite")
-                .arg("--url")
-                .arg("http://localhost:18443")
+                .args(["--ext-descriptor", ext_desc])
+                .args(["--int-descriptor", int_desc])
+                .args(["--client-type", "rpc"])
+                .args(["--database-type", "sqlite"])
+                .args(["--url", "http://localhost:18443"])
                 .assert()
                 .success();
         }
@@ -246,19 +240,13 @@ mod test_config {
         let mut cmd_init = cli.build_base_cmd();
         cmd_init
             .arg("wallet")
-            .arg("--wallet")
-            .arg(wallet_name)
+            .args(["--wallet", wallet_name])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext_desc)
-            .arg("--int-descriptor")
-            .arg(int_desc)
-            .arg("--client-type")
-            .arg(client_type)
-            .arg("--database-type")
-            .arg(db)
-            .arg("--url")
-            .arg(url);
+            .args(["--ext-descriptor", ext_desc])
+            .args(["--int-descriptor", int_desc])
+            .args(["--client-type", client_type])
+            .args(["--database-type", db])
+            .args(["--url", url]);
 
         cmd_init.assert().success();
 
@@ -310,19 +298,15 @@ mod test_config {
 
         cli.build_base_cmd()
             .arg("wallet")
-            .arg("--wallet")
-            .arg("secret_wallet")
+            .args(["--wallet", "secret_wallet"])
             .arg("config")
             .arg("--ext-descriptor")
             .arg(priv_desc["external"].as_str().unwrap())
             .arg("--int-descriptor")
             .arg(priv_desc["internal"].as_str().unwrap())
-            .arg("--client-type")
-            .arg("rpc")
-            .arg("--database-type")
-            .arg("sqlite")
-            .arg("--url")
-            .arg("http://localhost:18443")
+            .args(["--client-type", "rpc"])
+            .args(["--database-type", "sqlite"])
+            .args(["--url", "http://localhost:18443"])
             .assert()
             .success()
             .stderr(predicate::str::contains("PRIVATE KEYS"));
index 4b678eba06c23bee4d34395d6785e7989facc49c..417bf72b68b2c8bfe6e87c5e9297d850cd849018 100644 (file)
@@ -35,19 +35,13 @@ mod test_offline {
         let mut cmd_init = cli.build_base_cmd();
         cmd_init
             .arg("wallet")
-            .arg("--wallet")
-            .arg(WALLET_NAME)
+            .args(["--wallet", WALLET_NAME])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext_desc)
-            .arg("--int-descriptor")
-            .arg(int_desc)
-            .arg("--client-type")
-            .arg(client_type)
-            .arg("--database-type")
-            .arg(db)
-            .arg("--url")
-            .arg(url);
+            .args(["--ext-descriptor", ext_desc])
+            .args(["--int-descriptor", int_desc])
+            .args(["--client-type", client_type])
+            .args(["--database-type", db])
+            .args(["--url", url]);
         (cli, cmd_init)
     }
 
@@ -438,13 +432,10 @@ mod multipath_tests {
     fn save_config(cli: &BdkCli, wallet: &str, ext: &str, int: Option<&str>) -> Command {
         let mut cmd = cli.build_base_cmd();
         cmd.arg("wallet")
-            .arg("--wallet")
-            .arg(wallet)
+            .args(["--wallet", wallet])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext)
-            .arg("--database-type")
-            .arg("sqlite");
+            .args(["--ext-descriptor", ext])
+            .args(["--database-type", "sqlite"]);
         if let Some(int) = int {
             cmd.arg("--int-descriptor").arg(int);
         }
index b9dcc7b4a1efb31aed3a0b80dff5fec1f8e732ce..4d3f11afd44b75200b1ae75b3a5e1ac6326feabb 100644 (file)
@@ -33,19 +33,13 @@ mod test_online {
         let mut cmd_init = cli.build_base_cmd();
         cmd_init
             .arg("wallet")
-            .arg("--wallet")
-            .arg(WALLET_NAME)
+            .args(["--wallet", WALLET_NAME])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext_desc)
-            .arg("--int-descriptor")
-            .arg(int_desc)
-            .arg("--client-type")
-            .arg("electrum")
-            .arg("--database-type")
-            .arg("sqlite")
-            .arg("--url")
-            .arg(server_url);
+            .args(["--ext-descriptor", ext_desc])
+            .args(["--int-descriptor", int_desc])
+            .args(["--client-type", "electrum"])
+            .args(["--database-type", "sqlite"])
+            .args(["--url", server_url]);
 
         (cli, cmd_init, env)
     }
@@ -862,19 +856,13 @@ mod test_online {
             .unwrap();
         cli.build_base_cmd()
             .arg("wallet")
-            .arg("--wallet")
-            .arg(WALLET_NAME)
+            .args(["--wallet", WALLET_NAME])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext_desc)
-            .arg("--int-descriptor")
-            .arg(int_desc)
-            .arg("--client-type")
-            .arg("electrum")
-            .arg("--database-type")
-            .arg("sqlite")
-            .arg("--url")
-            .arg(server_url)
+            .args(["--ext-descriptor", ext_desc])
+            .args(["--int-descriptor", int_desc])
+            .args(["--client-type", "electrum"])
+            .args(["--database-type", "sqlite"])
+            .args(["--url", server_url])
             .assert()
             .success();
 
index 2d676b6fb4d8d041b1b7bb65341748e0abbfe14f..9b369468f99c36b849515a8ec74b5464e6927dfb 100644 (file)
@@ -10,14 +10,14 @@ mod test_proxy {
     #[cfg(any(feature = "rpc", feature = "cbf"))]
     use predicates::prelude::*;
     use serde_json::Value;
-    #[cfg(any(feature = "electrum", feature = "esplora"))]
-    use std::net::{TcpListener, TcpStream};
-    #[cfg(any(feature = "electrum", feature = "esplora"))]
-    use std::sync::mpsc::{Receiver, channel};
-    #[cfg(any(feature = "electrum", feature = "esplora"))]
-    use std::thread;
     use std::time::Duration;
     use tempfile::TempDir;
+    #[cfg(any(feature = "electrum", feature = "esplora"))]
+    use {
+        std::net::{TcpListener, TcpStream},
+        std::sync::mpsc::{Receiver, channel},
+        std::thread,
+    };
 
     static WALLET_NAME: &str = "proxy_test_wallet";
 
@@ -75,19 +75,13 @@ mod test_proxy {
 
         let mut cmd = cli.build_base_cmd();
         cmd.arg("wallet")
-            .arg("--wallet")
-            .arg(WALLET_NAME)
+            .args(["--wallet", WALLET_NAME])
             .arg("config")
-            .arg("--ext-descriptor")
-            .arg(ext_desc)
-            .arg("--int-descriptor")
-            .arg(int_desc)
-            .arg("--client-type")
-            .arg(client_type)
-            .arg("--database-type")
-            .arg("sqlite")
-            .arg("--url")
-            .arg(url);
+            .args(["--ext-descriptor", ext_desc])
+            .args(["--int-descriptor", int_desc])
+            .args(["--client-type", client_type])
+            .args(["--database-type", "sqlite"])
+            .args(["--url", url]);
         if let Some(proxy) = proxy_addr {
             cmd.arg("--proxy").arg(proxy);
         }
@@ -122,17 +116,14 @@ mod test_proxy {
 
         let mut cmd = cli.build_base_cmd();
         cmd.arg("wallet")
-            .arg("--wallet")
-            .arg(WALLET_NAME)
+            .args(["--wallet", WALLET_NAME])
             .arg("config")
             .arg("--ext-descriptor")
             .arg(public["external"].as_str().unwrap())
             .arg("--int-descriptor")
             .arg(public["internal"].as_str().unwrap())
-            .arg("--client-type")
-            .arg(client_type)
-            .arg("--database-type")
-            .arg("sqlite");
+            .args(["--client-type", client_type])
+            .args(["--database-type", "sqlite"]);
         // `--url` is required whenever electrum, esplora or rpc is built, no matter
         // which `--client-type` is chosen; it does not exist at all otherwise.
         #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]