Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
506 changes: 502 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ silent-payments = ["dep:bdk_sp"]

[dev-dependencies]
claims = "0.8.0"
predicates = "3.0"
tempfile = "3.8"
assert_cmd = "2.2.2"
bdk_testenv = "0.13.1"
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test:
pre-push:
cargo test --features default
cargo test --no-default-features
cargo test --all-features
cargo test --all-features -- --test-threads=2
cargo clippy --no-default-features --all-targets -- -D warnings
cargo clippy --all-features --all-targets -- -D warnings
cargo fmt --all -- --check
Expand Down
7 changes: 5 additions & 2 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,18 +281,21 @@ pub struct WalletOpts {
#[cfg(feature = "cbf")]
#[clap(flatten)]
pub compactfilter_opts: CompactFilterOpts,
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[command(flatten)]
pub proxy_opts: ProxyOpts,
}

/// Options to configure a SOCKS5 proxy for a blockchain client connection.
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[derive(Debug, Args, Clone, PartialEq, Eq)]
pub struct ProxyOpts {
/// Sets the SOCKS5 proxy for a blockchain client.
#[arg(env = "PROXY_ADDRS:PORT", long = "proxy", short = 'p')]
#[arg(env = "PROXY_ADDRS:PORT", long = "proxy")]
pub proxy: Option<String>,

/// Sets the SOCKS5 proxy credential.
#[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)]
#[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", value_parser = parse_proxy_auth)]
pub proxy_auth: Option<(String, String)>,

/// Sets the SOCKS5 proxy retries for the blockchain client.
Expand Down
52 changes: 50 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ pub struct WalletConfigInner {
pub parallel_requests: Option<usize>,
#[cfg(feature = "rpc")]
pub cookie: Option<String>,
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[serde(default)]
pub proxy: Option<String>,
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[serde(default)]
pub proxy_auth: Option<String>,
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[serde(default)]
pub proxy_retries: Option<u8>,
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[serde(default)]
pub proxy_timeout: Option<u8>,
#[cfg(feature = "cbf")]
#[serde(default)]
pub conn_count: Option<u8>,
}

impl WalletConfig {
Expand Down Expand Up @@ -93,7 +108,7 @@ impl TryFrom<&WalletConfigInner> for WalletOpts {
type Error = Error;

fn try_from(config: &WalletConfigInner) -> Result<Self, Self::Error> {
let _network = Network::from_str(&config.network)
Network::from_str(&config.network)
.map_err(|_| Error::Generic("Invalid network".to_string()))?;

#[cfg(any(feature = "sqlite", feature = "redb"))]
Expand Down Expand Up @@ -155,8 +170,21 @@ impl TryFrom<&WalletConfigInner> for WalletOpts {
#[cfg(feature = "rpc")]
cookie: config.cookie.clone(),

#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_opts: crate::commands::ProxyOpts {
proxy: config.proxy.clone(),
proxy_auth: match &config.proxy_auth {
Some(s) => Some(crate::utils::parse_proxy_auth(s)?),
None => None,
},
retries: config.proxy_retries.unwrap_or(5),
timeout: config.proxy_timeout,
},

#[cfg(feature = "cbf")]
compactfilter_opts: crate::commands::CompactFilterOpts { conn_count: 2 },
compactfilter_opts: crate::commands::CompactFilterOpts {
conn_count: config.conn_count.unwrap_or(2),
},
})
}
}
Expand Down Expand Up @@ -218,6 +246,16 @@ mod tests {
rpc_password: None,
#[cfg(feature = "rpc")]
cookie: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_auth: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_retries: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_timeout: None,
#[cfg(feature = "cbf")]
conn_count: None,
};

let opts: WalletOpts = (&wallet_config)
Expand Down Expand Up @@ -293,6 +331,16 @@ mod tests {
rpc_password: None,
#[cfg(feature = "rpc")]
cookie: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_auth: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_retries: None,
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_timeout: None,
#[cfg(feature = "cbf")]
conn_count: None,
};

