|
1 | 1 | 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; |
12 | 2 |
|
13 | 3 | #[cfg(any( |
14 | 4 | feature = "electrum", |
15 | 5 | feature = "esplora", |
16 | 6 | feature = "rpc", |
17 | 7 | feature = "cbf" |
18 | 8 | ))] |
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 | + }; |
36 | 42 |
|
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(); |
39 | 45 |
|
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. |
43 | 49 | Private keys will be saved in PLAINTEXT in the config file. |
44 | 50 | This is a security risk. Consider using public descriptors instead.\n" |
45 | | - ); |
46 | | - } |
| 51 | + ); |
| 52 | + } |
47 | 53 |
|
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. |
53 | 59 | Private keys will be saved in PLAINTEXT in the config file. |
54 | 60 | This is a security risk. Consider using public descriptors instead.\n" |
55 | | - ); |
56 | | - } |
| 61 | + ); |
| 62 | + } |
57 | 63 |
|
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 | + }); |
61 | 67 |
|
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 | + }; |
67 | 74 |
|
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, |
109 | 75 | #[cfg(any( |
110 | 76 | feature = "electrum", |
111 | 77 | feature = "esplora", |
112 | 78 | feature = "rpc", |
113 | 79 | feature = "cbf" |
114 | 80 | ))] |
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 | + } |
136 | 158 | } |
0 commit comments