//! 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;
/// 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,
},
/// 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,
},
/// 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",
#[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>,
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)]
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",
}
}
-/// 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",
};
let wallet_config = WalletConfigInner {
- wallet: wallet_name.to_string(),
+ wallet: wallet.clone(),
network: network.to_string(),
ext_descriptor,
int_descriptor,
};
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"))
}))?)
}
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 = {
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");
}
#[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)
}
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 {
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");
}
#[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();
let result = respond(
network,
&mut wallet,
- wallet_opts,
+ &wallet_name,
+ &mut wallet_opts.clone(),
line,
database_path.clone(),
&cli_opts,
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,
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 } => {
#[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)
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)
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)
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(),
- )),
}
}