Skip to content

Commit 53a6566

Browse files
committed
refactor(main): add runtime wallet module
- add wallet runtime module to serve as context manager
1 parent 1f37ef2 commit 53a6566

10 files changed

Lines changed: 234 additions & 143 deletions

File tree

src/client.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#[cfg(feature = "rpc")]
2-
use bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi};
2+
use {bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi},
3+
bdk_wallet::chain::CanonicalizationParams,
4+
};
35
#[cfg(feature = "esplora")]
46
use bdk_esplora::EsploraAsyncExt;
57
#[cfg(any(
@@ -14,7 +16,6 @@ use {
1416
bdk_wallet::{
1517
Wallet,
1618
bitcoin::{Transaction, Txid},
17-
chain::CanonicalizationParams,
1819
},
1920
clap::ValueEnum,
2021
std::path::PathBuf,
@@ -79,7 +80,7 @@ pub(crate) enum BlockchainClient {
7980
impl BlockchainClient {
8081
pub async fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
8182
match self {
82-
// #[cfg(feature = "electrum")]
83+
#[cfg(feature = "electrum")]
8384
Self::Electrum { client, .. } => client
8485
.transaction_broadcast(&tx)
8586
.map_err(|e| Error::Generic(e.to_string())),

src/handlers/mod.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,6 @@ pub trait AppCommand<C> {
102102
))]
103103
pub trait AsyncAppCommand<C> {
104104
type Output: FormatOutput;
105-
105+
106106
async fn execute(&self, ctx: &mut C) -> Result<Self::Output, Error>;
107107
}
108-
109-
// context for online and online
110-
// => cli.rs
111-
// handlers/{mod for commands}
112-
// wallet subdir /
113-
// wallet-offline and wallet-online (client mod)

src/handlers/online.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {
170170
sync_kyoto_client(wallet, client).await?;
171171
}
172172
}
173-
Ok(StatusResult::new("Full scan completed successfully."))
173+
Ok(StatusResult {
174+
message: "Full scan completed successfully.".to_string(),
175+
})
174176
}
175177
}
176178

@@ -190,7 +192,7 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for SyncCommand {
190192
&self,
191193
ctx: &mut AppContext<OnlineOperations<'_>>,
192194
) -> Result<Self::Output, Error> {
193-
let mut wallet = &mut ctx.state.wallet;
195+
let wallet = &mut ctx.state.wallet;
194196
let client = ctx.state.client;
195197
#[cfg(any(feature = "electrum", feature = "esplora"))]
196198
let request = wallet
@@ -267,12 +269,14 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for SyncCommand {
267269
let mempool_txs = emitter.mempool()?;
268270
wallet.apply_unconfirmed_txs(mempool_txs.update);
269271
}
270-
// #[cfg(feature = "cbf")]
271-
KyotoClient { client } => sync_kyoto_client(&mut wallet, client)
272+
#[cfg(feature = "cbf")]
273+
KyotoClient { client } => sync_kyoto_client(wallet, client)
272274
.await
273275
.map_err(|e| Error::Generic(e.to_string()))?,
274276
}
275-
Ok(StatusResult::new("Wallet synced successfully."))
277+
Ok(StatusResult {
278+
message: "Wallet synced successfully.".to_string(),
279+
})
276280
}
277281
}
278282

src/handlers/repl.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use bdk_wallet::{Wallet, bitcoin::Network};
2-
3-
#[cfg(feature = "repl")]
4-
use crate::handlers::AppCommand;
51
#[cfg(feature = "repl")]
6-
use crate::{handlers::AppContext, utils::output::FormatOutput};
2+
use {bdk_wallet::{Wallet, bitcoin::Network},
3+
crate::handlers::{AppCommand, AppContext},
4+
crate::utils::output::FormatOutput,
5+
clap::Parser,
6+
crate::commands::ReplSubCommand,
7+
};
78

8-
#[cfg(feature = "repl")]
9-
use crate::commands::ReplSubCommand;
10-
use clap::Parser;
119

1210
#[cfg(any(
1311
feature = "electrum",

src/main.rs

Lines changed: 46 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,12 @@ use bdk_redb::Store as RedbStore;
3030
use bdk_wallet::bitcoin::Network;
3131
use log::{debug, warn};
3232

