From: Vihiga Tyonum Date: Fri, 3 Oct 2025 10:56:51 +0000 (+0100) Subject: feat(init-wallet): rename init & add walletopts X-Git-Url: http://internal-gitweb-vhost/%22https:/parse/scripts/database/-script/struct.DecoderReader.html?a=commitdiff_plain;h=f3ee4ee4f72510b9cb9c7d3552f91c691e605a36;p=bdk-cli feat(init-wallet): rename init & add walletopts - rename init to config and move walletopts as config options --- diff --git a/src/commands.rs b/src/commands.rs index ce2a6bd..4035785 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,14 +13,11 @@ //! 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, + // #[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, + #[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, @@ -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)] diff --git a/src/config.rs b/src/config.rs index 4e4f0d2..c6f5e80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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", diff --git a/src/handlers.rs b/src/handlers.rs index 6b480da..8c2e383 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -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 { - 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 { 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 { 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 { } #[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 { 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 { 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 { } #[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 { 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 { 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 } => { diff --git a/src/utils.rs b/src/utils.rs index 5afd4ff..0318144 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -122,17 +122,13 @@ pub(crate) fn prepare_home_dir(home_path: Option) -> Result Result { 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 { + 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(), - )), } }