From 88d0ed09bcd678313a19f420d1ca7ffb9c8a504f Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Sat, 5 Sep 2026 18:34:38 +0100 Subject: [PATCH 01/13] Refactor wallet DB directory setup into config loading Combine `prepare_wallet_db_dir` and `load_wallet_config` into a single function that returns the database path alongside wallet options and network, reducing code duplication in the `WalletRuntime` loader. Signed-off-by: emma31-dev --- src/utils/common.rs | 34 +++++++++++++--------------------- src/utils/runtime.rs | 9 +++------ 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/utils/common.rs b/src/utils/common.rs index 136c9873..20996c7b 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -112,22 +112,6 @@ pub(crate) fn prepare_home_dir(home_path: Option) -> Result Result { - let mut dir = home_path.to_owned(); - dir.push(wallet_name); - - if !dir.exists() { - std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?; - } - - Ok(dir) -} - pub fn is_mnemonic(s: &str) -> bool { let word_count = s.split_whitespace().count(); (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace()) @@ -154,11 +138,19 @@ pub async fn trace_logger( } } -pub fn load_wallet_config( - home_dir: &Path, +/// Prepare wallet database directory and config. +pub fn prepare_wallet_db_dir_and_config( + home_path: &Path, wallet_name: &str, -) -> Result<(WalletOpts, Network), Error> { - let config = WalletConfig::load(home_dir)?.ok_or(Error::Generic(format!( +) -> Result<(std::path::PathBuf, WalletOpts, Network), Error> { + let mut dir = home_path.to_owned(); + dir.push(wallet_name); + + if !dir.exists() { + std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?; + } + + let config = WalletConfig::load(home_path)?.ok_or(Error::Generic(format!( "No config found for wallet {wallet_name}", )))?; @@ -173,7 +165,7 @@ pub fn load_wallet_config( let network = Network::from_str(&wallet_config.network) .map_err(|_| Error::Generic("Invalid network in config".to_string()))?; - Ok((wallet_opts, network)) + Ok((dir, wallet_opts, network)) } #[cfg(feature = "silent-payments")] diff --git a/src/utils/runtime.rs b/src/utils/runtime.rs index 8beb5527..31e86273 100644 --- a/src/utils/runtime.rs +++ b/src/utils/runtime.rs @@ -7,9 +7,7 @@ use std::{ }; use crate::{ - error::BDKCliError as Error, - persister::new_wallet, - utils::{load_wallet_config, prepare_wallet_db_dir}, + error::BDKCliError as Error, persister::new_wallet, utils::prepare_wallet_db_dir_and_config, }; #[cfg(any(feature = "sqlite", feature = "redb"))] use { @@ -76,9 +74,8 @@ pub struct WalletRuntime { impl WalletRuntime { pub fn load(home_dir: &Path, wallet_name: &str) -> Result { - let (wallet_opts, network) = load_wallet_config(home_dir, wallet_name)?; - - let database_path = prepare_wallet_db_dir(home_dir, wallet_name)?; + let (database_path, wallet_opts, network) = + prepare_wallet_db_dir_and_config(home_dir, wallet_name)?; Ok(Self { wallet_name: wallet_name.to_string(), From 9be71a76d555e90978c854c7ba69fa362c313f24 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 13:43:11 +0100 Subject: [PATCH 02/13] Add wallet delete-config command (#310) Introduce a new `delete-config` subcommand that removes a wallet's configuration. If it was the last wallet in `config.toml`, the configuration file itself is deleted; otherwise the file is rewritten without the wallet entry. The subcommand is gated behind the `repl` feature and rejects execution in REPL mode, since the wallet for the current session is already loaded. --- src/commands.rs | 5 +++++ src/config.rs | 17 +++++++++++++++++ src/handlers/config.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/handlers/repl.rs | 9 +++++++++ src/main.rs | 8 ++++++++ 5 files changed, 77 insertions(+) diff --git a/src/commands.rs b/src/commands.rs index 37f80523..1b73140b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,6 +13,8 @@ //! All subcommands are defined in the below enums. #![allow(clippy::large_enum_variant)] +#[cfg(feature = "repl")] +use crate::handlers::config::DeleteConfigCommand; #[cfg(feature = "message_signer")] use crate::handlers::offline::{SignMessageCommand, VerifyMessageCommand}; use crate::handlers::{ @@ -213,6 +215,9 @@ pub enum CliSubCommand { pub enum WalletSubCommand { /// Save wallet configuration to `config.toml`. Config(SaveConfigCommand), + /// Delete a saved wallet configuration. + #[cfg(feature = "repl")] + DeleteConfig(DeleteConfigCommand), #[cfg(any( feature = "electrum", feature = "esplora", diff --git a/src/config.rs b/src/config.rs index 60580037..b84fa5c0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -95,6 +95,23 @@ impl WalletConfig { Ok(()) } + /// Safely delete the configuration file from the wallet's data directory. + /// + /// Returns `Ok(true)` if the file was deleted, and `Ok(false)` if the file + /// did not exist (in which case no action is taken). + pub fn delete(datadir: &Path) -> Result { + let config_path = datadir.join("config.toml"); + if !config_path.exists() { + log::debug!("Config file {config_path:?} does not exist, nothing to delete"); + return Ok(false); + } + fs::remove_file(&config_path).map_err(|e| { + Error::Generic(format!("Failed to delete config file {config_path:?}: {e}")) + })?; + log::debug!("Deleted config file {config_path:?}"); + Ok(true) + } + /// Get config for a wallet pub fn get_wallet_opts(&self, wallet_name: &str) -> Result { self.wallets diff --git a/src/handlers/config.rs b/src/handlers/config.rs index 13131c19..54590582 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -160,6 +160,44 @@ impl AppCommand> for SaveConfigCommand { } } +#[derive(Args, Debug, Clone, PartialEq)] +pub struct DeleteConfigCommand { + /// The name of the wallet whose configuration should be deleted. + #[arg(long = "wallet")] + pub(crate) wallet: String, +} + +impl AppCommand> for DeleteConfigCommand { + type Output = StatusResult; + + fn execute(&self, ctx: &mut AppContext) -> Result { + let wallet_name = &self.wallet; + + let mut config = WalletConfig::load(&ctx.datadir)? + .ok_or_else(|| Error::Generic("No wallets configured yet.".to_owned()))?; + + if config.wallets.remove(wallet_name.as_str()).is_none() { + return Err(Error::Generic(format!( + "Wallet '{}' not found in config.", + wallet_name + ))); + } + + if config.wallets.is_empty() { + WalletConfig::delete(&ctx.datadir)?; + } else { + config + .save(&ctx.datadir) + .map_err(|error| Error::Generic(error.to_string()))?; + } + + Ok(StatusResult { + message: format!("Wallet '{}' configuration deleted successfully.", wallet_name), + }) + } +} + + #[derive(Args, Debug, Clone, PartialEq)] pub struct ListWalletsCommand; diff --git a/src/handlers/repl.rs b/src/handlers/repl.rs index b15b653f..6297abd7 100644 --- a/src/handlers/repl.rs +++ b/src/handlers/repl.rs @@ -86,6 +86,15 @@ pub(crate) async fn respond( .map_err(|e| e.to_string())?; Some(()) } + WalletSubCommand::DeleteConfig(_) => { + writeln!( + std::io::stdout(), + "`delete-config` is not available in REPL mode — the wallet for this session \ + is already loaded. Exit and run `bdk-cli wallet --wallet delete-config ...`." + ) + .map_err(|e| e.to_string())?; + Some(()) + } }, ReplSubCommand::Descriptor(cmd) => { diff --git a/src/main.rs b/src/main.rs index 06e3ea24..220d1508 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,6 +115,14 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> { config_cmd.execute(&mut ctx)?.write_out(std::io::stdout())?; } + + WalletSubCommand::DeleteConfig(mut delete_cmd) => { + delete_cmd.wallet = wallet_name; + + let mut ctx = AppContext::new(cli_opts.network, home_dir); + + delete_cmd.execute(&mut ctx)?.write_out(std::io::stdout())?; + } }, CliSubCommand::Key { subcommand } => { From 62405f480411afcd9ba487f5e1a2aeb768c04ea5 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 13:50:29 +0100 Subject: [PATCH 03/13] Remove repl feature gate from DeleteConfig command --- src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index 1b73140b..af5a81a6 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -216,7 +216,6 @@ pub enum WalletSubCommand { /// Save wallet configuration to `config.toml`. Config(SaveConfigCommand), /// Delete a saved wallet configuration. - #[cfg(feature = "repl")] DeleteConfig(DeleteConfigCommand), #[cfg(any( feature = "electrum", From 4c1e7ee44679e4b73c35fdd0049b88bfa8d7a99f Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 13:52:24 +0100 Subject: [PATCH 04/13] Add config file deletion via CLI to CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 098eee19..53e4e3a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details. ## [Unreleased] - Added support for Multipath (two-paths) descriptors. - +- Added deletion of config file through the cli ## [4.0.0] From 8fde4f5d45e40fe94001298cc3962872b8c6c8f8 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 13:53:01 +0100 Subject: [PATCH 05/13] Format long format string in DeleteConfigCommand --- src/handlers/config.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/handlers/config.rs b/src/handlers/config.rs index 54590582..8c323425 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -192,12 +192,14 @@ impl AppCommand> for DeleteConfigCommand { } Ok(StatusResult { - message: format!("Wallet '{}' configuration deleted successfully.", wallet_name), + message: format!( + "Wallet '{}' configuration deleted successfully.", + wallet_name + ), }) } } - #[derive(Args, Debug, Clone, PartialEq)] pub struct ListWalletsCommand; From 73256f1cf71c9970b3b9e0765137d2b3a9d693d8 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 18:55:58 +0100 Subject: [PATCH 06/13] Remove needless borrow in config wallet removal --- src/handlers/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/config.rs b/src/handlers/config.rs index 8c323425..f92b0dc6 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -176,7 +176,7 @@ impl AppCommand> for DeleteConfigCommand { let mut config = WalletConfig::load(&ctx.datadir)? .ok_or_else(|| Error::Generic("No wallets configured yet.".to_owned()))?; - if config.wallets.remove(wallet_name.as_str()).is_none() { + if config.wallets.remove(wallet_name).is_none() { return Err(Error::Generic(format!( "Wallet '{}' not found in config.", wallet_name From ffa9fc3272b1170710132e4c5f1d954442bfb8a8 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 18:56:36 +0100 Subject: [PATCH 07/13] Remove empty wallet config deletion logic --- src/handlers/config.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/handlers/config.rs b/src/handlers/config.rs index f92b0dc6..400037ee 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -183,13 +183,7 @@ impl AppCommand> for DeleteConfigCommand { ))); } - if config.wallets.is_empty() { - WalletConfig::delete(&ctx.datadir)?; - } else { - config - .save(&ctx.datadir) - .map_err(|error| Error::Generic(error.to_string()))?; - } + WalletConfig::delete(&ctx.datadir)?; Ok(StatusResult { message: format!( From bd93eda652d8a3ea956ce86c6dcbe0937870dcc2 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 19:18:13 +0100 Subject: [PATCH 08/13] Refactor wallet directory and config preparation Split the combined helper into separate functions for preparing the wallet database directory and loading wallet config. --- src/utils/common.rs | 35 ++++++++++++++++++++++------------- src/utils/runtime.rs | 7 ++++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/utils/common.rs b/src/utils/common.rs index 20996c7b..dff998f0 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -112,6 +112,22 @@ pub(crate) fn prepare_home_dir(home_path: Option) -> Result Result { + let mut dir = home_path.to_owned(); + dir.push(wallet_name); + + if !dir.exists() { + std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?; + } + + Ok(dir) +} + pub fn is_mnemonic(s: &str) -> bool { let word_count = s.split_whitespace().count(); (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace()) @@ -138,19 +154,12 @@ pub async fn trace_logger( } } -/// Prepare wallet database directory and config. -pub fn prepare_wallet_db_dir_and_config( - home_path: &Path, +/// Prepare wallet database directory. +pub fn load_wallet_config( + home_dir: &Path, wallet_name: &str, -) -> Result<(std::path::PathBuf, WalletOpts, Network), Error> { - let mut dir = home_path.to_owned(); - dir.push(wallet_name); - - if !dir.exists() { - std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?; - } - - let config = WalletConfig::load(home_path)?.ok_or(Error::Generic(format!( +) -> Result<(WalletOpts, Network), Error> { + let config = WalletConfig::load(home_dir)?.ok_or(Error::Generic(format!( "No config found for wallet {wallet_name}", )))?; @@ -165,7 +174,7 @@ pub fn prepare_wallet_db_dir_and_config( let network = Network::from_str(&wallet_config.network) .map_err(|_| Error::Generic("Invalid network in config".to_string()))?; - Ok((dir, wallet_opts, network)) + Ok((wallet_opts, network)) } #[cfg(feature = "silent-payments")] diff --git a/src/utils/runtime.rs b/src/utils/runtime.rs index 31e86273..cd0e0a02 100644 --- a/src/utils/runtime.rs +++ b/src/utils/runtime.rs @@ -7,7 +7,7 @@ use std::{ }; use crate::{ - error::BDKCliError as Error, persister::new_wallet, utils::prepare_wallet_db_dir_and_config, + error::BDKCliError as Error, persister::new_wallet, utils::{prepare_wallet_db_dir, load_wallet_config}, }; #[cfg(any(feature = "sqlite", feature = "redb"))] use { @@ -74,8 +74,9 @@ pub struct WalletRuntime { impl WalletRuntime { pub fn load(home_dir: &Path, wallet_name: &str) -> Result { - let (database_path, wallet_opts, network) = - prepare_wallet_db_dir_and_config(home_dir, wallet_name)?; + let (wallet_opts, network) = load_wallet_config(home_dir, wallet_name)?; + + let database_path = prepare_wallet_db_dir(home_dir, wallet_name)?; Ok(Self { wallet_name: wallet_name.to_string(), From a605a0626243c95bb8721e8c93a49b577afba625 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 19:20:24 +0100 Subject: [PATCH 09/13] Format imports in runtime module --- src/utils/runtime.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/runtime.rs b/src/utils/runtime.rs index cd0e0a02..8beb5527 100644 --- a/src/utils/runtime.rs +++ b/src/utils/runtime.rs @@ -7,7 +7,9 @@ use std::{ }; use crate::{ - error::BDKCliError as Error, persister::new_wallet, utils::{prepare_wallet_db_dir, load_wallet_config}, + error::BDKCliError as Error, + persister::new_wallet, + utils::{load_wallet_config, prepare_wallet_db_dir}, }; #[cfg(any(feature = "sqlite", feature = "redb"))] use { From 77fe4c42a629ccbee702ba9dbd00f9421768beab Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 19:21:15 +0100 Subject: [PATCH 10/13] Remove outdated doc comment from load_wallet_config --- src/utils/common.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/common.rs b/src/utils/common.rs index dff998f0..136c9873 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -154,7 +154,6 @@ pub async fn trace_logger( } } -/// Prepare wallet database directory. pub fn load_wallet_config( home_dir: &Path, wallet_name: &str, From b801e7f23cb3b7f55b5cbab54ccab202f4ec43e5 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 22:11:54 +0100 Subject: [PATCH 11/13] Log warning to stderr when config file is missing --- src/config.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index b84fa5c0..58fdf849 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ use clap::ValueEnum; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; +use std::io::Write; use std::path::Path; use std::str::FromStr; @@ -102,7 +103,11 @@ impl WalletConfig { pub fn delete(datadir: &Path) -> Result { let config_path = datadir.join("config.toml"); if !config_path.exists() { - log::debug!("Config file {config_path:?} does not exist, nothing to delete"); + writeln!( + std::io::stderr(), + "Config file {config_path:?} does not exist, nothing to delete" + ) + .map_err(|e| Error::Generic(format!("Failed to write warning: {e}")))?; return Ok(false); } fs::remove_file(&config_path).map_err(|e| { From de1ee573655ba970a3c656bb0bf83772e82b4d42 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 22:18:22 +0100 Subject: [PATCH 12/13] Add env var and short flag to delete config wallet arg --- src/handlers/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/config.rs b/src/handlers/config.rs index 400037ee..1eb17005 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -163,7 +163,7 @@ impl AppCommand> for SaveConfigCommand { #[derive(Args, Debug, Clone, PartialEq)] pub struct DeleteConfigCommand { /// The name of the wallet whose configuration should be deleted. - #[arg(long = "wallet")] + #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)] pub(crate) wallet: String, } From 04e88c7b774701571aee7b91ebbdd94a5518f528 Mon Sep 17 00:00:00 2001 From: emma31-dev Date: Fri, 11 Sep 2026 22:22:12 +0100 Subject: [PATCH 13/13] Remove unnecessary cfg gate from DeleteConfigCommand import --- src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index af5a81a6..4b523210 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,7 +13,6 @@ //! All subcommands are defined in the below enums. #![allow(clippy::large_enum_variant)] -#[cfg(feature = "repl")] use crate::handlers::config::DeleteConfigCommand; #[cfg(feature = "message_signer")] use crate::handlers::offline::{SignMessageCommand, VerifyMessageCommand};