]> Untitled Git - bdk-cli/commitdiff
feat(wallet-init): add `wallets` command
authorVihiga Tyonum <withtvpeter@gmail.com>
Sat, 4 Oct 2025 01:42:55 +0000 (02:42 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Mon, 19 Jan 2026 15:16:53 +0000 (16:16 +0100)
- add wallets command
- add warning for using priv descriptors
- update readme
- add loading network from config
- fix review comments

CHANGELOG.md
Cargo.lock
README.md
src/commands.rs
src/config.rs
src/handlers.rs
src/main.rs
src/utils.rs

index 48cc73006c2ac2c201555fb551af53e750f9c47e..55a95b77973f0dd61dbb495ca8e93e295d13d943 100644 (file)
@@ -4,8 +4,8 @@ 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]
-- Add wallet configs initialization for initialiazing and saving wallet configs
 - Add wallet subcommand `config` to save wallet configs
+- Add `wallets` command to list all wallets saved configs
 
 ## [2.0.0]
 
index 0cc6d4fae10a272cd08537d45c9c03b9ea837926..98036e68639b61c8ff72edd6859dbbef6b0f3208 100644 (file)
@@ -302,9 +302,9 @@ dependencies = [
 
 [[package]]
 name = "bdk_wallet"
-version = "2.3.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b03f1e31ccc562f600981f747d2262b84428cbff52c9c9cdf14d15fb15bd2286"
+checksum = "d30b5dba770184863b5d966ccbc6a11d12c145450be3b6a4435308297e6a12dc"
 dependencies = [
  "bdk_chain",
  "bip39",
index 456be5ebef50921832908db464175449e629a6a0..4526443e7845da1c413aec66baa60346e479fb90 100644 (file)
--- a/README.md
+++ b/README.md
@@ -216,41 +216,43 @@ cargo run --pretty -n signet wallet -w {wallet_name} -d sqlite balance
 ```
 This is available for wallet, key, repl and compile features. When ommitted, outputs default to `JSON`.
 
-## Initializing Wallet Configurations with `init` Subcommand
+## Saving and using wallet configurations
 
-The `wallet init` sub-command simplifies wallet operations by saving configuration parameters to `config.toml` in the data directory (default `~/.bdk-bitcoin/config.toml`). This allows you to run subsequent `bdk-cli wallet` commands without repeatedly specifying configuration details, easing wallet operations.
+The `wallet config` sub-command allows you to save wallet settings to a `config.toml` file in the default directory (`~/.bdk-bitcoin/`) or custom directory specified with the `--datadir` flag. This eliminate the need to repeatedly specify descriptors, client types, and other parameters for each command. Once configured, you can use any wallet command by simply specifying the wallet name. All other parameters are automatically loaded from the saved configuration.
 
-To initialize a wallet configuration, use the following command structure:
+To save a wallet settings:
 
 ```shell
-cargo run --features <list-of-features> -- -n <network> wallet --wallet <wallet_name> --ext-descriptor <ext_descriptor> --int-descriptor <int_descriptor>  --client-type <client_type>  --url <server_url> [--database-type <database_type>] [--rpc-user <rpc_user>]
-  [--rpc-password <rpc_password>] init
+cargo run --features <list-of-features> -- -n <network> wallet --wallet <wallet_name> config [ -f ] --ext-descriptor <ext_descriptor> --int-descriptor <int_descriptor>  --client-type <client_type>  --url <server_url> [--database-type <database_type>] [--rpc-user <rpc_user>]
+  [--rpc-password <rpc_password>] 
 ```
 
 For example, to initialize a wallet named `my_wallet` with `electrum` as the backend on `signet` network:
 
 ```shell
-cargo run --features electrum -- -n signet wallet -w my_wallet -e "tr(tprv8Z.../0/*)#dtdqk3dx" -i "tr(tprv8Z.../1/*)#ulgptya7" -d sqlite -c electrum -u "ssl://mempool.space:60602" init
+cargo run --features electrum -- -n signet wallet -w my_wallet config -e "tr(tprv8Z.../0/*)#dtdqk3dx" -i "tr(tprv8Z.../1/*)#ulgptya7" -d sqlite -c electrum -u "ssl://mempool.space:60602"
 ```
 
-To overwrite an existing wallet configuration, use the  `--force` flag after the `init` sub-command.
+To overwrite an existing wallet configuration, use the  `--force` flag after the `config` sub-command.
 
-You can omit the following arguments to use their default values:
+#### Using a Configured Wallet
 
-`network`: Defaults to `testnet`
+Once configured, use any wallet command with just the wallet name:
 
-`database_type`: Defaults to `sqlite`
-
-#### Using Saved Configuration
-
-After a wallet is initialized, you can then run `bdk-cli` wallet commands without specifying the parameters, referencing only the wallet subcommand.
-
-For example, with the wallet `my_wallet` initialized, generate a new address and sync the wallet as follow:
 
 ```shell
-cargo run wallet -w my_wallet --use-config new_address
+cargo run --features electrum wallet -w my_wallet new_address
 
-cargo run --features electrum wallet -w my_wallet --use-config sync
+cargo run --features electrum wallet -w my_wallet full_scan
 ```
 
 Note that each wallet has its own configuration, allowing multiple wallets with different configurations.
+
+#### View all saved Wallet Configs
+
+To view all saved wallet configurations:
+
+```shell
+cargo run wallets`
+```
+You can also use the `--pretty` flag for a formatted output.
index 403578504f9ea0ffb422a59506160f4df7c3afad..14ad9eaee13552fc37c28eb79e9a38651b0d5753 100644 (file)
@@ -108,9 +108,6 @@ pub enum CliSubCommand {
         /// Wallet name for this REPL session
         #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
         wallet: String,
-
-        #[command(flatten)]
-        wallet_opts: WalletOpts,
     },
     /// Output Descriptors operations.
     ///
@@ -128,6 +125,8 @@ pub enum CliSubCommand {
         /// Optional key: xprv, xpub, or mnemonic phrase
         key: Option<String>,
     },
+    /// List all saved wallet configurations.
+    Wallets,
 }
 /// Wallet operation subcommands.
 #[derive(Debug, Subcommand, Clone, PartialEq)]
@@ -187,7 +186,6 @@ pub struct WalletOpts {
     /// Selects the wallet to use.
     #[arg(skip)]
     pub wallet: Option<String>,
-    // #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
     /// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects.
     #[arg(env = "VERBOSE", short = 'v', long = "verbose")]
     pub verbose: bool,
index c6f5e806604e6d8905cde8f8f55b2519fcbdd68a..f024f0beb68c9eebb6dd4d5ec41316c49213a5a0 100644 (file)
@@ -90,6 +90,7 @@ impl WalletConfig {
             "testnet" => Network::Testnet,
             "regtest" => Network::Regtest,
             "signet" => Network::Signet,
+            "testnet4" => Network::Testnet4,
             _ => {
                 return Err(Error::Generic("Invalid network".to_string()));
             }
@@ -105,7 +106,6 @@ impl WalletConfig {
                 return Err(Error::Generic("Invalid database type".to_string()));
             }
         };
-
         #[cfg(any(
             feature = "electrum",
             feature = "esplora",
@@ -144,9 +144,9 @@ impl WalletConfig {
                 .clone()
                 .ok_or_else(|| Error::Generic(format!("Server url not found")))?,
             #[cfg(feature = "electrum")]
-            batch_size: 10,
+            batch_size: wallet_config.batch_size.unwrap_or(10),
             #[cfg(feature = "esplora")]
-            parallel_requests: 5,
+            parallel_requests: wallet_config.parallel_requests.unwrap_or(5),
             #[cfg(feature = "rpc")]
             basic_auth: (
                 wallet_config
@@ -161,10 +161,7 @@ impl WalletConfig {
             #[cfg(feature = "rpc")]
             cookie: wallet_config.cookie.clone(),
             #[cfg(feature = "cbf")]
-            compactfilter_opts: crate::commands::CompactFilterOpts {
-                conn_count: 2,
-                skip_blocks: None,
-            },
+            compactfilter_opts: crate::commands::CompactFilterOpts { conn_count: 2 },
         })
     }
 }
index 8c2e383ec7751bbad19bd2ed5c6724bb55ef87a2..46c3dc0e5bf26e9aa5a0d6713fd8fa93042c9943 100644 (file)
@@ -68,6 +68,7 @@ use std::str::FromStr;
     feature = "rpc"
 ))]
 use std::sync::Arc;