33-
#[cfg(any(
34-
feature = "electrum",
35-
feature = "esplora",
36-
feature = "rpc",
37-
feature = "cbf"
38-
))]
39-
use crate::client::new_blockchain_client;
4033
use crate::commands::{CliOpts, CliSubCommand, WalletSubCommand};
4134
use crate::error::BDKCliError as Error;
4235
use crate::handlers::{AppCommand, AppContext};
43-
#[cfg(any(feature = "sqlite", feature = "redb"))]
44-
use crate::persister::{Persister, new_persisted_wallet};
4536
use crate::utils::output::FormatOutput;
46-
use crate::utils::prepare_wallet_db_dir;
47-
use crate::utils::{load_wallet_config, prepare_home_dir};
37+
use crate::utils::runtime::WalletRuntime;
38+
use crate::utils::{command_requires_db, prepare_home_dir};
4839
use clap::Parser;
4940

5041
#[tokio::main]
@@ -81,70 +72,36 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
8172
feature = "rpc",
8273
feature = "cbf"
8374
))]
84-
WalletSubCommand::OnlineWalletSubCommand(online_cmd) => {
85-
let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
86-
87-
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
88-
#[cfg(any(feature = "sqlite", feature = "redb"))]
89-
let mut persister: Persister = match &wallet_opts.database_type {
90-
#[cfg(feature = "sqlite")]
91-
crate::persister::DatabaseType::Sqlite => {
92-
let db_file = database_path.join("wallet.sqlite");
93-
let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
94-
Persister::Connection(connection)
95-
}
96-
#[cfg(feature = "redb")]
97-
crate::persister::DatabaseType::Redb => {
98-
use crate::persister::Persister;
99-
100-
let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
101-
home_dir.join("wallet.redb"),
102-
)?);
103-
let store = RedbStore::new(db, wallet_name)?;
104-
log::debug!("Redb database opened successfully");
105-
Persister::RedbStore(store)
106-
}
107-
};
75+
WalletSubCommand::OnlineWalletSubCommand(cmd) => {
76+
let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
10877

109-
let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
78+
let mut wallet = runtime.build_wallet(true)?;
79+
let client = runtime.build_client(&wallet)?;
11080

111-
let client = new_blockchain_client(&wallet_opts, &wallet, database_path)?;
112-
113-
let mut ctx =
114-
AppContext::new_online_wallet(network, home_dir, &mut wallet, &client);
81+
let mut ctx = AppContext::new_online_wallet(
82+
runtime.network,
83+
runtime.home_dir.clone(),
84+
&mut wallet,
85+
&client,
86+
);
11587

116-
online_cmd.execute(&mut ctx).await?;
88+
cmd.execute(&mut ctx).await?;
11789
}
118-
WalletSubCommand::OfflineWalletSubCommand(offline_cmd) => {
119-
let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
120-
121-
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
122-
123-
#[cfg(any(feature = "sqlite", feature = "redb"))]
124-
let mut persister: Persister = match &wallet_opts.database_type {
125-
#[cfg(feature = "sqlite")]
126-
crate::persister::DatabaseType::Sqlite => {
127-
let db_file = database_path.join("wallet.sqlite");
128-
let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
129-
Persister::Connection(connection)
130-
}
131-
#[cfg(feature = "redb")]
132-
crate::persister::DatabaseType::Redb => {
133-
use crate::persister::Persister;
134-
let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
135-
home_dir.join("wallet.redb"),
136-
)?);
137-
let store = RedbStore::new(db, wallet_name)?;
138-
Persister::RedbStore(store)
139-
}
140-
};
14190

