Skip to content

Commit 424b9f5

Browse files
committed
feat(init-wallet): rename init & add walletopts
- rename init to config and move walletopts as config options
1 parent b105292 commit 424b9f5

File tree

4 files changed

+99
-154
lines changed

4 files changed

+99
-154
lines changed

src/commands.rs

Lines changed: 18 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@
1313
//! All subcommands are defined in the below enums.
1414
1515
#![allow(clippy::large_enum_variant)]
16-
17-
use crate::config::WalletConfig;
18-
use crate::error::BDKCliError as Error;
1916
use bdk_wallet::bitcoin::{
2017
Address, Network, OutPoint, ScriptBuf,
2118
bip32::{DerivationPath, Xpriv},
2219
};
2320
use clap::{Args, Parser, Subcommand, ValueEnum, value_parser};
24-
use std::path::Path;
2521

2622
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
2723
use crate::utils::parse_proxy_auth;
@@ -73,8 +69,10 @@ pub enum CliSubCommand {
7369
/// needs backend like `sync` and `broadcast`, compile the binary with specific backend feature
7470
/// and use the configuration options below to configure for that backend.
7571
Wallet {
76-
#[command(flatten)]
77-
wallet_opts: WalletOpts,
72+
/// Selects the wallet to use.
73+
#[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
74+
wallet: String,
75+
7876
#[command(subcommand)]
7977
subcommand: WalletSubCommand,
8078
},
@@ -107,6 +105,10 @@ pub enum CliSubCommand {
107105
/// REPL command loop can be used to make recurring callbacks to an already loaded wallet.
108106
/// This mode is useful for hands on live testing of wallet operations.
109107
Repl {
108+
/// Wallet name for this REPL session
109+
#[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
110+
wallet: String,
111+
110112
#[command(flatten)]
111113
wallet_opts: WalletOpts,
112114
},
@@ -115,11 +117,14 @@ pub enum CliSubCommand {
115117
/// Wallet operation subcommands.
116118
#[derive(Debug, Subcommand, Clone, PartialEq)]
117119
pub enum WalletSubCommand {
118-
/// Initialize a wallet configuration and save to `config.toml`.
119-
Init {
120+
/// Save wallet configuration to `config.toml`.
121+
Config {
120122
/// Overwrite existing wallet configuration if it exists.
121-
#[arg(long = "force", default_value_t = false)]
123+
#[arg(short = 'f', long = "force", default_value_t = false)]
122124
force: bool,
125+
126+
#[command(flatten)]
127+
wallet_opts: WalletOpts,
123128
},
124129
#[cfg(any(
125130
feature = "electrum",
@@ -165,14 +170,15 @@ pub enum ClientType {
165170
#[derive(Debug, Args, Clone, PartialEq, Eq)]
166171
pub struct WalletOpts {
167172
/// Selects the wallet to use.
168-
#[arg(env = "WALLET_NAME", short = 'w', long = "wallet")]
173+
#[arg(skip)]
169174
pub wallet: Option<String>,
175+
// #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)]
170176
/// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects.
171177
#[arg(env = "VERBOSE", short = 'v', long = "verbose")]
172178
pub verbose: bool,
173179
/// Sets the descriptor to use for the external addresses.
174-
#[arg(env = "EXT_DESCRIPTOR", short = 'e', long)]
175-
pub ext_descriptor: Option<String>,
180+
#[arg(env = "EXT_DESCRIPTOR", short = 'e', long, required = true)]
181+
pub ext_descriptor: String,
176182
/// Sets the descriptor to use for internal/change addresses.
177183
#[arg(env = "INT_DESCRIPTOR", short = 'i', long)]
178184
pub int_descriptor: Option<String>,
@@ -223,56 +229,6 @@ pub struct WalletOpts {
223229
pub compactfilter_opts: CompactFilterOpts,
224230
}
225231

226-
impl WalletOpts {
227-
/// Merges optional configuration values from config.toml into the current WalletOpts.
228-
pub fn load_config(&mut self, wallet_name: &str, datadir: &Path) -> Result<(), Error> {
229-
if let Some(config) = WalletConfig::load(datadir)? {
230-
if let Ok(config_opts) = config.get_wallet_opts(wallet_name) {
231-
self.verbose = self.verbose || config_opts.verbose;
232-
#[cfg(feature = "electrum")]
233-
{
234-
self.batch_size = if self.batch_size != 10 {
235-
self.batch_size
236-
} else {
237-
config_opts.batch_size
238-
};
239-
}
240-
#[cfg(feature = "esplora")]
241-
{
242-
self.parallel_requests = if self.parallel_requests != 5 {
243-
self.parallel_requests
244-
} else {
245-
config_opts.parallel_requests
246-
};
247-
}
248-
#[cfg(feature = "rpc")]
249-
{
250-
self.basic_auth = if self.basic_auth != ("user".into(), "password".into()) {
251-
self.basic_auth.clone()
252-
} else {
253-
config_opts.basic_auth
254-
};
255-
self.cookie = self.cookie.take().or(config_opts.cookie);
256-
}
257-
#[cfg(feature = "cbf")]
258-
{
259-
if self.compactfilter_opts.conn_count == 2
260-
&& config_opts.compactfilter_opts.conn_count != 2
261-
{
262-
self.compactfilter_opts.conn_count =
263-
config_opts.compactfilter_opts.conn_count;
264-
}
265-
if self.compactfilter_opts.skip_blocks.is_none() {
266-
self.compactfilter_opts.skip_blocks =
267-
config_opts.compactfilter_opts.skip_blocks;
268-
}
269-
}
270-
}
271-
}
272-
Ok(())
273-
}
274-
}
275-
276232
/// Options to configure a SOCKS5 proxy for a blockchain client connection.
277233
#[cfg(any(feature = "electrum", feature = "esplora"))]
278234
#[derive(Debug, Args, Clone, PartialEq, Eq)]

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl WalletConfig {
127127
Ok(WalletOpts {
128128
wallet: Some(wallet_config.wallet.clone()),
129129
verbose: false,
130-
ext_descriptor: Some(wallet_config.ext_descriptor.clone()),
130+
ext_descriptor: wallet_config.ext_descriptor.clone(),
131131
int_descriptor: wallet_config.int_descriptor.clone(),
132132
#[cfg(any(
133133
feature = "electrum",

src/handlers.rs

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use bdk_wallet::rusqlite::Connection;
4141
use bdk_wallet::{KeychainKind, SignOptions, Wallet};
4242
#[cfg(feature = "compiler")]
4343
use bdk_wallet::{
44+
bitcoin::XOnlyPublicKey,
4445
descriptor::{Descriptor, Legacy, Miniscript},
4546
miniscript::{Tap, descriptor::TapTree, policy::Concrete},
4647
};
@@ -850,33 +851,26 @@ pub(crate) async fn handle_online_wallet_subcommand(
850851
}
851852
}
852853

853-
/// Handle wallet init subcommand to create or update config.toml
854-
pub fn handle_init_subcommand(
854+
/// Handle wallet config subcommand to create or update config.toml
855+
pub fn handle_config_subcommand(
855856
datadir: &Path,
856857
network: Network,
858+
wallet: String,
857859
wallet_opts: &WalletOpts,
858860
force: bool,
859861
) -> Result<String, Error> {
860-
let wallet_name = wallet_opts
861-
.wallet
862-
.as_ref()
863-
.ok_or_else(|| Error::Generic("Wallet name is required".to_string()))?;
864-
865862
let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
866863
network,
867864
wallets: HashMap::new(),
868865
});
869866

870-
if config.wallets.contains_key(wallet_name) && !force {
867+
if config.wallets.contains_key(&wallet) && !force {
871868
return Err(Error::Generic(format!(
872-
"Wallet '{wallet_name}' already exists in config.toml. Use --force to overwrite."
869+
"Wallet '{wallet}' already exists in config.toml. Use --force to overwrite."
873870
)));
874871
}
875872

876-
let ext_descriptor = wallet_opts
877-
.ext_descriptor
878-
.clone()
879-
.ok_or_else(|| Error::Generic("External descriptor is required".to_string()))?;
873+
let ext_descriptor = wallet_opts.ext_descriptor.clone();
880874
let int_descriptor = wallet_opts.int_descriptor.clone();
881875
#[cfg(any(
882876
feature = "electrum",
@@ -913,7 +907,7 @@ pub fn handle_init_subcommand(
913907
};
914908

915909
let wallet_config = WalletConfigInner {
916-
wallet: wallet_name.to_string(),
910+
wallet: wallet.clone(),
917911
network: network.to_string(),
918912
ext_descriptor,
919913
int_descriptor,
@@ -941,13 +935,11 @@ pub fn handle_init_subcommand(
941935
};
942936

943937
config.network = network;
944-
config
945-
.wallets
946-
.insert(wallet_name.to_string(), wallet_config);
938+
config.wallets.insert(wallet.clone(), wallet_config);
947939
config.save(datadir)?;
948940

949941
Ok(serde_json::to_string_pretty(&json!({
950-
"message": format!("Wallet '{wallet_name}' initialized successfully in {:?}", datadir.join("config.toml"))
942+
"message": format!("Wallet '{wallet}' initialized successfully in {:?}", datadir.join("config.toml"))
951943
}))?)
952944
}
953945

@@ -1165,11 +1157,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
11651157
feature = "rpc"
11661158
))]
11671159
CliSubCommand::Wallet {
1168-
mut wallet_opts,
1160+
wallet,
11691161
subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
11701162
} => {
11711163
let home_dir = prepare_home_dir(cli_opts.datadir)?;
1172-
let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts)?;
1164+
1165+
let config = WalletConfig::load(&home_dir)?
1166+
.ok_or(Error::Generic("No config found".to_string()))?;
1167+
let wallet_opts = config.get_wallet_opts(&wallet)?;
1168+
let database_path = prepare_wallet_db_dir(&home_dir, &wallet)?;
11731169

11741170
#[cfg(any(feature = "sqlite", feature = "redb"))]
11751171
let result = {
@@ -1221,34 +1217,33 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12211217
Ok(result)
12221218
}
12231219
CliSubCommand::Wallet {
1224-
mut wallet_opts,
1220+
wallet: wallet_name,
12251221
subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
12261222
} => {
12271223
let network = cli_opts.network;
12281224
let datadir = cli_opts.datadir.clone();
1225+
let home_dir = prepare_home_dir(datadir)?;
1226+
let config = WalletConfig::load(&home_dir)?.ok_or(Error::Generic(format!(
1227+
"No config found for wallet '{wallet_name}'"
1228+
)))?;
1229+
let wallet_opts = config.get_wallet_opts(&wallet_name)?;
12291230
#[cfg(any(feature = "sqlite", feature = "redb"))]
12301231
let result = {
1231-
let home_dir = prepare_home_dir(datadir)?;
12321232
let mut persister: Persister = match &wallet_opts.database_type {
12331233
#[cfg(feature = "sqlite")]
12341234
DatabaseType::Sqlite => {
1235-
let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts)?;
1235+
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
12361236
let db_file = database_path.join("wallet.sqlite");
12371237
let connection = Connection::open(db_file)?;
12381238
log::debug!("Sqlite database opened successfully");
12391239
Persister::Connection(connection)
12401240
}
12411241
#[cfg(feature = "redb")]
12421242
DatabaseType::Redb => {
1243-
let wallet_name = &wallet_opts.wallet;
1244-
12451243
let db = Arc::new(bdk_redb::redb::Database::create(
12461244
home_dir.join("wallet.redb"),
12471245
)?);
1248-
let store = RedbStore::new(
1249-
db,
1250-
wallet_name.as_deref().unwrap_or("wallet").to_string(),
1251-
)?;
1246+
let store = RedbStore::new(db, wallet_name)?;
12521247
log::debug!("Redb database opened successfully");
12531248
Persister::RedbStore(store)
12541249
}
@@ -1278,12 +1273,12 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12781273
Ok(result)
12791274
}
12801275
CliSubCommand::Wallet {
1281-
wallet_opts,
1282-
subcommand: WalletSubCommand::Init { force },
1276+
wallet,
1277+
subcommand: WalletSubCommand::Config { force, wallet_opts },
12831278
} => {
12841279
let network = cli_opts.network;
12851280
let home_dir = prepare_home_dir(cli_opts.datadir)?;
1286-
let result = handle_init_subcommand(&home_dir, network, &wallet_opts, force)?;
1281+
let result = handle_config_subcommand(&home_dir, network, wallet, &wallet_opts, force)?;
12871282
Ok(result)
12881283
}
12891284
CliSubCommand::Key {
@@ -1301,43 +1296,48 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13011296
Ok(result)
13021297
}
13031298
#[cfg(feature = "repl")]
1304-
CliSubCommand::Repl { ref wallet_opts } => {
1299+
CliSubCommand::Repl {
1300+
wallet: wallet_name,
1301+
mut wallet_opts,
1302+
} => {
13051303
let network = cli_opts.network;
1304+
let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
1305+
wallet_opts.wallet = Some(wallet_name.clone());
1306+
1307+
let config = WalletConfig::load(&home_dir)?.ok_or(Error::Generic(format!(
1308+
"No config found for wallet {}",
1309+
wallet_name.clone()
1310+
)))?;
1311+
let loaded_wallet_opts = config.get_wallet_opts(&wallet_name)?;
1312+
13061313
#[cfg(any(feature = "sqlite", feature = "redb"))]
13071314
let (mut wallet, mut persister) = {
1308-
let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
1309-
1310-
let mut persister: Persister = match &wallet_opts.database_type {
1315+
let mut persister: Persister = match &loaded_wallet_opts.database_type {
13111316
#[cfg(feature = "sqlite")]
13121317
DatabaseType::Sqlite => {
1313-
let database_path =
1314-
prepare_wallet_db_dir(&home_dir, &mut wallet_opts.clone())?;
1318+
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
13151319
let db_file = database_path.join("wallet.sqlite");
13161320
let connection = Connection::open(db_file)?;
13171321
log::debug!("Sqlite database opened successfully");
13181322
Persister::Connection(connection)
13191323
}
13201324
#[cfg(feature = "redb")]
13211325
DatabaseType::Redb => {
1322-
let wallet_name = &wallet_opts.wallet;
13231326
let db = Arc::new(bdk_redb::redb::Database::create(
13241327
home_dir.join("wallet.redb"),
13251328
)?);
1326-
let store = RedbStore::new(
1327-
db,
1328-
wallet_name.as_deref().unwrap_or("wallet").to_string(),
1329-
)?;
1329+
let store = RedbStore::new(db, wallet_name.clone())?;
13301330
log::debug!("Redb database opened successfully");
13311331
Persister::RedbStore(store)
13321332
}
13331333
};
1334-
let wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1334+
let wallet = new_persisted_wallet(network, &mut persister, &loaded_wallet_opts)?;
13351335
(wallet, persister)
13361336
};
13371337
#[cfg(not(any(feature = "sqlite", feature = "redb")))]
1338-
let mut wallet = new_wallet(network, &wallet_opts)?;
1338+
let mut wallet = new_wallet(network, &loaded_wallet_opts)?;
13391339
let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
1340-
let database_path = prepare_wallet_db_dir(&home_dir, &mut wallet_opts.clone())?;
1340+
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
13411341
loop {
13421342
let line = readline()?;
13431343
let line = line.trim();
@@ -1348,7 +1348,8 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13481348
let result = respond(
13491349
network,
13501350
&mut wallet,
1351-
wallet_opts,
1351+
&wallet_name,
1352+
&mut wallet_opts.clone(),
13521353
line,
13531354
database_path.clone(),
13541355
&cli_opts,
@@ -1382,7 +1383,8 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13821383
async fn respond(
13831384
network: Network,
13841385
wallet: &mut Wallet,
1385-
wallet_opts: &WalletOpts,
1386+
wallet_name: &String,
1387+
wallet_opts: &mut WalletOpts,
13861388
line: &str,
13871389
_datadir: std::path::PathBuf,
13881390
cli_opts: &CliOpts,
@@ -1417,10 +1419,16 @@ async fn respond(
14171419
Some(value)
14181420
}
14191421
ReplSubCommand::Wallet {
1420-
subcommand: WalletSubCommand::Init { force },
1422+
subcommand: WalletSubCommand::Config { force, wallet_opts },
14211423
} => {
1422-
let value = handle_init_subcommand(&_datadir, network, wallet_opts, force)
1423-
.map_err(|e| e.to_string())?;
1424+
let value = handle_config_subcommand(
1425+
&_datadir,
1426+
network,
1427+
wallet_name.to_string(),
1428+
&wallet_opts,
1429+
force,
1430+
)
1431+
.map_err(|e| e.to_string())?;
14241432
Some(value)
14251433
}
14261434
ReplSubCommand::Key { subcommand } => {

0 commit comments

Comments
 (0)