+
 #[cfg(any(
     feature = "electrum",
     feature = "esplora",
@@ -750,6 +751,35 @@ pub fn handle_config_subcommand(
     wallet_opts: &WalletOpts,
     force: bool,
 ) -> Result<String, Error> {
+    if network == Network::Bitcoin {
+        eprintln!(
+            "WARNING: You are configuring a wallet for Bitcoin MAINNET.
+             This software is experimental and not recommended for use with real funds.
+             Consider using a testnet for testing purposes. \n"
+        );
+    }
+
+    let ext_descriptor = wallet_opts.ext_descriptor.clone();
+    let int_descriptor = wallet_opts.int_descriptor.clone();
+
+    if ext_descriptor.contains("xprv") || ext_descriptor.contains("tprv") {
+        eprintln!(
+            "WARNING: Your external descriptor contains PRIVATE KEYS.
+             Private keys will be saved in PLAINTEXT in the config file.
+             This is a security risk. Consider using public descriptors instead.\n"
+        );
+    }
+
+    if let Some(ref internal_desc) = int_descriptor {
+        if internal_desc.contains("xprv") || internal_desc.contains("tprv") {
+            eprintln!(
+                "WARNING: Your internal descriptor contains PRIVATE KEYS.
+                 Private keys will be saved in PLAINTEXT in the config file.
+                 This is a security risk. Consider using public descriptors instead.\n"
+            );
+        }
+    }
+
     let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
         network,
         wallets: HashMap::new(),
@@ -761,8 +791,6 @@ pub fn handle_config_subcommand(
         )));
     }
 