let result: Result<WalletOpts, Error> = (&inner).try_into();
Expand Down
19 changes: 17 additions & 2 deletions src/handlers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::config::{WalletConfig, WalletConfigInner};
use crate::error::BDKCliError as Error;
use crate::handlers::Init;
use crate::handlers::{AppCommand, AppContext};
#[cfg(feature = "sqlite")]
#[cfg(any(feature = "sqlite", feature = "redb"))]
use crate::persister::DatabaseType;
use crate::utils::types::{StatusResult, WalletsListResult};
use bdk_wallet::bitcoin::Network;
Expand Down Expand Up @@ -95,7 +95,6 @@ impl AppCommand<AppContext<Init>> for SaveConfigCommand {
network: ctx.network.to_string(),
ext_descriptor: self.wallet_opts.ext_descriptor.clone(),
int_descriptor: self.wallet_opts.int_descriptor.clone(),

#[cfg(any(feature = "sqlite", feature = "redb"))]
database_type: match self.wallet_opts.database_type {
#[cfg(feature = "sqlite")]
Expand Down Expand Up @@ -125,6 +124,22 @@ impl AppCommand<AppContext<Init>> for SaveConfigCommand {
parallel_requests: Some(self.wallet_opts.parallel_requests),
#[cfg(feature = "rpc")]
cookie: self.wallet_opts.cookie.clone(),

#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy: self.wallet_opts.proxy_opts.proxy.clone(),
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_auth: self
.wallet_opts
.proxy_opts
.proxy_auth
.as_ref()
.map(|(u, p)| format!("{u}:{p}")),
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_retries: Some(self.wallet_opts.proxy_opts.retries),
#[cfg(any(feature = "electrum", feature = "esplora"))]
proxy_timeout: self.wallet_opts.proxy_opts.timeout,
#[cfg(feature = "cbf")]
conn_count: Some(self.wallet_opts.compactfilter_opts.conn_count),
};

config.wallets.insert(wallet_name.clone(), wallet_config);
Expand Down
11 changes: 5 additions & 6 deletions src/handlers/offline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use bdk_wallet::{KeychainKind, SignOptions};
use clap::Parser;
use serde_json::json;
use std::collections::BTreeMap;
use std::str::FromStr;
#[cfg(feature = "silent-payments")]
use {
crate::utils::common::parse_sp_code_value_pairs,
Expand Down Expand Up @@ -311,6 +310,8 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CreateTxCommand {

let psbt = tx_builder.finish()?;

// let psbt_base64 = BASE64_STANDARD.encode(psbt.serialize());

Ok(PsbtResult::new(&psbt, Some(false)))
}
}
Expand Down Expand Up @@ -540,7 +541,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CreateSpTxCommand {
pub struct BumpFeeCommand {
/// TXID of the transaction to update.
#[arg(env = "TXID", long = "txid")]
pub txid: String,
pub txid: Txid,

/// Allows the wallet to reduce the amount to the specified address in order to increase fees.
#[arg(env = "SHRINK_ADDRESS", long = "shrink", value_parser = parse_address)]
Expand Down Expand Up @@ -574,9 +575,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for BumpFeeCommand {
fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
let wallet = &mut ctx.state.wallet;

let txid = Txid::from_str(self.txid.as_str())?;

let mut tx_builder = wallet.build_fee_bump(txid)?;
let mut tx_builder = wallet.build_fee_bump(self.txid)?;
let fee_rate =
FeeRate::from_sat_per_vb(self.fee_rate as u64).unwrap_or(FeeRate::BROADCAST_MIN);
tx_builder.fee_rate(fee_rate);
Expand Down Expand Up @@ -761,7 +760,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CombinePsbtCommand {
Ok(acc)
})?;

Ok(PsbtResult::new(&final_psbt, None))
Ok(PsbtResult::new(&final_psbt, Some(false)))
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/handlers/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub struct FullScanCommand {
/// Stop searching addresses for transactions after finding an unused gap of this length.
#[arg(env = "STOP_GAP", long = "scan-stop-gap", default_value = "20")]
stop_gap: usize,
// #[clap(long, default_value = "5")]
#[clap(long, default_value = "5")]
pub parallel_request: usize,
}

Expand Down
30 changes: 10 additions & 20 deletions src/handlers/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ pub(crate) async fn respond(
) -> Result<bool, String> {
let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;

let mut repl_args = vec!["repl".to_string()];
repl_args.extend(args);

let repl_subcommand = match ReplSubCommand::try_parse_from(&repl_args) {
let repl_subcommand = match ReplSubCommand::try_parse_from(&args) {
Ok(cmd) => cmd,
Err(e) => {
writeln!(std::io::stdout(), "{}", e).map_err(|e| e.to_string())?;
Expand All @@ -58,10 +55,7 @@ pub(crate) async fn respond(
ReplSubCommand::Wallet { subcommand } => match subcommand {
WalletSubCommand::OfflineWalletSubCommand(cmd) => {
let mut ctx = AppContext::new_offline_wallet(network, datadir, wallet);
cmd.execute(&mut ctx)
.map_err(|e| e.to_string())?
.write_out(std::io::stdout())
.map_err(|e| e.to_string())?;
cmd.execute(&mut ctx).map_err(|e| e.to_string())?;
Some(())
}
#[cfg(any(
Expand All @@ -80,20 +74,16 @@ pub(crate) async fn respond(
wallet_name.to_string(),
);

cmd.execute(&mut ctx)
.await
.map_err(|e| e.to_string())?
.write_out(std::io::stdout())
.map_err(|e| e.to_string())?;
cmd.execute(&mut ctx).await.map_err(|e| e.to_string())?;
Some(())
}
WalletSubCommand::Config(config_cmd) => {
let mut ctx = AppContext::new(network, datadir);
config_cmd
.execute(&mut ctx)
.map_err(|e| e.to_string())?
.write_out(std::io::stdout())
.map_err(|e| e.to_string())?;
WalletSubCommand::Config(_) => {
writeln!(
std::io::stdout(),
"`config` is not available in REPL mode — the wallet for this session \
is already loaded. Exit and run `bdk-cli wallet --wallet <name> config ...`."
)
.map_err(|e| e.to_string())?;
Some(())
}
},
Expand Down
12 changes: 9 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::handlers::{AppCommand, AppContext};
use crate::utils::output::FormatOutput;
use crate::utils::runtime::WalletRuntime;
use crate::utils::{command_requires_db, prepare_home_dir};
use clap::Parser;
use clap::{CommandFactory, Parser};

#[tokio::main]
async fn main() {
Expand Down Expand Up @@ -199,8 +199,14 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {

cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
}
CliSubCommand::Completions { shell: _ } => unimplemented!(),

CliSubCommand::Completions { shell } => {
clap_complete::generate(
shell,
&mut CliOpts::command(),
"bdk-cli",
&mut std::io::stdout(),
);
}
#[cfg(feature = "silent-payments")]
CliSubCommand::SilentPaymentCode(cmd) => {
let mut ctx = AppContext::new(cli_opts.network, home_dir);
Expand Down
2 changes: 2 additions & 0 deletions src/persister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use bdk_wallet::Wallet;
use bdk_wallet::bitcoin::Network;
#[cfg(any(feature = "sqlite", feature = "redb"))]
use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
#[cfg(any(feature = "sqlite", feature = "redb"))]
use clap::ValueEnum;

#[cfg(any(feature = "sqlite", feature = "redb"))]
#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)]
pub enum DatabaseType {
/// Sqlite database
Expand Down
31 changes: 12 additions & 19 deletions src/utils/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,18 @@ pub(crate) fn parse_address(address_str: &str) -> Result<Address, Error> {
/// If not the default home directory is created at `~/.bdk-bitcoin`.
#[allow(dead_code)]
pub(crate) fn prepare_home_dir(home_path: Option<PathBuf>) -> Result<PathBuf, Error> {
let dir = home_path.unwrap_or_else(|| {
let mut dir = PathBuf::new();
dir.push(
dirs::home_dir()
.ok_or_else(|| Error::Generic("home dir not found".to_string()))
.unwrap(),
);
dir.push(".bdk-bitcoin");
dir
});
let dir = match home_path {
Some(dir) => dir,
None => {
let mut dir =
dirs::home_dir().ok_or_else(|| Error::Generic("Home dir not found".to_string()))?;
dir.push(".bdk-bitcoin");
dir
}
};

if !dir.exists() {
std::fs::create_dir(&dir).map_err(|e| Error::Generic(e.to_string()))?;
std::fs::create_dir_all(&dir).map_err(|e| Error::Generic(e.to_string()))?;
}

Ok(dir)
Expand Down Expand Up @@ -171,14 +170,8 @@ pub fn load_wallet_config(
"Wallet '{wallet_name}' not found in config"
)))?;

let network = match wallet_config.network.as_str() {
"bitcoin" => Ok(Network::Bitcoin),
"testnet" => Ok(Network::Testnet),
"regtest" => Ok(Network::Regtest),
"signet" => Ok(Network::Signet),
"testnet4" => Ok(Network::Testnet4),
_ => Err(Error::Generic("Invalid network in config".to_string())),
}?;
let network = Network::from_str(&wallet_config.network)
.map_err(|_| Error::Generic("Invalid network in config".to_string()))?;

Ok((wallet_opts, network))
}
Expand Down
Loading
Loading