Skip to content

Commit 306069a

Browse files
committed
ref(handlers): refactor config, descr and key mods
- move execution logic into config, desc and key modules
1 parent 389bf5c commit 306069a

3 files changed

Lines changed: 355 additions & 268 deletions

File tree

src/handlers/config.rs

Lines changed: 133 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,158 @@
11
use std::collections::HashMap;
2-
use std::path::Path;
3-
4-
#[cfg(any(feature = "sqlite", feature = "redb"))]
5-
#[cfg(feature = "sqlite")]
6-
use crate::commands::DatabaseType;
7-
use crate::commands::WalletOpts;
8-
use crate::config::{WalletConfig, WalletConfigInner};
9-
use crate::error::BDKCliError as Error;
10-
use bdk_wallet::bitcoin::Network;
11-
use serde_json::json;
122

133
#[cfg(any(
144
feature = "electrum",
155
feature = "esplora",
166
feature = "rpc",
177
feature = "cbf"
188
))]
19-
use crate::commands::ClientType;
20-
21-
/// Handle wallet config subcommand to create or update config.toml
22-
pub fn handle_config_subcommand(
23-
datadir: &Path,
24-
network: Network,
25-
wallet: String,
26-
wallet_opts: &WalletOpts,
27-
force: bool,
28-
) -> Result<String, Error> {
29-
if network == Network::Bitcoin {
30-
eprintln!(
31-
"WARNING: You are configuring a wallet for Bitcoin MAINNET.
32-
This software is experimental and not recommended for use with real funds.
33-
Consider using a testnet for testing purposes. \n"
34-
);
35-
}
9+
use crate::client::ClientType;
10+
use crate::commands::WalletOpts;
11+
use crate::config::{WalletConfig, WalletConfigInner};
12+
use crate::error::BDKCliError as Error;
13+
use crate::handlers::{AppCommand, AppContext};
14+
#[cfg(feature = "sqlite")]
15+
use crate::persister::DatabaseType;
16+
use crate::utils::types::{StatusResult, WalletsListResult};
17+
use bdk_wallet::bitcoin::Network;
18+
use clap::Args;
19+
20+
#[derive(Args, Debug, Clone, PartialEq)]
21+
pub struct SaveConfigCommand {
22+
/// Overwrite existing wallet configuration if it exists.
23+
#[arg(short = 'f', long = "force", default_value_t = false)]
24+
pub(crate) force: bool,
25+
26+
#[command(flatten)]
27+
pub(crate) wallet_opts: WalletOpts,
28+
}
29+
30+
impl AppCommand for SaveConfigCommand {
31+
type Output = StatusResult;
32+
33+
fn execute(&self, ctx: &mut AppContext<'_>) -> Result<Self::Output, Error> {
34+
if ctx.network == Network::Bitcoin {
35+
eprintln!("WARNING: Configuring for Bitcoin MAINNET. Experimental software!");
36+
}
37+
38+
let wallet_name = match &self.wallet_opts.wallet {
39+
Some(wallet) => wallet,
40+
None => return Err(Error::Generic("wallet is required".to_owned())),
41+
};
3642

37-
let ext_descriptor = wallet_opts.ext_descriptor.clone();
38-
let int_descriptor = wallet_opts.int_descriptor.clone();
43+
let ext_descriptor = self.wallet_opts.ext_descriptor.clone();
44+
let int_descriptor = self.wallet_opts.int_descriptor.clone();
3945

40-
if ext_descriptor.contains("xprv") || ext_descriptor.contains("tprv") {
41-
eprintln!(
42-
"WARNING: Your external descriptor contains PRIVATE KEYS.
46+
if ext_descriptor.contains("xprv") || ext_descriptor.contains("tprv") {
47+
eprintln!(
48+
"WARNING: Your external descriptor contains PRIVATE KEYS.
4349
Private keys will be saved in PLAINTEXT in the config file.
4450
This is a security risk. Consider using public descriptors instead.\n"
45-
);
46-
}
51+
);
52+
}
4753

48-
if let Some(ref internal_desc) = int_descriptor
49-
&& (internal_desc.contains("xprv") || internal_desc.contains("tprv"))
50-
{
51-
eprintln!(
52-
"WARNING: Your internal descriptor contains PRIVATE KEYS.
54+
if let Some(ref internal_desc) = int_descriptor
55+
&& (internal_desc.contains("xprv") || internal_desc.contains("tprv"))
56+
{
57+
eprintln!(
58+
"WARNING: Your internal descriptor contains PRIVATE KEYS.
5359
Private keys will be saved in PLAINTEXT in the config file.
5460
This is a security risk. Consider using public descriptors instead.\n"
55-
);
56-
}
61+
);
62+
}
5763

58-
let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
59-
wallets: HashMap::new(),
60-
});
64+
let mut config = WalletConfig::load(&ctx.datadir)?.unwrap_or(WalletConfig {
65+
wallets: HashMap::new(),
66+
});
6167

62-
if config.wallets.contains_key(&wallet) && !force {
63-
return Err(Error::Generic(format!(
64-
"Wallet '{wallet}' already exists in config.toml. Use --force to overwrite."
65-
)));
66-
}
68+
if config.wallets.contains_key(wallet_name.as_str()) && !self.force {
69+
return Err(Error::Generic(format!(
70+
"Wallet '{}' already exists. Use --force to overwrite.",
71+
wallet_name
72+
)));
73+
};
6774

68-
#[cfg(any(
69-
feature = "electrum",
70-
feature = "esplora",
71-
feature = "rpc",
72-
feature = "cbf"
73-
))]
74-
let client_type = wallet_opts.client_type.clone();
75-
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
76-
let url = &wallet_opts.url.clone();
77-
#[cfg(any(feature = "sqlite", feature = "redb"))]
78-
let database_type = match wallet_opts.database_type {
79-
#[cfg(feature = "sqlite")]
80-
DatabaseType::Sqlite => "sqlite".to_string(),
81-
#[cfg(feature = "redb")]
82-
DatabaseType::Redb => "redb".to_string(),
83-
};
84-
85-
#[cfg(any(
86-
feature = "electrum",
87-
feature = "esplora",
88-
feature = "rpc",
89-
feature = "cbf"
90-
))]
91-
let client_type = match client_type {
92-
#[cfg(feature = "electrum")]
93-
ClientType::Electrum => "electrum".to_string(),
94-
#[cfg(feature = "esplora")]
95-
ClientType::Esplora => "esplora".to_string(),
96-
#[cfg(feature = "rpc")]
97-
ClientType::Rpc => "rpc".to_string(),
98-
#[cfg(feature = "cbf")]
99-
ClientType::Cbf => "cbf".to_string(),
100-
};
101-
102-
let wallet_config = WalletConfigInner {
103-
wallet: wallet.clone(),
104-
network: network.to_string(),
105-
ext_descriptor,
106-
int_descriptor,
107-
#[cfg(any(feature = "sqlite", feature = "redb"))]
108-
database_type,
10975
#[cfg(any(
11076
feature = "electrum",
11177
feature = "esplora",
11278
feature = "rpc",
11379
feature = "cbf"
11480
))]
115-
client_type: Some(client_type),
116-
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc",))]
117-
server_url: Some(url.to_string()),
118-
#[cfg(feature = "rpc")]
119-
rpc_user: Some(wallet_opts.basic_auth.0.clone()),
120-
#[cfg(feature = "rpc")]
121-
rpc_password: Some(wallet_opts.basic_auth.1.clone()),
122-
#[cfg(feature = "electrum")]
123-
batch_size: Some(wallet_opts.batch_size),
124-
#[cfg(feature = "esplora")]
125-
parallel_requests: Some(wallet_opts.parallel_requests),
126-
#[cfg(feature = "rpc")]
127-
cookie: wallet_opts.cookie.clone(),
128-
};
129-
130-
config.wallets.insert(wallet.clone(), wallet_config);
131-
config.save(datadir)?;
132-
133-
Ok(serde_json::to_string_pretty(&json!({
134-
"message": format!("Wallet '{wallet}' initialized successfully in {:?}", datadir.join("config.toml"))
135-
}))?)
81+
let client_type = match self.wallet_opts.client_type.clone() {
82+
#[cfg(feature = "electrum")]
83+
ClientType::Electrum => "electrum".to_string(),
84+
#[cfg(feature = "esplora")]
85+
ClientType::Esplora => "esplora".to_string(),
86+
#[cfg(feature = "rpc")]
87+
ClientType::Rpc => "rpc".to_string(),
88+
#[cfg(feature = "cbf")]
89+
ClientType::Cbf => "cbf".to_string(),
90+
};
91+
92+
let wallet_config = WalletConfigInner {
93+
wallet: wallet_name.clone(),
94+
network: ctx.network.to_string(),
95+
ext_descriptor: self.wallet_opts.ext_descriptor.clone(),
96+
int_descriptor: self.wallet_opts.int_descriptor.clone(),
97+
98+
#[cfg(any(feature = "sqlite", feature = "redb"))]
99+
database_type: match self.wallet_opts.database_type {
100+
#[cfg(feature = "sqlite")]
101+
DatabaseType::Sqlite => "sqlite".to_string(),
102+
#[cfg(feature = "redb")]
103+
DatabaseType::Redb => "redb".to_string(),
104+
},
105+
106+
#[cfg(any(
107+
feature = "electrum",
108+
feature = "esplora",
109+
feature = "rpc",
110+
feature = "cbf"
111+
))]
112+
client_type: Some(client_type),
113+
114+
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
115+
server_url: Some(self.wallet_opts.url.clone()),
116+
117+
#[cfg(feature = "rpc")]
118+
rpc_user: Some(self.wallet_opts.basic_auth.0.clone()),
119+
#[cfg(feature = "rpc")]
120+
rpc_password: Some(self.wallet_opts.basic_auth.1.clone()),
121+
#[cfg(feature = "electrum")]
122+
batch_size: Some(self.wallet_opts.batch_size),
123+
#[cfg(feature = "esplora")]
124+
parallel_requests: Some(self.wallet_opts.parallel_requests),
125+
#[cfg(feature = "rpc")]
126+
cookie: self.wallet_opts.cookie.clone(),
127+
};
128+
129+
config.wallets.insert(wallet_name.clone(), wallet_config);
130+
config
131+
.save(&ctx.datadir)
132+
.map_err(|error| Error::Generic(error.to_string()))?;
133+
134+
Ok(StatusResult {
135+
message: format!(
136+
"Wallet '{}' initialized successfully in {:?}",
137+
wallet_name,
138+
ctx.datadir.join("config.toml")
139+
),
140+
})
141+
}
142+
}
143+
144+
#[derive(Args, Debug, Clone, PartialEq)]
145+
pub struct ListWalletsCommand {}
146+
147+
impl AppCommand for ListWalletsCommand {
148+
type Output = WalletsListResult;
149+
150+
fn execute(&self, ctx: &mut AppContext) -> Result<Self::Output, Error> {
151+
let config = match WalletConfig::load(&ctx.datadir)? {
152+
Some(cfg) => cfg,
153+
None => return Err(Error::Generic("No wallets configured yet.".into())),
154+
};
155+
156+
Ok(WalletsListResult(config.wallets))
157+
}
136158
}

0 commit comments

Comments
 (0)