142-
let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
91+
WalletSubCommand::OfflineWalletSubCommand(cmd) => {
92+
let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
14393

144-
let mut ctx = AppContext::new_offline_wallet(network, home_dir, &mut wallet);
94+
let mut wallet = runtime.build_wallet(command_requires_db(&cmd))?;
95+
96+
let mut ctx = AppContext::new_offline_wallet(
97+
runtime.network,
98+
runtime.home_dir.clone(),
99+
&mut wallet,
100+
);
145101

146-
offline_cmd.execute(&mut ctx)?;
102+
cmd.execute(&mut ctx)?;
147103
}
104+
148105
WalletSubCommand::Config(mut config_cmd) => {
149106
config_cmd.wallet_opts.wallet = Some(wallet_name);
150107

@@ -156,107 +113,87 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
156113

157114
CliSubCommand::Key { subcommand } => {
158115
let mut ctx = AppContext::new(cli_opts.network, home_dir);
116+
159117
subcommand.execute(&mut ctx)?;
160118
}
161-
CliSubCommand::Descriptor(descriptor_command) => {
119+
120+
CliSubCommand::Descriptor(cmd) => {
162121
let mut ctx = AppContext::new(cli_opts.network, home_dir);
163-
descriptor_command
164-
.execute(&mut ctx)?
165-
.write_out(std::io::stdout())?;
122+
123+
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
166124
}
125+
167126
CliSubCommand::Wallets(cmd) => {
168127
let mut ctx = AppContext::new(cli_opts.network, home_dir);
128+
169129
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
170130
}
131+
171132
CliSubCommand::Repl {
172133
wallet: wallet_name,
173134
} => {
174135
#[cfg(feature = "repl")]
175136
{
176-
let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
177-
let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
178-
179-
#[cfg(any(feature = "sqlite", feature = "redb"))]
180-
let mut persister: Persister = match &wallet_opts.database_type {
181-
#[cfg(feature = "sqlite")]
182-
crate::persister::DatabaseType::Sqlite => {
183-
let db_file = database_path.join("wallet.sqlite");
184-
let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
185-
Persister::Connection(connection)
186-
}
187-
#[cfg(feature = "redb")]
188-
crate::persister::DatabaseType::Redb => {
189-
use crate::persister::Persister;
190-
let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
191-
home_dir.join("wallet.redb"),
192-
)?);
193-
let store = RedbStore::new(db, wallet_name.clone())?;
194-
Persister::RedbStore(store)
195-
}
196-
};
137+
let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
197138

198-
let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
139+
let mut wallet = runtime.build_wallet(true)?;
199140

200141
#[cfg(any(
201142
feature = "electrum",
202143
feature = "esplora",
203144
feature = "rpc",
204145
feature = "cbf"
205146
))]
206-
let client = Some(new_blockchain_client(&wallet_opts, &wallet, database_path)?);
147+
let client = runtime.build_client(&wallet).ok();
207148

208149
println!(
209-
"Entering REPL mode for wallet '{}'. Type 'exit' to quit.",
150+
"Entering REPL mode for wallet '{}'. \
151+
Type 'exit' to quit.",
210152
wallet_name
211153
);
212154

213155
loop {
214156
let line = crate::handlers::repl::readline()?;
157+
215158
if line.trim().is_empty() {
216159
continue;
217160
}
218161

219-
// Pass it to our newly refactored respond function
220162
let should_exit = crate::handlers::repl::respond(
221-
network,
163+
runtime.network,
222164
&mut wallet,
223165
#[cfg(any(
224166
feature = "electrum",
225167
feature = "esplora",
226168
feature = "rpc",
227169
feature = "cbf"
228170
))]
229-
client.as_ref(),
171+
client.as_ref(),
230172
&line,
231-
home_dir.clone(),
173+
runtime.home_dir.clone(),
232174
&cli_opts,
233175
)
234176
.await
235-
.map_err(Error::Generic)?;
177+
.map_err(Error::Generic)?;
236178

237-
// Break the loop if the user typed `exit`
238179
if should_exit {
239180
break;
240181
}
241182
}
242183
}
243-
244-
#[cfg(not(feature = "repl"))]
245-
{
246-
return Err(Error::Generic(
247-
"The 'repl' feature is not enabled in this build.".into(),
248-
));
249-
}
250184
}
185+
251186
CliSubCommand::Completions { shell } => {
252187
shell;
253188
}
189+
254190
#[cfg(feature = "compiler")]
255191
CliSubCommand::Compile(cmd) => {
256192
let mut ctx = AppContext::new(cli_opts.network, home_dir);
193+
257194
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
258195
}
259-
};
196+
}
260197

261198
Ok(())
262199
}

src/persister.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#[cfg(any(feature = "sqlite", feature = "redb"))]
21
use crate::commands::WalletOpts;
32
use crate::error::BDKCliError as Error;
43
#[cfg(any(feature = "sqlite", feature = "redb"))]
5-
use bdk_wallet::{KeychainKind, PersistedWallet, bitcoin::Network};
4+
use bdk_wallet::{KeychainKind, PersistedWallet};
5+
use bdk_wallet::bitcoin::Network;
66
use bdk_wallet::{Wallet, WalletPersister};
77
use clap::ValueEnum;
88

@@ -25,6 +25,7 @@ pub(crate) enum Persister {
2525
RedbStore(bdk_redb::Store),
2626
}
2727

28+
#[cfg(any(feature = "sqlite", feature = "redb"))]
2829
impl WalletPersister for Persister {
2930
type Error = Error;
3031

@@ -97,7 +98,6 @@ where
9798
Ok(wallet)
9899
}
99100

100-
#[cfg(not(any(feature = "sqlite", feature = "redb")))]
101101
pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result<Wallet, Error> {
102102
let ext_descriptor = wallet_opts.ext_descriptor.clone();
103103
let int_descriptor = wallet_opts.int_descriptor.clone();

0 commit comments

Comments
 (0)