-    let ext_descriptor = wallet_opts.ext_descriptor.clone();
-    let int_descriptor = wallet_opts.int_descriptor.clone();
     #[cfg(any(
         feature = "electrum",
         feature = "esplora",
@@ -1034,9 +1062,141 @@ pub(crate) fn handle_compile_subcommand(
     }
 }
 
+/// Handle wallets command to show all saved wallet configurations
+pub fn handle_wallets_subcommand(datadir: &Path, pretty: bool) -> Result<String, Error> {
+    let load_config = WalletConfig::load(datadir)?;
+
+    let config = match load_config {
+        Some(c) if !c.wallets.is_empty() => c,
+        _ => {
+            return Ok(if pretty {
+                "No wallet configurations found.".to_string()
+            } else {
+                serde_json::to_string_pretty(&json!({
+                    "wallets": []
+                }))?
+            });
+        }
+    };
+
+    if pretty {
+        let mut rows: Vec<Vec<CellStruct>> = vec![];
+
+        for (name, wallet_config) in config.wallets.iter() {
+            let mut row = vec![name.cell(), wallet_config.network.clone().cell()];
+
+            #[cfg(any(feature = "sqlite", feature = "redb"))]
+            row.push(wallet_config.database_type.clone().cell());
+
+            #[cfg(any(
+                feature = "electrum",
+                feature = "esplora",
+                feature = "rpc",
+                feature = "cbf"
+            ))]
+            {
+                let client_str = wallet_config.client_type.as_deref().unwrap_or("N/A");
+                row.push(client_str.cell());
+            }
+
+            #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
+            {
+                let url_str = wallet_config.server_url.as_deref().unwrap_or("N/A");
+                let display_url = if url_str.len() > 20 {
+                    shorten(url_str, 15, 10)
+                } else {
+                    url_str.to_string()
+                };
+                row.push(display_url.cell());
+            }
+
+            let ext_desc_display = if wallet_config.ext_descriptor.len() > 40 {
+                shorten(&wallet_config.ext_descriptor, 20, 15)
+            } else {
+                wallet_config.ext_descriptor.clone()
+            };
+            row.push(ext_desc_display.cell());
+
+            let has_int_desc = if wallet_config.int_descriptor.is_some() {
+                "Yes"
+            } else {
+                "No"
+            };
+            row.push(has_int_desc.cell());
+
+            rows.push(row);
+        }
+
+        let mut title_cells = vec!["Wallet Name".cell().bold(true), "Network".cell().bold(true)];
+
+        #[cfg(any(feature = "sqlite", feature = "redb"))]
+        title_cells.push("Database".cell().bold(true));
+
+        #[cfg(any(
+            feature = "electrum",
+            feature = "esplora",
+            feature = "rpc",
+            feature = "cbf"
+        ))]
+        title_cells.push("Client".cell().bold(true));
+
+        #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
+        title_cells.push("Server URL".cell().bold(true));
+
+        title_cells.push("External Desc".cell().bold(true));
+        title_cells.push("Internal Desc".cell().bold(true));
+
+        let table = rows
+            .table()
+            .title(title_cells)
+            .display()
+            .map_err(|e| Error::Generic(e.to_string()))?;
+
+        Ok(format!("{table}"))
+    } else {
+        let wallets_summary: Vec<_> = config
+            .wallets
+            .iter()
+            .map(|(name, wallet_config)| {
+                let mut wallet_json = json!({
+                    "name": name,
+                    "network": wallet_config.network,
+                    "ext_descriptor": wallet_config.ext_descriptor,
+                    "int_descriptor": wallet_config.int_descriptor,
+                });
+
+                #[cfg(any(feature = "sqlite", feature = "redb"))]
+                {
+                    wallet_json["database_type"] = json!(wallet_config.database_type.clone());
+                }
+
+                #[cfg(any(
+                    feature = "electrum",
+                    feature = "esplora",
+                    feature = "rpc",
+                    feature = "cbf"
+                ))]
+                {
+                    wallet_json["client_type"] = json!(wallet_config.client_type.clone());
+                }
+
+                #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
+                {
+                    wallet_json["server_url"] = json!(wallet_config.server_url.clone());
+                }
+
+                wallet_json
+            })
+            .collect();
+
+        Ok(serde_json::to_string_pretty(&json!({
+            "wallets": wallets_summary
+        }))?)
+    }
+}
+
 /// The global top level handler.
 pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
