]> Untitled Git - bdk-cli/commitdiff
feat(init-wallet): rename init & add walletopts
authorVihiga Tyonum <withtvpeter@gmail.com>
Fri, 3 Oct 2025 10:56:51 +0000 (11:56 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 16 Jan 2026 10:43:01 +0000 (11:43 +0100)
- rename init to config and move walletopts as
config options

src/commands.rs
src/config.rs
src/handlers.rs
src/utils.rs

index ce2a6bd96f4f13c7d936c3e697665424e7c5d34d..403578504f9ea0ffb422a59506160f4df7c3afad 100644 (file)
 //! All subcommands are defined in the below enums.
 
 #![allow(clippy::large_enum_variant)]
-use crate::config::WalletConfig;
-use crate::error::BDKCliError as Error;
 use bdk_wallet::bitcoin::{
     Address, Network, OutPoint, ScriptBuf,
     bip32::{DerivationPath, Xpriv},
 };
 use clap::{Args, Parser, Subcommand, ValueEnum, value_parser};
-use std::path::Path;
 
 #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
 use crate::utils::parse_proxy_auth;
@@ -72,8 +69,10 @@ pub enum CliSubCommand {
     /// 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 {
-        #[command(flatten)]
-        wallet_opts: WalletOpts,
+        /// Selects the wallet to use.
+        #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
+        wallet: String,
+
         #[command(subcommand)]
         subcommand: WalletSubCommand,
     },
@@ -106,6 +105,10 @@ pub enum CliSubCommand {
     /// 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 {
+        /// Wallet name for this REPL session
+        #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
+        wallet: String,
+
         #[command(flatten)]
         wallet_opts: WalletOpts,
     },
@@ -129,11 +132,14 @@ pub enum CliSubCommand {
 /// Wallet operation subcommands.
 #[derive(Debug, Subcommand, Clone, PartialEq)]
 pub enum WalletSubCommand {
-    /// Initialize a wallet configuration and save to `config.toml`.
-    Init {
+    /// Save wallet configuration to `config.toml`.
+    Config {
         /// Overwrite existing wallet configuration if it exists.
-        #[arg(long = "force", default_value_t = false)]
+        #[arg(short = 'f', long = "force", default_value_t = false)]
         force: bool,
+
+        #[command(flatten)]
+        wallet_opts: WalletOpts,
     },
     #[cfg(any(
         feature = "electrum",
@@ -179,14 +185,15 @@ pub enum ClientType {
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
 pub struct WalletOpts {
     /// Selects the wallet to use.
-    #[arg(env = "WALLET_NAME", short = 'w', long = "wallet")]
+    #[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,
     /// Sets the descriptor to use for the external addresses.
-    #[arg(env = "EXT_DESCRIPTOR", short = 'e', long)]
-    pub ext_descriptor: Option<String>,
+    #[arg(env = "EXT_DESCRIPTOR", short = 'e', long, required = true)]
+    pub ext_descriptor: String,
     /// Sets the descriptor to use for internal/change addresses.
     #[arg(env = "INT_DESCRIPTOR", short = 'i', long)]
     pub int_descriptor: Option<String>,
@@ -237,56 +244,6 @@ pub struct WalletOpts {
     pub compactfilter_opts: CompactFilterOpts,
 }
 
-impl WalletOpts {
-    /// Merges optional configuration values from config.toml into the current WalletOpts.
-    pub fn load_config(&mut self, wallet_name: &str, datadir: &Path) -> Result<(), Error> {
-        if let Some(config) = WalletConfig::load(datadir)? {
-            if let Ok(config_opts) = config.get_wallet_opts(wallet_name) {
-                self.verbose = self.verbose || config_opts.verbose;
-                #[cfg(feature = "electrum")]
-                {
-                    self.batch_size = if self.batch_size != 10 {
-                        self.batch_size
-                    } else {
-                        config_opts.batch_size
-                    };
-                }
-                #[cfg(feature = "esplora")]
-                {
-                    self.parallel_requests = if self.parallel_requests != 5 {
-                        self.parallel_requests
-                    } else {
-                        config_opts.parallel_requests
-                    };
-                }
-                #[cfg(feature = "rpc")]
-                {
-                    self.basic_auth = if self.basic_auth != ("user".into(), "password".into()) {
-                        self.basic_auth.clone()
-                    } else {
-                        config_opts.basic_auth
-                    };
-                    self.cookie = self.cookie.take().or(config_opts.cookie);
-                }
-                #[cfg(feature = "cbf")]
-                {
-                    if self.compactfilter_opts.conn_count == 2
-                        && config_opts.compactfilter_opts.conn_count != 2
-                    {
-                        self.compactfilter_opts.conn_count =
-                            config_opts.compactfilter_opts.conn_count;
-                    }
-                    if self.compactfilter_opts.skip_blocks.is_none() {
-                        self.compactfilter_opts.skip_blocks =
-                            config_opts.compactfilter_opts.skip_blocks;
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-}
-
 /// Options to configure a SOCKS5 proxy for a blockchain client connection.
 #[cfg(any(feature = "electrum", feature = "esplora"))]
 #[derive(Debug, Args, Clone, PartialEq, Eq)]
index 4e4f0d2ba9f22e018f3333634c4cceba062a0727..c6f5e806604e6d8905cde8f8f55b2519fcbdd68a 100644 (file)
@@ -127,7 +127,7 @@ impl WalletConfig {
         Ok(WalletOpts {
             wallet: Some(wallet_config.wallet.clone()),
             verbose: false,
-            ext_descriptor: Some(wallet_config.ext_descriptor.clone()),
+            ext_descriptor: wallet_config.ext_descriptor.clone(),
             int_descriptor: wallet_config.int_descriptor.clone(),
             #[cfg(any(
                 feature = "electrum",
index 6b480da272f13c392e3341a033d8f2f10e6577bb..8c2e383ec7751bbad19bd2ed5c6724bb55ef87a2 100644 (file)
@@ -742,33 +742,26 @@ pub(crate) async fn handle_online_wallet_subcommand(
     }
 }
 
-/// Handle wallet init subcommand to create or update config.toml
-pub fn handle_init_subcommand(
+/// Handle wallet config subcommand to create or update config.toml
+pub fn handle_config_subcommand(
     datadir: &Path,
     network: Network,
+    wallet: String,
     wallet_opts: &WalletOpts,
     force: bool,
 ) -> Result<String, Error> {
-    let wallet_name = wallet_opts
-        .wallet
-        .as_ref()
-        .ok_or_else(|| Error::Generic("Wallet name is required".to_string()))?;
-
     let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
         network,
         wallets: HashMap::new(),
     });
 
-    if config.wallets.contains_key(wallet_name) && !force {
+    if config.wallets.contains_key(&wallet) && !force {
         return Err(Error::Generic(format!(
-            "Wallet '{wallet_name}' already exists in config.toml. Use --force to overwrite."
+            "Wallet '{wallet}' already exists in config.toml. Use --force to overwrite."
         )));
     }
 
-    let ext_descriptor = wallet_opts
-        .ext_descriptor
-        .clone()
-        .ok_or_else(|| Error::Generic("External descriptor is required".to_string()))?;
+    let ext_descriptor = wallet_opts.ext_descriptor.clone();
     let int_descriptor = wallet_opts.int_descriptor.clone();
     #[cfg(any(
         feature = "electrum",
@@ -805,7 +798,7 @@ pub fn handle_init_subcommand(
     };
 
     let wallet_config = WalletConfigInner {
-        wallet: wallet_name.to_string(),
+        wallet: wallet.clone(),
         network: network.to_string(),
         ext_descriptor,
         int_descriptor,
@@ -833,13 +826,11 @@ pub fn handle_init_subcommand(
     };
 
     config.network = network;
-    config
-        .wallets
-        .insert(wallet_name.to_string(), wallet_config);
+    config.wallets.insert(wallet.clone(), wallet_config);
     config.save(datadir)?;
 
     Ok(serde_json::to_string_pretty(&json!({
-        "message": format!("Wallet '{wallet_name}' initialized successfully in {:?}", datadir.join("config.toml"))
+        "message": format!("Wallet '{wallet}' initialized successfully in {:?}", datadir.join("config.toml"))
     }))?)
 }
 
@@ -1057,11 +1048,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             feature = "rpc"
         ))]
         CliSubCommand::Wallet {
-            mut wallet_opts,
+            wallet,
             subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
         } => {
             let home_dir = prepare_home_dir(cli_opts.datadir)?;
-            let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts)?;
+
+            let config = WalletConfig::load(&home_dir)?
+                .ok_or(Error::Generic("No config found".to_string()))?;
+            let wallet_opts = config.get_wallet_opts(&wallet)?;
+            let database_path = prepare_wallet_db_dir(&home_dir, &wallet)?;
 
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             let result = {
@@ -1111,18 +1106,22 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             Ok(result)
         }
         CliSubCommand::Wallet {
-            mut wallet_opts,
+            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)?;
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             let result = {
-                let home_dir = prepare_home_dir(datadir)?;
                 let mut persister: Persister = match &wallet_opts.database_type {
                     #[cfg(feature = "sqlite")]
                     DatabaseType::Sqlite => {
-                        let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts)?;
+                        let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
                         let db_file = database_path.join("wallet.sqlite");
                         let connection = Connection::open(db_file)?;
                         log::debug!("Sqlite database opened successfully");
@@ -1130,15 +1129,10 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
                     }
                     #[cfg(feature = "redb")]
                     DatabaseType::Redb => {
-                        let wallet_name = &wallet_opts.wallet;
-
                         let db = Arc::new(bdk_redb::redb::Database::create(
                             home_dir.join("wallet.redb"),
                         )?);
-                        let store = RedbStore::new(
-                            db,
-                            wallet_name.as_deref().unwrap_or("wallet").to_string(),
-                        )?;
+                        let store = RedbStore::new(db, wallet_name)?;
                         log::debug!("Redb database opened successfully");
                         Persister::RedbStore(store)
                     }
@@ -1168,12 +1162,12 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             Ok(result)
         }
         CliSubCommand::Wallet {
-            wallet_opts,
-            subcommand: WalletSubCommand::Init { force },
+            wallet,
+            subcommand: WalletSubCommand::Config { force, wallet_opts },
         } => {
             let network = cli_opts.network;
             let home_dir = prepare_home_dir(cli_opts.datadir)?;
-            let result = handle_init_subcommand(&home_dir, network, &wallet_opts, force)?;
+            let result = handle_config_subcommand(&home_dir, network, wallet, &wallet_opts, force)?;
             Ok(result)
         }
         CliSubCommand::Key {
@@ -1191,17 +1185,26 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
             Ok(result)
         }
         #[cfg(feature = "repl")]
-        CliSubCommand::Repl { ref wallet_opts } => {
+        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)?;
+
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             let (mut wallet, mut persister) = {
-                let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
-
-                let mut persister: Persister = match &wallet_opts.database_type {
+                let mut persister: Persister = match &loaded_wallet_opts.database_type {
                     #[cfg(feature = "sqlite")]
                     DatabaseType::Sqlite => {
-                        let database_path =
-                            prepare_wallet_db_dir(&home_dir, &mut wallet_opts.clone())?;
+                        let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
                         let db_file = database_path.join("wallet.sqlite");
                         let connection = Connection::open(db_file)?;
                         log::debug!("Sqlite database opened successfully");
@@ -1209,25 +1212,21 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
                     }
                     #[cfg(feature = "redb")]
                     DatabaseType::Redb => {
-                        let wallet_name = &wallet_opts.wallet;
                         let db = Arc::new(bdk_redb::redb::Database::create(
                             home_dir.join("wallet.redb"),
                         )?);
-                        let store = RedbStore::new(
-                            db,
-                            wallet_name.as_deref().unwrap_or("wallet").to_string(),
-                        )?;
+                        let store = RedbStore::new(db, wallet_name.clone())?;
                         log::debug!("Redb database opened successfully");
                         Persister::RedbStore(store)
                     }
                 };
-                let wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
+                let wallet = new_persisted_wallet(network, &mut persister, &loaded_wallet_opts)?;
                 (wallet, persister)
             };
             #[cfg(not(any(feature = "sqlite", feature = "redb")))]
-            let mut wallet = new_wallet(network, &wallet_opts)?;
+            let mut wallet = new_wallet(network, &loaded_wallet_opts)?;
             let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
-            let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts.clone())?;
+            let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
             loop {
                 let line = readline()?;
                 let line = line.trim();
@@ -1238,7 +1237,8 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
                 let result = respond(
                     network,
                     &mut wallet,
-                    wallet_opts,
+                    &wallet_name,
+                    &mut wallet_opts.clone(),
                     line,
                     database_path.clone(),
                     &cli_opts,
@@ -1276,7 +1276,8 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
 async fn respond(
     network: Network,
     wallet: &mut Wallet,
-    wallet_opts: &WalletOpts,
+    wallet_name: &String,
+    wallet_opts: &mut WalletOpts,
     line: &str,
     _datadir: std::path::PathBuf,
     cli_opts: &CliOpts,
@@ -1311,10 +1312,16 @@ async fn respond(
             Some(value)
         }
         ReplSubCommand::Wallet {
-            subcommand: WalletSubCommand::Init { force },
+            subcommand: WalletSubCommand::Config { force, wallet_opts },
         } => {
-            let value = handle_init_subcommand(&_datadir, network, wallet_opts, force)
-                .map_err(|e| e.to_string())?;
+            let value = handle_config_subcommand(
+                &_datadir,
+                network,
+                wallet_name.to_string(),
+                &wallet_opts,
+                force,
+            )
+            .map_err(|e| e.to_string())?;
             Some(value)
         }
         ReplSubCommand::Key { subcommand } => {
index 5afd4ff8d6579b9d9a993808653b5186af2453b9..0318144ae7aaf93c87435c90df7a55c044a11506 100644 (file)
@@ -122,17 +122,13 @@ pub(crate) fn prepare_home_dir(home_path: Option<PathBuf>) -> Result<PathBuf, Er
 #[allow(dead_code)]
 pub(crate) fn prepare_wallet_db_dir(
     home_path: &Path,
-    wallet_opts: &mut WalletOpts,
+    wallet_name: &str,
 ) -> Result<std::path::PathBuf, Error> {
     let mut dir = home_path.to_owned();
-    let wallet_name = wallet_opts.wallet.clone();
-    if let Some(wallet_name) = wallet_name {
-        dir.push(&wallet_name);
+    dir.push(wallet_name);
 
-        if !dir.exists() {
-            std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
-        }
-        wallet_opts.load_config(wallet_name.as_str(), home_path)?;
+    if !dir.exists() {
+        std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
     }
 
     Ok(dir)
@@ -245,17 +241,14 @@ where
     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());
-    }
+    wallet_load_params =
+        wallet_load_params.descriptor(KeychainKind::External, Some(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();
-    }
+    wallet_load_params = wallet_load_params.extract_keys();
 
     let wallet_opt = wallet_load_params
         .check_network(network)
@@ -264,25 +257,16 @@ where
 
     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(|e| Error::Generic(e.to_string()))?;
-                Ok(wallet)
-            }
-            (Some(ext_descriptor), None) => {
-                let wallet = Wallet::create_single(ext_descriptor)
-                    .network(network)
-                    .create_wallet(persister)
-                    .map_err(|e| Error::Generic(e.to_string()))?;
-                Ok(wallet)
-            }
-            _ => Err(Error::Generic(
-                "An external descriptor is required.".to_string(),
-            )),
-        }?,
+        None => match int_descriptor {
+            Some(int_descriptor) => Wallet::create(ext_descriptor, int_descriptor)
+                .network(network)
+                .create_wallet(persister)
+                .map_err(|e| Error::Generic(e.to_string()))?,
+            None => Wallet::create_single(ext_descriptor)
+                .network(network)
+                .create_wallet(persister)
+                .map_err(|e| Error::Generic(e.to_string()))?,
+        },
     };
 
     Ok(wallet)
@@ -294,22 +278,19 @@ pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result<W
     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)) => {
+    match int_descriptor {
+         Some(int_descriptor) => {
             let wallet = Wallet::create(ext_descriptor, int_descriptor)
                 .network(network)
                 .create_wallet_no_persist()?;
             Ok(wallet)
         }
-        (Some(ext_descriptor), None) => {
+         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(),
-        )),
     }
 }