-#[cfg(feature = "rpc")]
-use bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi};
#[cfg(feature = "esplora")]
use bdk_esplora::EsploraAsyncExt;
#[cfg(any(
bdk_wallet::{
Wallet,
bitcoin::{Transaction, Txid},
- chain::CanonicalizationParams,
},
clap::ValueEnum,
std::path::PathBuf,
};
+#[cfg(feature = "rpc")]
+use {
+ bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi},
+ bdk_wallet::chain::CanonicalizationParams,
+};
#[cfg(feature = "cbf")]
use {
impl BlockchainClient {
pub async fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
match self {
- // #[cfg(feature = "electrum")]
+ #[cfg(feature = "electrum")]
Self::Electrum { client, .. } => client
.transaction_broadcast(&tx)
.map_err(|e| Error::Generic(e.to_string())),
#[cfg(feature = "compiler")]
#[clap(long_about = "Miniscript policy compiler")]
Compile(CompileCommand),
- // #[cfg(feature = "repl")]
+ #[cfg(feature = "repl")]
/// REPL command loop mode.
///
/// REPL command loop can be used to make recurring callbacks to an already loaded wallet.
#[error("PsbtError: {0}")]
PsbtError(#[from] bdk_wallet::bitcoin::psbt::Error),
- // #[cfg(feature = "sqlite")]
+ #[cfg(feature = "sqlite")]
#[error("Rusqlite error: {0}")]
RusqliteError(Box<bdk_wallet::rusqlite::Error>),
))]
pub trait AsyncAppCommand<C> {
type Output: FormatOutput;
-
+
async fn execute(&self, ctx: &mut C) -> Result<Self::Output, Error>;
}
-
-// context for online and online
-// => cli.rs
-// handlers/{mod for commands}
-// wallet subdir /
-// wallet-offline and wallet-online (client mod)
}
}
-// #[cfg(feature = "bip322")]
+#[cfg(feature = "bip322")]
#[derive(Debug, Parser, Clone, PartialEq)]
pub struct SignMessageCommand {
/// The message to sign
}
}
-// #[cfg(feature = "bip322")]
+#[cfg(feature = "bip322")]
#[derive(Debug, Parser, Clone, PartialEq)]
pub struct VerifyMessageCommand {
/// The signature proof to verify
sync_kyoto_client(wallet, client).await?;
}
}
- Ok(StatusResult::new("Full scan completed successfully."))
+ Ok(StatusResult {
+ message: "Full scan completed successfully.".to_string(),
+ })
}
}
&self,
ctx: &mut AppContext<OnlineOperations<'_>>,
) -> Result<Self::Output, Error> {
- let mut wallet = &mut ctx.state.wallet;
+ let wallet = &mut ctx.state.wallet;
let client = ctx.state.client;
#[cfg(any(feature = "electrum", feature = "esplora"))]
let request = wallet
let mempool_txs = emitter.mempool()?;
wallet.apply_unconfirmed_txs(mempool_txs.update);
}
- // #[cfg(feature = "cbf")]
- KyotoClient { client } => sync_kyoto_client(&mut wallet, client)
+ #[cfg(feature = "cbf")]
+ KyotoClient { client } => sync_kyoto_client(wallet, client)
.await
.map_err(|e| Error::Generic(e.to_string()))?,
}
- Ok(StatusResult::new("Wallet synced successfully."))
+ Ok(StatusResult {
+ message: "Wallet synced successfully.".to_string(),
+ })
}
}
psbt.extract_tx()?
}
(None, Some(tx)) => {
- let tx_bytes = Vec::<u8>::from_hex(&tx)?;
+ let tx_bytes = Vec::<u8>::from_hex(tx)?;
Transaction::consensus_decode(&mut tx_bytes.as_slice())?
}
(Some(_), Some(_)) => {
-use bdk_wallet::{Wallet, bitcoin::Network};
-
-#[cfg(feature = "repl")]
-use crate::handlers::AppCommand;
-#[cfg(feature = "repl")]
-use crate::{handlers::AppContext, utils::output::FormatOutput};
-
#[cfg(feature = "repl")]
-use crate::commands::ReplSubCommand;
-use clap::Parser;
+use {
+ crate::commands::ReplSubCommand,
+ crate::handlers::{AppCommand, AppContext},
+ crate::utils::output::FormatOutput,
+ bdk_wallet::{Wallet, bitcoin::Network},
+ clap::Parser,
+};
#[cfg(any(
feature = "electrum",
))]
use crate::client::BlockchainClient;
#[cfg(feature = "repl")]
-use std::io::Write;
-use {
- crate::commands::{CliOpts, WalletSubCommand},
- crate::error::BDKCliError as Error,
-};
+use {crate::commands::WalletSubCommand, crate::error::BDKCliError as Error, std::io::Write};
#[cfg(feature = "repl")]
pub(crate) async fn respond(
client: Option<&BlockchainClient>,
line: &str,
datadir: std::path::PathBuf,
- _cli_opts: &CliOpts,
) -> Result<bool, String> {
let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;
mod persister;
mod utils;
-#[cfg(feature = "redb")]
-use bdk_redb::Store as RedbStore;
use bdk_wallet::bitcoin::Network;
use log::{debug, warn};
-#[cfg(any(
- feature = "electrum",
- feature = "esplora",
- feature = "rpc",
- feature = "cbf"
-))]
-use crate::client::new_blockchain_client;
use crate::commands::{CliOpts, CliSubCommand, WalletSubCommand};
use crate::error::BDKCliError as Error;
use crate::handlers::{AppCommand, AppContext};
-#[cfg(any(feature = "sqlite", feature = "redb"))]
-use crate::persister::{Persister, new_persisted_wallet};
use crate::utils::output::FormatOutput;
-use crate::utils::prepare_wallet_db_dir;
-use crate::utils::{load_wallet_config, prepare_home_dir};
+use crate::utils::runtime::WalletRuntime;
+use crate::utils::{command_requires_db, prepare_home_dir};
use clap::Parser;
#[tokio::main]
feature = "rpc",
feature = "cbf"
))]
- WalletSubCommand::OnlineWalletSubCommand(online_cmd) => {
- let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
-
- let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
- #[cfg(any(feature = "sqlite", feature = "redb"))]
- let mut persister: Persister = match &wallet_opts.database_type {
- #[cfg(feature = "sqlite")]
- crate::persister::DatabaseType::Sqlite => {
- let db_file = database_path.join("wallet.sqlite");
- let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
- Persister::Connection(connection)
- }
- #[cfg(feature = "redb")]
- crate::persister::DatabaseType::Redb => {
- use crate::persister::Persister;
-
- let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
- home_dir.join("wallet.redb"),
- )?);
- let store = RedbStore::new(db, wallet_name)?;
- log::debug!("Redb database opened successfully");
- Persister::RedbStore(store)
- }
- };
-
- let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
-
- let client = new_blockchain_client(&wallet_opts, &wallet, database_path)?;
-
- let mut ctx =
- AppContext::new_online_wallet(network, home_dir, &mut wallet, &client);
-
- online_cmd.execute(&mut ctx).await?;
+ WalletSubCommand::OnlineWalletSubCommand(cmd) => {
+ let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
+
+ let mut wallet = runtime.build_wallet(true)?;
+ let client = runtime.build_client(&wallet)?;
+
+ let mut ctx = AppContext::new_online_wallet(
+ runtime.network,
+ runtime.home_dir.clone(),
+ &mut wallet,
+ &client,
+ );
+
+ cmd.execute(&mut ctx).await?;
}
- WalletSubCommand::OfflineWalletSubCommand(offline_cmd) => {
- let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
-
- let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
-
- #[cfg(any(feature = "sqlite", feature = "redb"))]
- let mut persister: Persister = match &wallet_opts.database_type {
- #[cfg(feature = "sqlite")]
- crate::persister::DatabaseType::Sqlite => {
- let db_file = database_path.join("wallet.sqlite");
- let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
- Persister::Connection(connection)
- }
- #[cfg(feature = "redb")]
- crate::persister::DatabaseType::Redb => {
- use crate::persister::Persister;
- let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
- home_dir.join("wallet.redb"),
- )?);
- let store = RedbStore::new(db, wallet_name)?;
- Persister::RedbStore(store)
- }
- };
-
- let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
-
- let mut ctx = AppContext::new_offline_wallet(network, home_dir, &mut wallet);
-
- offline_cmd.execute(&mut ctx)?;
+
+ WalletSubCommand::OfflineWalletSubCommand(cmd) => {
+ let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
+
+ let mut wallet = runtime.build_wallet(command_requires_db(&cmd))?;
+
+ let mut ctx = AppContext::new_offline_wallet(
+ runtime.network,
+ runtime.home_dir.clone(),
+ &mut wallet,
+ );
+
+ cmd.execute(&mut ctx)?;
}
+
WalletSubCommand::Config(mut config_cmd) => {
config_cmd.wallet_opts.wallet = Some(wallet_name);
CliSubCommand::Key { subcommand } => {
let mut ctx = AppContext::new(cli_opts.network, home_dir);
+
subcommand.execute(&mut ctx)?;
}
- CliSubCommand::Descriptor(descriptor_command) => {
+
+ CliSubCommand::Descriptor(cmd) => {
let mut ctx = AppContext::new(cli_opts.network, home_dir);
- descriptor_command
- .execute(&mut ctx)?
- .write_out(std::io::stdout())?;
+
+ cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
}
+
CliSubCommand::Wallets(cmd) => {
let mut ctx = AppContext::new(cli_opts.network, home_dir);
+
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
}
+
+ #[cfg(feature = "repl")]
CliSubCommand::Repl {
wallet: wallet_name,
} => {
- #[cfg(feature = "repl")]
- {
- let (wallet_opts, network) = load_wallet_config(&home_dir, &wallet_name)?;
- let database_path = prepare_wallet_db_dir(&home_dir, &wallet_name)?;
-
- #[cfg(any(feature = "sqlite", feature = "redb"))]
- let mut persister: Persister = match &wallet_opts.database_type {
- #[cfg(feature = "sqlite")]
- crate::persister::DatabaseType::Sqlite => {
- let db_file = database_path.join("wallet.sqlite");
- let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
- Persister::Connection(connection)
- }
- #[cfg(feature = "redb")]
- crate::persister::DatabaseType::Redb => {
- use crate::persister::Persister;
- let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
- home_dir.join("wallet.redb"),
- )?);
- let store = RedbStore::new(db, wallet_name.clone())?;
- Persister::RedbStore(store)
- }
- };
-
- let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
-
- #[cfg(any(
- feature = "electrum",
- feature = "esplora",
- feature = "rpc",
- feature = "cbf"
- ))]
- let client = Some(new_blockchain_client(&wallet_opts, &wallet, database_path)?);
-
- println!(
- "Entering REPL mode for wallet '{}'. Type 'exit' to quit.",
- wallet_name
- );
+ let runtime = WalletRuntime::load(&home_dir, &wallet_name)?;
- loop {
- let line = crate::handlers::repl::readline()?;
- if line.trim().is_empty() {
- continue;
- }
-
- // Pass it to our newly refactored respond function
- let should_exit = crate::handlers::repl::respond(
- network,
- &mut wallet,
- #[cfg(any(
- feature = "electrum",
- feature = "esplora",
- feature = "rpc",
- feature = "cbf"
- ))]
- client.as_ref(),
- &line,
- home_dir.clone(),
- &cli_opts,
- )
- .await
- .map_err(Error::Generic)?;
-
- // Break the loop if the user typed `exit`
- if should_exit {
- break;
- }
+ let mut wallet = runtime.build_wallet(true)?;
+
+ #[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "rpc",
+ feature = "cbf"
+ ))]
+ let client = runtime.build_client(&wallet).ok();
+
+ println!(
+ "Entering REPL mode for wallet '{}'. \
+ Type 'exit' to quit.",
+ wallet_name
+ );
+
+ loop {
+ let line = crate::handlers::repl::readline()?;
+
+ if line.trim().is_empty() {
+ continue;
}
- }
- #[cfg(not(feature = "repl"))]
- {
- return Err(Error::Generic(
- "The 'repl' feature is not enabled in this build.".into(),
- ));
+ let should_exit = crate::handlers::repl::respond(
+ runtime.network,
+ &mut wallet,
+ #[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "rpc",
+ feature = "cbf"
+ ))]
+ client.as_ref(),
+ &line,
+ runtime.home_dir.clone(),
+ )
+ .await
+ .map_err(Error::Generic)?;
+
+ if should_exit {
+ break;
+ }
}
}
- CliSubCommand::Completions { shell } => {
- shell;
- }
+
#[cfg(feature = "compiler")]
CliSubCommand::Compile(cmd) => {
let mut ctx = AppContext::new(cli_opts.network, home_dir);
+
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
}
- };
+ CliSubCommand::Completions { shell: _ } => unimplemented!(),
+ }
Ok(())
}
-#[cfg(any(feature = "sqlite", feature = "redb"))]
use crate::commands::WalletOpts;
use crate::error::BDKCliError as Error;
+use bdk_wallet::Wallet;
+use bdk_wallet::bitcoin::Network;
#[cfg(any(feature = "sqlite", feature = "redb"))]
-use bdk_wallet::{KeychainKind, PersistedWallet, bitcoin::Network};
-use bdk_wallet::{Wallet, WalletPersister};
+use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
use clap::ValueEnum;
#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
RedbStore(bdk_redb::Store),
}
+#[cfg(any(feature = "sqlite", feature = "redb"))]
impl WalletPersister for Persister {
type Error = Error;
Ok(wallet)
}
-#[cfg(not(any(feature = "sqlite", feature = "redb")))]
pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result<Wallet, Error> {
let ext_descriptor = wallet_opts.ext_descriptor.clone();
let int_descriptor = wallet_opts.int_descriptor.clone();
#[cfg(feature = "silent-payments")]
use bdk_sp::encoding::SilentPaymentCode;
+use crate::commands::OfflineWalletSubCommand;
use std::{
path::{Path, PathBuf},
str::FromStr,
)),
}
}
+
+pub fn command_requires_db(command: &OfflineWalletSubCommand) -> bool {
+ match command {
+ OfflineWalletSubCommand::Balance(_)
+ | OfflineWalletSubCommand::Unspent(_)
+ | OfflineWalletSubCommand::Transactions(_)
+ | OfflineWalletSubCommand::BumpFee(_) => true,
+
+ OfflineWalletSubCommand::NewAddress(_)
+ | OfflineWalletSubCommand::UnusedAddress(_)
+ | OfflineWalletSubCommand::CreateTx(_)
+ | OfflineWalletSubCommand::Policies(_)
+ | OfflineWalletSubCommand::PublicDescriptor(_)
+ | OfflineWalletSubCommand::Sign(_)
+ | OfflineWalletSubCommand::ExtractPsbt(_)
+ | OfflineWalletSubCommand::FinalizePsbt(_)
+ | OfflineWalletSubCommand::CombinePsbt(_) => false,
+
+ #[cfg(feature = "bip322")]
+ OfflineWalletSubCommand::SignMessage(_) => true,
+
+ #[cfg(feature = "bip322")]
+ OfflineWalletSubCommand::VerifyMessage(_) => false,
+ }
+}
pub mod descriptors;
pub mod output;
pub use common::*;
+pub mod runtime;
pub mod types;
--- /dev/null
+#[cfg(feature = "redb")]
+use bdk_redb::Store as RedbStore;
+use bdk_wallet::{Wallet, bitcoin::Network};
+use std::{
+ ops::{Deref, DerefMut},
+ path::{Path, PathBuf},
+};
+
+use crate::{
+ error::BDKCliError as Error,
+ persister::new_wallet,
+ utils::{load_wallet_config, prepare_wallet_db_dir},
+};
+#[cfg(any(feature = "sqlite", feature = "redb"))]
+use {
+ crate::persister::{DatabaseType, Persister, new_persisted_wallet},
+ bdk_wallet::PersistedWallet,
+};
+
+#[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "rpc",
+ feature = "cbf"
+))]
+use crate::client::{BlockchainClient, new_blockchain_client};
+
+pub enum RuntimeWallet {
+ Standard(Wallet),
+ #[cfg(any(feature = "sqlite", feature = "redb"))]
+ Persisted(PersistedWallet<Persister>),
+}
+
+impl Deref for RuntimeWallet {
+ type Target = Wallet;
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Standard(wallet) => wallet,
+ #[cfg(any(feature = "sqlite", feature = "redb"))]
+ Self::Persisted(wallet) => wallet,
+ }
+ }
+}
+
+impl DerefMut for RuntimeWallet {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ match self {
+ Self::Standard(wallet) => wallet,
+ #[cfg(any(feature = "sqlite", feature = "redb"))]
+ Self::Persisted(wallet) => wallet,
+ }
+ }
+}
+
+#[allow(unused)]
+pub struct WalletRuntime {
+ pub wallet_name: String,
+ pub wallet_opts: crate::commands::WalletOpts,
+ pub network: Network,
+ pub home_dir: PathBuf,
+ pub database_path: PathBuf,
+}
+
+impl WalletRuntime {
+ pub fn load(home_dir: &Path, wallet_name: &str) -> Result<Self, Error> {
+ let (wallet_opts, network) = load_wallet_config(home_dir, wallet_name)?;
+
+ let database_path = prepare_wallet_db_dir(home_dir, wallet_name)?;
+
+ Ok(Self {
+ wallet_name: wallet_name.to_string(),
+ wallet_opts,
+ network,
+ home_dir: home_dir.to_path_buf(),
+ database_path,
+ })
+ }
+
+ pub fn build_wallet(&self, require_db: bool) -> Result<RuntimeWallet, Error> {
+ if !require_db {
+ return Ok(RuntimeWallet::Standard(new_wallet(
+ self.network,
+ &self.wallet_opts,
+ )?));
+ }
+
+ #[cfg(any(feature = "sqlite", feature = "redb"))]
+ {
+ let mut persister = self.create_persister()?;
+ let wallet = new_persisted_wallet(self.network, &mut persister, &self.wallet_opts)?;
+ Ok(RuntimeWallet::Persisted(wallet))
+ }
+
+ #[cfg(not(any(feature = "sqlite", feature = "redb")))]
+ {
+ Ok(RuntimeWallet::Standard(new_wallet(
+ self.network,
+ &self.wallet_opts,
+ )?))
+ }
+ }
+
+ #[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "rpc",
+ feature = "cbf"
+ ))]
+ pub fn build_client(&self, wallet: &Wallet) -> Result<BlockchainClient, Error> {
+ new_blockchain_client(&self.wallet_opts, wallet, self.database_path.clone())
+ }
+
+ #[cfg(any(feature = "sqlite", feature = "redb"))]
+ fn create_persister(&self) -> Result<Persister, Error> {
+ match &self.wallet_opts.database_type {
+ #[cfg(feature = "sqlite")]
+ DatabaseType::Sqlite => {
+ let db_file = self.database_path.join("wallet.sqlite");
+
+ let connection = bdk_wallet::rusqlite::Connection::open(db_file)?;
+
+ Ok(Persister::Connection(connection))
+ }
+
+ #[cfg(feature = "redb")]
+ DatabaseType::Redb => {
+ let db = std::sync::Arc::new(bdk_redb::redb::Database::create(
+ self.home_dir.join("wallet.redb"),
+ )?);
+
+ let store = RedbStore::new(db, self.wallet_name.clone())?;
+
+ Ok(Persister::RedbStore(store))
+ }
+ }
+ }
+}
}
}
+#[allow(unused)]
/// Represents the data for a single transaction
#[derive(Serialize)]
pub struct TransactionDetails {
pub message: String,
}
-impl StatusResult {
- pub fn new(msg: &str) -> Self {
- Self {
- message: msg.to_string(),
- }
- }
-}
-
#[cfg(any(
feature = "electrum",
feature = "esplora",
assert_eq!(confirmed_balance, 1000000000u64);
}
- // #[test]
- // #[cfg(feature = "regtest-bitcoin")]
- // fn test_basic_wallet_op_bitcoind() {
- // basic_wallet_ops("regtest-bitcoin")
- // }
- //
- // #[test]
- // #[cfg(feature = "regtest-electrum")]
- // fn test_basic_wallet_op_electrum() {
- // basic_wallet_ops("regtest-electrum")
- // }
}