-    let network = cli_opts.network;
     let pretty = cli_opts.pretty;
     let subcommand = cli_opts.subcommand.clone();
 
@@ -1053,9 +1213,8 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
         } => {
             let home_dir = prepare_home_dir(cli_opts.datadir)?;
 
-            let config = WalletConfig::load(&home_dir)?
-                .ok_or(Error::Generic("No config found".to_string()))?;
-            let wallet_opts = config.get_wallet_opts(&wallet)?;
+            let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet)?;
+
             let database_path = prepare_wallet_db_dir(&home_dir, &wallet)?;
 
             #[cfg(any(feature = "sqlite", feature = "redb"))]
@@ -1083,8 +1242,9 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
                     }
                 };
 
-                let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
-                let blockchain_client = new_blockchain_client(wallet_opts, &wallet, database_path)?;
+                let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
+                let blockchain_client =
+                    new_blockchain_client(&wallet_opts, &wallet, database_path)?;
 
                 let result = handle_online_wallet_subcommand(
                     &mut wallet,
@@ -1109,13 +1269,10 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             wallet: wallet_name,
             subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
         } => {
-            let network = cli_opts.network;
             let datadir = cli_opts.datadir.clone();
             let home_dir = prepare_home_dir(datadir)?;
-            let config = WalletConfig::load(&home_dir)?.ok_or(Error::Generic(format!(
-                "No config found for wallet '{wallet_name}'"
-            )))?;
-            let wallet_opts = config.get_wallet_opts(&wallet_name)?;
+            let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
+
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             let result = {
                 let mut persister: Persister = match &wallet_opts.database_type {
@@ -1151,10 +1308,10 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             };
             #[cfg(not(any(feature = "sqlite", feature = "redb")))]
             let result = {
-                let mut wallet = new_wallet(network, wallet_opts)?;
+                let mut wallet = new_wallet(network, &wallet_opts)?;
                 handle_offline_wallet_subcommand(
                     &mut wallet,
-                    wallet_opts,
+                    &wallet_opts,
                     &cli_opts,
                     offline_subcommand.clone(),
                 )?
@@ -1170,9 +1327,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             let result = handle_config_subcommand(&home_dir, network, wallet, &wallet_opts, force)?;
             Ok(result)
         }
+        CliSubCommand::Wallets => {
+            let home_dir = prepare_home_dir(cli_opts.datadir)?;
+            let result = handle_wallets_subcommand(&home_dir, pretty)?;
+            Ok(result)
+        }
         CliSubCommand::Key {
             subcommand: key_subcommand,
         } => {
+            let network = cli_opts.network;
             let result = handle_key_subcommand(network, key_subcommand, pretty)?;
             Ok(result)
         }
@@ -1181,27 +1344,20 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             policy,
             script_type,
         } => {
+            let network = cli_opts.network;
             let result = handle_compile_subcommand(network, policy, script_type, pretty)?;
             Ok(result)
         }
         #[cfg(feature = "repl")]
         CliSubCommand::Repl {
             wallet: wallet_name,
-            mut wallet_opts,
         } => {
-            let network = cli_opts.network;
             let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
-            wallet_opts.wallet = Some(wallet_name.clone());
-
-            let config = WalletConfig::load(&home_dir)?.ok_or(Error::Generic(format!(
-                "No config found for wallet {}",
-                wallet_name.clone()
-            )))?;
-            let loaded_wallet_opts = config.get_wallet_opts(&wallet_name)?;
+            let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
 
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             let (mut wallet, mut persister) = {
-                let mut persister: Persister = match &loaded_wallet_opts.database_type {
+                let mut persister: Persister = match &wallet_opts.database_type {
                     #[cfg(feature = "sqlite")]
                     DatabaseType::Sqlite => {
                         let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
@@ -1220,7 +1376,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
                         Persister::RedbStore(store)
                     }
                 };
-                let wallet = new_persisted_wallet(network, &mut persister, &loaded_wallet_opts)?;
+                let wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
                 (wallet, persister)
             };
             #[cfg(not(any(feature = "sqlite", feature = "redb")))]
index 7bcc4bcdea4e39ceb39ad1f04ae17364b732534b..90d701b0527f12fb8ddd14cecf9414c377af3b0f 100644 (file)
@@ -27,185 +27,15 @@ mod utils;
 
 use bdk_wallet::bitcoin::Network;
 use log::{debug, error, warn};
-use std::env;
-use std::path::PathBuf;
 
 use crate::commands::CliOpts;
-use crate::config::WalletConfig;
-use crate::error::BDKCliError as Error;
 use crate::handlers::*;
-use crate::utils::prepare_home_dir;
 use clap::Parser;
 
-fn is_value_arg(arg: &str) -> bool {
-    matches!(
-        arg,
-        "-w" | "--wallet"
-            | "-e"
-            | "--ext-descriptor"
-            | "-i"
-            | "--int-descriptor"
-            | "-c"
-            | "--client-type"
-            | "-d"
-            | "--database-type"
-            | "-u"
-            | "--url"
-            | "-b"
-            | "--batch-size"
-            | "-p"
-            | "--parallel-requests"
-            | "-a"
-            | "--basic-auth"
-            | "--cookie"
-            | "-n"
-            | "--network"
-            | "--datadir"
-    )
-}
-
-/// Inject configuration values from config.toml
-/// when --use-config is present, except for the init subcommand.
-fn preprocess_args(args: &mut Vec<String>) -> Result<(), Error> {
-    let use_config = args.iter().any(|arg| arg == "--use-config");
-
-    let is_init = args.iter().any(|arg| arg == "init");
-
-    if !use_config || is_init {
-        return Ok(());
-    }
-
-    let mut wallet_name: Option<String> = None;
-    let mut datadir: Option<String> = None;
-
-    let mut i = 1;
-    while i < args.len() {
-        if args[i] == "-w" || args[i] == "--wallet" {
-            if i + 1 < args.len() {
-                wallet_name = Some(args[i + 1].clone());
-            }
-        } else if (args[i] == "-d" || args[i] == "--datadir") && i + 1 < args.len() {
-            datadir = Some(args[i + 1].clone());
-        }
-        i += if is_value_arg(&args[i]) && i + 1 < args.len() {
-            2
-        } else {
-            1
-        };
-    }
-
-    if let Some(wallet_name) = wallet_name {
-        let home_dir = prepare_home_dir(datadir.map(PathBuf::from))?;
-        if let Ok(Some(config)) = WalletConfig::load(&home_dir) {
-            if let Some(wallet_config) = config.wallets.get(&wallet_name) {
-                let mut top_level_injections: Vec<String> = Vec::new();
-                let mut wallet_injections: Vec<String> = Vec::new();
-
-                if !args.iter().any(|arg| arg == "-n" || arg == "--network") {
-                    top_level_injections.push("--network".to_string());
-                    top_level_injections.push(wallet_config.network.clone());
-                }
-
-                if !args
-                    .iter()
-                    .any(|arg| arg == "-e" || arg == "--ext-descriptor")
-                {
-                    wallet_injections.push("--ext-descriptor".to_string());
-                    wallet_injections.push(wallet_config.ext_descriptor.clone());
-                }
-                if !args
-                    .iter()
-                    .any(|arg| arg == "-i" || arg == "--int-descriptor")
-                {
-                    if let Some(int_descriptor) = &wallet_config.int_descriptor {
-                        wallet_injections.push("--int-descriptor".to_string());
-                        wallet_injections.push(int_descriptor.clone());
-                    }
-                }
-                #[cfg(any(
-                    feature = "electrum",
-                    feature = "esplora",
-                    feature = "rpc",
-                    feature = "cbf"
-                ))]
-                if !args.iter().any(|arg| arg == "-c" || arg == "--client-type") {
-                    if let Some(ct) = &wallet_config.client_type {
-                        wallet_injections.push("--client-type".to_string());
-                        wallet_injections.push(ct.clone());
-                    }
-                }
-                if !args
-                    .iter()
-                    .any(|arg| arg == "-d" || arg == "--database-type")
-                {
-                    #[cfg(any(feature = "sqlite", feature = "redb"))]
-                    {
-                        wallet_injections.push("--database-type".to_string());
-                        wallet_injections.push(wallet_config.database_type.clone());
-                    }
-                }
-                #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
-                if !args.iter().any(|arg| arg == "-u" || arg == "--url") {
-                    if let Some(url) = &wallet_config.server_url {
-                        wallet_injections.push("--url".to_string());
-                        wallet_injections.push(url.clone());
-                    }
-                }
-
-                let mut top_level_insert_pos = 1;
-                while top_level_insert_pos < args.len()
-                    && args[top_level_insert_pos].starts_with('-')
-                {
-                    if is_value_arg(&args[top_level_insert_pos])
-                        && top_level_insert_pos + 1 < args.len()
-                    {
-                        top_level_insert_pos += 2;
-                    } else {
-                        top_level_insert_pos += 1;
-                    }
-                }
-                args.splice(
-                    top_level_insert_pos..top_level_insert_pos,
-                    top_level_injections,
-                );
-
-                let wallet_pos = args
-                    .iter()
-                    .position(|arg| arg == "wallet")
-                    .unwrap_or(args.len());
-                let mut wallet_insert_pos = wallet_pos + 1;
-                while wallet_insert_pos < args.len() && args[wallet_insert_pos].starts_with('-') {
-                    if is_value_arg(&args[wallet_insert_pos]) && wallet_insert_pos + 1 < args.len()
-                    {
-                        wallet_insert_pos += 2;
-                    } else {
-                        wallet_insert_pos += 1;
-                    }
-                }
-                args.splice(wallet_insert_pos..wallet_insert_pos, wallet_injections);
-            }
-        }
-    }
-
-    Ok(())
-}
-
 #[tokio::main]
 async fn main() {
     env_logger::init();
-
-    let mut args: Vec<String> = env::args().collect();
-
-    if let Err(e) = preprocess_args(&mut args) {
-        error!("Failed to preprocess arguments: {e}");
-        std::process::exit(1);
-    }
-
-    if let Some(pos) = args.iter().position(|arg| arg == "--use-config") {
-        args.remove(pos);
-    }
-
-    let cli_opts: CliOpts = CliOpts::parse_from(args);
+    let cli_opts: CliOpts = CliOpts::parse();
 
     let network = &cli_opts.network;
     debug!("network: {network:?}");
index 0318144ae7aaf93c87435c90df7a55c044a11506..73d345312c8c5f819e0b3e397609fada73c24570 100644 (file)
@@ -9,6 +9,7 @@
 //! Utility Tools
 //!
 //! This module includes all the utility tools used by the App.
+use crate::config::WalletConfig;
 use crate::error::BDKCliError as Error;
 use std::{
     fmt::Display,
@@ -279,13 +280,13 @@ pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result<W
     let int_descriptor = wallet_opts.int_descriptor.clone();
 
     match int_descriptor {
-         Some(int_descriptor) => {
+        Some(int_descriptor) => {
             let wallet = Wallet::create(ext_descriptor, int_descriptor)
                 .network(network)
                 .create_wallet_no_persist()?;
             Ok(wallet)
         }
-         None => {
+        None => {
             let wallet = Wallet::create_single(ext_descriptor)
                 .network(network)
                 .create_wallet_no_persist()?;
@@ -363,6 +364,11 @@ pub async fn sync_kyoto_client(wallet: &mut Wallet, client: Box<LightClient>) ->
 
 pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> String {
     let displayable = displayable.to_string();
+
+    if displayable.len() <= (start + end) as usize {
+        return displayable;
+    }
+
     let start_str: &str = &displayable[0..start as usize];
     let end_str: &str = &displayable[displayable.len() - end as usize..];
     format!("{start_str}...{end_str}")
@@ -614,3 +620,31 @@ pub fn format_descriptor_output(result: &Value, pretty: bool) -> Result<String,
 
     Ok(format!("{table}"))
 }
+
+pub fn load_wallet_config(
+    home_dir: &Path,
+    wallet_name: &str,
+) -> Result<(WalletOpts, Network), Error> {
+    let config = WalletConfig::load(home_dir)?.ok_or(Error::Generic(format!(
+        "No config found for wallet {wallet_name}",
+    )))?;
+
+    let wallet_opts = config.get_wallet_opts(wallet_name)?;
+    let wallet_config = config
+        .wallets
+        .get(wallet_name)
+        .ok_or(Error::Generic(format!(
+            "Wallet '{wallet_name}' not found in config"
+        )))?;
+
+    let network = match wallet_config.network.as_str() {
+        "bitcoin" => Ok(Network::Bitcoin),
+        "testnet" => Ok(Network::Testnet),
+        "regtest" => Ok(Network::Regtest),
+        "signet" => Ok(Network::Signet),
+        "testnet4" => Ok(Network::Testnet4),
+        _ => Err(Error::Generic("Invalid network in config".to_string())),
+    }?;
+
+    Ok((wallet_opts, network))
+}