-use crate::error::BDKCliError as Error;
+#[cfg(feature = "rpc")]
+use bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi};
#[cfg(feature = "esplora")]
use bdk_esplora::EsploraAsyncExt;
-use bdk_wallet::{
- bitcoin::{Transaction, Txid},
- chain::CanonicalizationParams,
-};
#[cfg(any(
feature = "electrum",
feature = "esplora",
feature = "cbf"
))]
use {
- crate::commands::{ClientType, WalletOpts},
-
- bdk_wallet::Wallet,
+ crate::commands::WalletOpts,
+ crate::error::BDKCliError as Error,
+ bdk_wallet::{
+ Wallet,
+ bitcoin::{Transaction, Txid},
+ chain::CanonicalizationParams,
+ },
+ clap::ValueEnum,
std::path::PathBuf,
};
-#[cfg(feature = "rpc")]
-use bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi};
-
#[cfg(feature = "cbf")]
use {
bdk_kyoto::{BuilderExt, LightClient},
};
+#[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ feature = "rpc",
+ feature = "cbf"
+))]
+#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
+pub enum ClientType {
+ #[cfg(feature = "electrum")]
+ Electrum,
+ #[cfg(feature = "esplora")]
+ Esplora,
+ #[cfg(feature = "rpc")]
+ Rpc,
+ #[cfg(feature = "cbf")]
+ Cbf,
+}
+
#[cfg(any(
feature = "electrum",
feature = "esplora",
},
#[cfg(feature = "cbf")]
- KyotoClient {
- client: Box<KyotoClientHandle>,
- },
+ KyotoClient { client: Box<KyotoClientHandle> },
}
#[cfg(any(
#[cfg(feature = "cbf")]
Self::KyotoClient { client } => {
- // ... (Kyoto broadcast logic from your online.rs) ...
- Ok(tx.compute_txid())
+ let txid = tx.compute_txid();
+ let wtxid = client
+ .requester
+ .broadcast_random(tx.clone())
+ .await
+ .map_err(|_| {
+ tracing::warn!("Broadcast was unsuccessful");
+ Error::Generic("Transaction broadcast timed out after 30 seconds".into())
+ })?;
+ tracing::info!("Successfully broadcast WTXID: {wtxid}");
+ Ok(txid)
}
}
}
pub async fn sync_wallet(&self, wallet: &mut Wallet) -> Result<(), Error> {
- // #[cfg(any(feature = "electrum", feature = "esplora"))]
+ #[cfg(any(feature = "electrum", feature = "esplora"))]
let request = wallet
.start_sync_with_revealed_spks()
.inspect(|item, progress| {
eprintln!("[ SCANNING {pc:03.0}% ] {item}");
});
match self {
- // #[cfg(feature = "electrum")]
+ #[cfg(feature = "electrum")]
Self::Electrum { client, batch_size } => {
// Populate the electrum client's transaction cache so it doesn't re-download transaction we
// already have.
.apply_update(update)
.map_err(|e| Error::Generic(e.to_string()))
}
- // #[cfg(feature = "esplora")]
+ #[cfg(feature = "esplora")]
Self::Esplora {
client,
parallel_requests,
.apply_update(update)
.map_err(|e| Error::Generic(e.to_string()))
}
- // #[cfg(feature = "rpc")]
+ #[cfg(feature = "rpc")]
Self::RpcClient { client } => {
let blockchain_info = client.get_blockchain_info()?;
let wallet_cp = wallet.latest_checkpoint();
wallet.apply_unconfirmed_txs(mempool_txs.update);
Ok(())
}
- // #[cfg(feature = "cbf")]
+ #[cfg(feature = "cbf")]
Self::KyotoClient { client } => sync_kyoto_client(wallet, client)
.await
.map_err(|e| Error::Generic(e.to_string())),
#![doc(html_logo_url = "https://github.com/bitcoindevkit/bdk/raw/master/static/bdk.png")]
#![warn(missing_docs)]
-mod backend;
+mod client;
mod commands;
mod config;
mod error;
feature = "rpc"
))]
mod payjoin;
+mod persister;
mod utils;
-mod wallet;
+#[cfg(feature = "redb")]
+use bdk_redb::Store as RedbStore;
use bdk_wallet::bitcoin::Network;
-use log::{debug, error, warn};
+use log::{debug, warn};
-use crate::commands::CliOpts;
-use crate::handlers::handle_command;
+#[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 clap::Parser;
#[tokio::main]
)
}
- match handle_command(cli_opts).await {
- Ok(result) => println!("{result}"),
- Err(e) => {
- error!("{e}");
- std::process::exit(1);
- }
+ if let Err(e) = run(cli_opts).await {
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
}
}
+
+async fn run(cli_opts: CliOpts) -> Result<(), Error> {
+ let datadir = cli_opts.datadir.clone();
+ let home_dir = prepare_home_dir(datadir)?;
+
+ match cli_opts.subcommand.clone() {
+ CliSubCommand::Wallet {
+ wallet: wallet_name,
+ subcommand,
+ } => match subcommand {
+ #[cfg(any(
+ feature = "electrum",
+ feature = "esplora",
+ 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(network, home_dir)
+ .with_wallet(&mut wallet)
+ .with_client(&client);
+
+ online_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(network, home_dir).with_wallet(&mut wallet);
+
+ offline_cmd.execute(&mut ctx)?;
+ }
+ WalletSubCommand::Config(mut config_cmd) => {
+ config_cmd.wallet_opts.wallet = Some(wallet_name);
+
+ let mut ctx = AppContext::new(cli_opts.network, home_dir);
+
+ config_cmd.execute(&mut ctx)?.print()?;
+ }
+ },
+
+ CliSubCommand::Key { subcommand } => {
+ let mut ctx = AppContext::new(cli_opts.network, home_dir);
+ subcommand.execute(&mut ctx)?;
+ }
+ CliSubCommand::Descriptor(descriptor_command) => {
+ let mut ctx = AppContext::new(cli_opts.network, home_dir);
+ descriptor_command.execute(&mut ctx)?.print()?;
+ }
+ CliSubCommand::Wallets(cmd) => {
+ let mut ctx = AppContext::new(cli_opts.network, home_dir);
+ cmd.execute(&mut ctx)?.print()?;
+ }
+ CliSubCommand::Repl { wallet: _ } => todo!(),
+ CliSubCommand::Completions { shell } => {
+ shell;
+ }
+ #[cfg(feature = "compiler")]
+ CliSubCommand::Compile(cmd) => {
+ let mut ctx = AppContext::new(cli_opts.network, home_dir);
+ cmd.execute(&mut ctx)?.print()?;
+ }
+ };
+
+ Ok(())
+}