From: Vihiga Tyonum Date: Tue, 21 Apr 2026 09:52:35 +0000 (+0100) Subject: ref(commands): mv commands & clients into subdirs X-Git-Tag: v4.0.0~8^2~36 X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/ui-kit/FromScriptException.UnrecognizedScript.html?a=commitdiff_plain;h=5c286d64aec3f1b34a889047222dd1c559e9d4a6;p=bdk-cli ref(commands): mv commands & clients into subdirs - move all blockchain clients code into the backend subdirectory - move commands.rs content into commands subdirectory --- diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..a4695b7 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,172 @@ +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf" +))] +use { + crate::commands::{ClientType, WalletOpts}, + crate::error::BDKCliError as Error, + bdk_wallet::Wallet, + std::path::PathBuf, +}; + +#[cfg(feature = "cbf")] +use { + crate::utils::trace_logger, + bdk_kyoto::{BuilderExt, LightClient}, +}; + +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf" +))] +pub(crate) enum BlockchainClient { + #[cfg(feature = "electrum")] + Electrum { + client: Box>, + batch_size: usize, + }, + #[cfg(feature = "esplora")] + Esplora { + client: Box, + parallel_requests: usize, + }, + #[cfg(feature = "rpc")] + RpcClient { + client: Box, + }, + + #[cfg(feature = "cbf")] + KyotoClient { client: Box }, +} + +/// Handle for the Kyoto client after the node has been started. +/// Contains only the components needed for sync and broadcast operations. +#[cfg(feature = "cbf")] +pub struct KyotoClientHandle { + pub requester: bdk_kyoto::Requester, + pub update_subscriber: tokio::sync::Mutex, +} + +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf", +))] +/// Create a new blockchain from the wallet configuration options. +pub(crate) fn new_blockchain_client( + wallet_opts: &WalletOpts, + _wallet: &Wallet, + _datadir: PathBuf, +) -> Result { + #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] + let url = &wallet_opts.url; + let client = match wallet_opts.client_type { + #[cfg(feature = "electrum")] + ClientType::Electrum => { + let client = bdk_electrum::electrum_client::Client::new(url) + .map(bdk_electrum::BdkElectrumClient::new)?; + BlockchainClient::Electrum { + client: Box::new(client), + batch_size: wallet_opts.batch_size, + } + } + #[cfg(feature = "esplora")] + ClientType::Esplora => { + let client = bdk_esplora::esplora_client::Builder::new(url).build_async()?; + BlockchainClient::Esplora { + client: Box::new(client), + parallel_requests: wallet_opts.parallel_requests, + } + } + + #[cfg(feature = "rpc")] + ClientType::Rpc => { + let auth = match &wallet_opts.cookie { + Some(cookie) => bdk_bitcoind_rpc::bitcoincore_rpc::Auth::CookieFile(cookie.into()), + None => bdk_bitcoind_rpc::bitcoincore_rpc::Auth::UserPass( + wallet_opts.basic_auth.0.clone(), + wallet_opts.basic_auth.1.clone(), + ), + }; + let client = bdk_bitcoind_rpc::bitcoincore_rpc::Client::new(url, auth) + .map_err(|e| Error::Generic(e.to_string()))?; + BlockchainClient::RpcClient { + client: Box::new(client), + } + } + + #[cfg(feature = "cbf")] + ClientType::Cbf => { + let scan_type = bdk_kyoto::ScanType::Sync; + let builder = bdk_kyoto::builder::Builder::new(_wallet.network()); + + let light_client = builder + .required_peers(wallet_opts.compactfilter_opts.conn_count) + .data_dir(&_datadir) + .build_with_wallet(_wallet, scan_type)?; + + let LightClient { + requester, + info_subscriber, + warning_subscriber, + update_subscriber, + node, + } = light_client; + + let subscriber = tracing_subscriber::FmtSubscriber::new(); + let _ = tracing::subscriber::set_global_default(subscriber); + + tokio::task::spawn(async move { node.run().await }); + tokio::task::spawn( + async move { trace_logger(info_subscriber, warning_subscriber).await }, + ); + + BlockchainClient::KyotoClient { + client: Box::new(KyotoClientHandle { + requester, + update_subscriber: tokio::sync::Mutex::new(update_subscriber), + }), + } + } + }; + Ok(client) +} + +// Handle Kyoto Client sync +#[cfg(feature = "cbf")] +pub async fn sync_kyoto_client( + wallet: &mut Wallet, + handle: &KyotoClientHandle, +) -> Result<(), Error> { + if !handle.requester.is_running() { + tracing::error!("Kyoto node is not running"); + return Err(Error::Generic("Kyoto node failed to start".to_string())); + } + tracing::info!("Kyoto node is running"); + + let update = handle.update_subscriber.lock().await.update().await?; + tracing::info!("Received update: applying to wallet"); + wallet + .apply_update(update) + .map_err(|e| Error::Generic(format!("Failed to apply update: {e}")))?; + + tracing::info!( + "Chain tip: {}, Transactions: {}, Balance: {}", + wallet.local_chain().tip().height(), + wallet.transactions().count(), + wallet.balance().total().to_sat() + ); + + tracing::info!( + "Sync completed: tx_count={}, balance={}", + wallet.transactions().count(), + wallet.balance().total().to_sat() + ); + + Ok(()) +} diff --git a/src/commands.rs b/src/commands.rs deleted file mode 100644 index 46404dc..0000000 --- a/src/commands.rs +++ /dev/null @@ -1,708 +0,0 @@ -// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! bdk-cli Command structure -//! -//! This module defines all the bdk-cli commands structure. -//! All optional args are defined in the structs below. -//! All subcommands are defined in the below enums. - -#![allow(clippy::large_enum_variant)] -#[cfg(feature = "silent-payments")] -use {crate::utils::parse_sp_code_value_pairs, bdk_sp::encoding::SilentPaymentCode}; - -use bdk_wallet::bitcoin::{ - Address, Network, OutPoint, ScriptBuf, - bip32::{DerivationPath, Xpriv}, -}; -use clap::{Args, Parser, Subcommand, ValueEnum, value_parser}; -use clap_complete::Shell; - -#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] -use crate::utils::parse_proxy_auth; -use crate::utils::{parse_address, parse_outpoint, parse_recipient}; - -/// The BDK Command Line Wallet App -/// -/// bdk-cli is a lightweight command line bitcoin wallet, powered by BDK. -/// This app can be used as a playground as well as testing environment to simulate -/// various wallet testing situations. If you are planning to use BDK in your wallet, bdk-cli -/// is also a great intro tool to get familiar with the BDK API. -/// -/// But this is not just any toy. -/// bdk-cli is also a fully functioning Bitcoin wallet with taproot support! -/// -/// For more information checkout -#[derive(PartialEq, Clone, Debug, Parser)] -#[command(version, about, long_about = None)] -pub struct CliOpts { - /// Sets the network. - #[arg( - env = "NETWORK", - short = 'n', - long = "network", - default_value = "testnet", - value_parser = value_parser!(Network) - )] - pub network: Network, - /// Sets the wallet data directory. - /// Default value : ~/.bdk-bitcoin - #[arg(env = "DATADIR", short = 'd', long = "datadir")] - pub datadir: Option, - /// Output results in pretty format (instead of JSON). - #[arg(long = "pretty", global = true)] - pub pretty: bool, - /// Top level cli sub-commands. - #[command(subcommand)] - pub subcommand: CliSubCommand, -} - -/// Top level cli sub-commands. -#[derive(Debug, Subcommand, Clone, PartialEq)] -#[command(rename_all = "snake")] -pub enum CliSubCommand { - /// Wallet operations. - /// - /// bdk-cli wallet operations includes all the basic wallet level tasks. - /// Most commands can be used without connecting to any backend. To use commands that - /// needs backend like `sync` and `broadcast`, compile the binary with specific backend feature - /// and use the configuration options below to configure for that backend. - Wallet { - /// Selects the wallet to use. - #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)] - wallet: String, - - #[command(subcommand)] - subcommand: WalletSubCommand, - }, - /// Key management operations. - /// - /// Provides basic key operations that are not related to a specific wallet such as generating a - /// new random master extended key or restoring a master extended key from mnemonic words. - /// - /// These sub-commands are **EXPERIMENTAL** and should only be used for testing. Do not use this - /// feature to create keys that secure actual funds on the Bitcoin mainnet. - Key { - #[clap(subcommand)] - subcommand: KeySubCommand, - }, - /// Compile a miniscript policy to an output descriptor. - #[cfg(feature = "compiler")] - #[clap(long_about = "Miniscript policy compiler")] - Compile { - /// Sets the spending policy to compile. - #[arg(env = "POLICY", required = true, index = 1)] - policy: String, - /// Sets the script type used to embed the compiled policy. - #[arg(env = "TYPE", short = 't', long = "type", default_value = "wsh", value_parser = ["sh","wsh", "sh-wsh", "tr"] - )] - script_type: String, - }, - #[cfg(feature = "repl")] - /// REPL command loop mode. - /// - /// REPL command loop can be used to make recurring callbacks to an already loaded wallet. - /// This mode is useful for hands on live testing of wallet operations. - Repl { - /// Wallet name for this REPL session - #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)] - wallet: String, - }, - /// Output Descriptors operations. - /// - /// Generate output descriptors from either extended key (Xprv/Xpub) or mnemonic phrase. - /// This feature is intended for development and testing purposes only. - Descriptor { - /// Descriptor type (script type) - #[arg( - long = "type", - short = 't', - value_parser = ["pkh", "wpkh", "sh", "wsh", "tr"], - default_value = "wsh" - )] - desc_type: String, - /// Optional key: xprv, xpub, or mnemonic phrase - key: Option, - }, - /// List all saved wallet configurations. - Wallets, - /// Generate tab-completion scripts for your shell. - /// - /// The completion script is output on stdout, allowing you to redirect - /// it to a file of your choosing. Where you place the file will depend - /// on your shell and operating system. - /// - /// Here are common setups for supported shells: - /// - /// Bash: - /// - /// Completion files are commonly stored in - /// `~/.local/share/bash-completion/completions` for user-specific commands. - /// Run the commands: - /// - /// $ mkdir -p ~/.local/share/bash-completion/completions - /// $ bdk-cli completions bash > ~/.local/share/bash-completion/completions/bdk-cli - /// - /// Zsh: - /// - /// Completion files are commonly stored in a directory listed in your `fpath`. - /// Run the commands: - /// - /// $ mkdir -p ~/.zfunc - /// $ bdk-cli completions zsh > ~/.zfunc/_bdk-cli - /// - /// Make sure `~/.zfunc` is in your fpath by adding to your `.zshrc`: - /// - /// fpath=(~/.zfunc $fpath) - /// autoload -Uz compinit && compinit - /// - /// Fish: - /// - /// Completion files are commonly stored in - /// `~/.config/fish/completions`. Run the commands: - /// - /// $ mkdir -p ~/.config/fish/completions - /// $ bdk-cli completions fish > ~/.config/fish/completions/bdk-cli.fish - /// - /// PowerShell: - /// - /// $ bdk-cli completions powershell >> $PROFILE - /// - /// Elvish: - /// - /// $ bdk-cli completions elvish >> ~/.elvish/rc.elv - /// - /// After installing the completion script, restart your shell or source - /// the configuration file for the changes to take effect. - #[command(verbatim_doc_comment)] - Completions { - /// Target shell syntax - #[arg(value_enum)] - shell: Shell, - }, - /// Silent payment code generation tool. - /// - /// Allows the encoding of two public keys into a silent payment code. - /// Useful to create silent payment transactions using fake silent payment codes. - #[cfg(feature = "silent-payments")] - SilentPaymentCode { - /// The scan public key to use on the silent payment code. - #[arg(long = "scan_key")] - scan: bdk_sp::bitcoin::secp256k1::PublicKey, - /// The spend public key to use on the silent payment code. - #[arg(long = "spend_key")] - spend: bdk_sp::bitcoin::secp256k1::PublicKey, - }, -} - -/// Wallet operation subcommands. -#[derive(Debug, Subcommand, Clone, PartialEq)] -pub enum WalletSubCommand { - /// Save wallet configuration to `config.toml`. - Config { - /// Overwrite existing wallet configuration if it exists. - #[arg(short = 'f', long = "force", default_value_t = false)] - force: bool, - - #[command(flatten)] - wallet_opts: WalletOpts, - }, - #[cfg(any( - feature = "electrum", - feature = "esplora", - feature = "cbf", - feature = "rpc" - ))] - #[command(flatten)] - OnlineWalletSubCommand(OnlineWalletSubCommand), - #[command(flatten)] - OfflineWalletSubCommand(OfflineWalletSubCommand), -} - -#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)] -pub enum DatabaseType { - /// Sqlite database - #[cfg(feature = "sqlite")] - Sqlite, - /// Redb database - #[cfg(feature = "redb")] - Redb, -} - -#[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, -} - -/// Config options wallet operations can take. -#[derive(Debug, Args, Clone, PartialEq, Eq)] -pub struct WalletOpts { - /// Selects the wallet to use. - #[arg(skip)] - pub wallet: Option, - /// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects. - #[arg(env = "VERBOSE", short = 'v', long = "verbose")] - pub verbose: bool, - /// Sets the descriptor to use for the external addresses. - #[arg(env = "EXT_DESCRIPTOR", short = 'e', long, required = true)] - pub ext_descriptor: String, - /// Sets the descriptor to use for internal/change addresses. - #[arg(env = "INT_DESCRIPTOR", short = 'i', long)] - pub int_descriptor: Option, - #[cfg(any( - feature = "electrum", - feature = "esplora", - feature = "rpc", - feature = "cbf" - ))] - #[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)] - pub client_type: ClientType, - #[cfg(any(feature = "sqlite", feature = "redb"))] - #[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)] - pub database_type: DatabaseType, - /// Sets the server url. - #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] - #[arg(env = "SERVER_URL", short = 'u', long, required = true)] - pub url: String, - /// Electrum batch size. - #[cfg(feature = "electrum")] - #[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")] - pub batch_size: usize, - /// Esplora parallel requests. - #[cfg(feature = "esplora")] - #[arg( - env = "ESPLORA_PARALLEL_REQUESTS", - short = 'p', - long, - default_value = "5" - )] - pub parallel_requests: usize, - #[cfg(feature = "rpc")] - /// Sets the rpc basic authentication. - #[arg( - env = "USER:PASSWD", - short = 'a', - long, - value_parser = parse_proxy_auth, - default_value = "user:password", - )] - pub basic_auth: (String, String), - #[cfg(feature = "rpc")] - /// Sets an optional cookie authentication. - #[arg(env = "COOKIE")] - pub cookie: Option, - #[cfg(feature = "cbf")] - #[clap(flatten)] - pub compactfilter_opts: CompactFilterOpts, -} - -/// 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')] - pub proxy: Option, - - /// Sets the SOCKS5 proxy credential. - #[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)] - pub proxy_auth: Option<(String, String)>, - - /// Sets the SOCKS5 proxy retries for the blockchain client. - #[arg( - env = "PROXY_RETRIES", - short = 'r', - long = "retries", - default_value = "5" - )] - pub retries: u8, - - /// Sets the SOCKS5 proxy timeout for the blockchain client. - #[arg(env = "PROXY_TIMEOUT", short = 't', long = "timeout")] - pub timeout: Option, -} - -/// Options to configure a BIP157 Compact Filter backend. -#[cfg(feature = "cbf")] -#[derive(Debug, Args, Clone, PartialEq, Eq)] -pub struct CompactFilterOpts { - /// Sets the number of parallel node connections. - #[clap(name = "CONNECTIONS", long = "cbf-conn-count", default_value = "2", value_parser = value_parser!(u8).range(1..=15))] - pub conn_count: u8, -} - -/// Wallet subcommands that can be issued without a blockchain backend. -#[derive(Debug, Subcommand, Clone, PartialEq)] -#[command(rename_all = "snake")] -pub enum OfflineWalletSubCommand { - /// Get a new external address. - NewAddress, - /// Get the first unused external address. - UnusedAddress, - /// Lists the available spendable UTXOs. - Unspent, - /// Lists all the incoming and outgoing transactions of the wallet. - Transactions, - /// Returns the current wallet balance. - Balance, - /// Creates a new unsigned transaction. - CreateTx { - /// Adds a recipient to the transaction. - // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704. - // Address and amount parsing is done at run time in handler function. - #[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)] - recipients: Vec<(ScriptBuf, u64)>, - /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0. - #[arg(long = "send_all", short = 'a')] - send_all: bool, - /// Enables Replace-By-Fee (BIP125). - #[arg(long = "enable_rbf", short = 'r', default_value_t = true)] - enable_rbf: bool, - /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. - #[arg(long = "offline_signer")] - offline_signer: bool, - /// Selects which utxos *must* be spent. - #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] - utxos: Option>, - /// Marks a utxo as unspendable. - #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] - unspendable: Option>, - /// Fee rate to use in sat/vbyte. - #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")] - fee_rate: Option, - /// Selects which policy should be used to satisfy the external descriptor. - #[arg(env = "EXT_POLICY", long = "external_policy")] - external_policy: Option, - /// Selects which policy should be used to satisfy the internal descriptor. - #[arg(env = "INT_POLICY", long = "internal_policy")] - internal_policy: Option, - /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes) - #[arg( - env = "ADD_STRING", - long = "add_string", - short = 's', - conflicts_with = "add_data" - )] - add_string: Option, - /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes) - #[arg( - env = "ADD_DATA", - long = "add_data", - short = 'o', - conflicts_with = "add_string" - )] - add_data: Option, //base 64 econding - }, - /// Creates a silent payment transaction - /// - /// This sub-command is **EXPERIMENTAL** and should only be used for testing. Do not use this - /// feature to create transactions that spend actual funds on the Bitcoin mainnet. - - // This command DOES NOT return a PSBT. Instead, it directly returns a signed transaction - // ready for broadcast, as it is not yet possible to perform a shared derivation of a silent - // payment script pubkey in a secure and trustless manner. - #[cfg(feature = "silent-payments")] - CreateSpTx { - /// Adds a recipient to the transaction. - // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704. - // Address and amount parsing is done at run time in handler function. - #[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)] - recipients: Option>, - /// Parse silent payment recipients - #[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)] - silent_payment_recipients: Vec<(SilentPaymentCode, u64)>, - /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0. - #[arg(long = "send_all", short = 'a')] - send_all: bool, - /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. - #[arg(long = "offline_signer")] - offline_signer: bool, - /// Selects which utxos *must* be spent. - #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] - utxos: Option>, - /// Marks a utxo as unspendable. - #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] - unspendable: Option>, - /// Fee rate to use in sat/vbyte. - #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")] - fee_rate: Option, - /// Selects which policy should be used to satisfy the external descriptor. - #[arg(env = "EXT_POLICY", long = "external_policy")] - external_policy: Option, - /// Selects which policy should be used to satisfy the internal descriptor. - #[arg(env = "INT_POLICY", long = "internal_policy")] - internal_policy: Option, - /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes) - #[arg( - env = "ADD_STRING", - long = "add_string", - short = 's', - conflicts_with = "add_data" - )] - add_string: Option, - /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes) - #[arg( - env = "ADD_DATA", - long = "add_data", - short = 'o', - conflicts_with = "add_string" - )] - add_data: Option, //base 64 econding - }, - /// Bumps the fees of an RBF transaction. - BumpFee { - /// TXID of the transaction to update. - #[arg(env = "TXID", long = "txid")] - txid: String, - /// 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)] - shrink_address: Option
, - /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. - #[arg(long = "offline_signer")] - offline_signer: bool, - /// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used. - #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] - utxos: Option>, - /// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees. - #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] - unspendable: Option>, - /// The new targeted fee rate in sat/vbyte. - #[arg( - env = "SATS_VBYTE", - short = 'f', - long = "fee_rate", - default_value = "1.0" - )] - fee_rate: f32, - }, - /// Returns the available spending policies for the descriptor. - Policies, - /// Returns the public version of the wallet's descriptor(s). - PublicDescriptor, - /// Signs and tries to finalize a PSBT. - Sign { - /// Sets the PSBT to sign. - #[arg(env = "BASE64_PSBT")] - psbt: String, - /// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor. - #[arg(env = "HEIGHT", long = "assume_height")] - assume_height: Option, - /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. - #[arg(env = "WITNESS", long = "trust_witness_utxo")] - trust_witness_utxo: Option, - }, - /// Extracts a raw transaction from a PSBT. - ExtractPsbt { - /// Sets the PSBT to extract - #[arg(env = "BASE64_PSBT")] - psbt: String, - }, - /// Finalizes a PSBT. - FinalizePsbt { - /// Sets the PSBT to finalize. - #[arg(env = "BASE64_PSBT")] - psbt: String, - /// Assume the blockchain has reached a specific height. - #[arg(env = "HEIGHT", long = "assume_height")] - assume_height: Option, - /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. - #[arg(env = "WITNESS", long = "trust_witness_utxo")] - trust_witness_utxo: Option, - }, - /// Combines multiple PSBTs into one. - CombinePsbt { - /// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT. - #[arg(env = "BASE64_PSBT", required = true)] - psbt: Vec, - }, - /// Sign a message using BIP322 - #[cfg(feature = "bip322")] - SignMessage { - /// The message to sign - #[arg(long)] - message: String, - /// The signature format (e.g., Legacy, Simple, Full) - #[arg(long, default_value = "simple")] - signature_type: String, - /// Address to sign - #[arg(long)] - address: String, - /// Optional list of specific UTXOs for proof-of-funds (only for `FullWithProofOfFunds`) - #[arg(long)] - utxos: Option>, - }, - /// Verify a BIP322 signature - #[cfg(feature = "bip322")] - VerifyMessage { - /// The signature proof to verify - #[arg(long)] - proof: String, - /// The message that was signed - #[arg(long)] - message: String, - /// The address associated with the signature - #[arg(long)] - address: String, - }, -} - -/// Wallet subcommands that needs a blockchain backend. -#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] -#[command(rename_all = "snake")] -#[cfg(any( - feature = "electrum", - feature = "esplora", - feature = "cbf", - feature = "rpc" -))] -pub enum OnlineWalletSubCommand { - /// Full Scan with the chosen blockchain server. - FullScan { - /// 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, - }, - /// Syncs with the chosen blockchain server. - Sync, - /// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract. - Broadcast { - /// Sets the PSBT to sign. - #[arg( - env = "BASE64_PSBT", - long = "psbt", - required_unless_present = "tx", - conflicts_with = "tx" - )] - psbt: Option, - /// Sets the raw transaction to broadcast. - #[arg( - env = "RAWTX", - long = "tx", - required_unless_present = "psbt", - conflicts_with = "psbt" - )] - tx: Option, - }, - /// Generates a Payjoin receive URI and processes the sender's Payjoin proposal. - ReceivePayjoin { - /// Amount to be received in sats. - #[arg(env = "PAYJOIN_AMOUNT", long = "amount", required = true)] - amount: u64, - /// Payjoin directory which will be used to store the PSBTs which are pending action - /// from one of the parties. - #[arg(env = "PAYJOIN_DIRECTORY", long = "directory", required = true)] - directory: String, - /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the - /// operation with multiple relays for redundancy. - #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay", required = true)] - ohttp_relay: Vec, - /// Maximum effective fee rate the receiver is willing to pay for their own input/output contributions. - #[arg(env = "PAYJOIN_RECEIVER_MAX_FEE_RATE", long = "max_fee_rate")] - max_fee_rate: Option, - }, - /// Sends an original PSBT to a BIP 21 URI and broadcasts the returned Payjoin PSBT. - SendPayjoin { - /// BIP 21 URI for the Payjoin. - #[arg(env = "PAYJOIN_URI", long = "uri", required = true)] - uri: String, - /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the - /// operation with multiple relays for redundancy. - #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay", required = true)] - ohttp_relay: Vec, - /// Fee rate to use in sat/vbyte. - #[arg( - env = "PAYJOIN_SENDER_FEE_RATE", - short = 'f', - long = "fee_rate", - required = true - )] - fee_rate: u64, - }, -} - -/// Subcommands for Key operations. -#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] -pub enum KeySubCommand { - /// Generates new random seed mnemonic phrase and corresponding master extended key. - Generate { - /// Entropy level based on number of random seed mnemonic words. - #[arg( - env = "WORD_COUNT", - short = 'e', - long = "entropy", - default_value = "12" - )] - word_count: usize, - /// Seed password. - #[arg(env = "PASSWORD", short = 'p', long = "password")] - password: Option, - }, - /// Restore a master extended key from seed backup mnemonic words. - Restore { - /// Seed mnemonic words, must be quoted (eg. "word1 word2 ..."). - #[arg(env = "MNEMONIC", short = 'm', long = "mnemonic")] - mnemonic: String, - /// Seed password. - #[arg(env = "PASSWORD", short = 'p', long = "password")] - password: Option, - }, - /// Derive a child key pair from a master extended key and a derivation path string (eg. "m/84'/1'/0'/0" or "m/84h/1h/0h/0"). - Derive { - /// Extended private key to derive from. - #[arg(env = "XPRV", short = 'x', long = "xprv")] - xprv: Xpriv, - /// Path to use to derive extended public key from extended private key. - #[arg(env = "PATH", short = 'p', long = "path")] - path: DerivationPath, - }, -} - -/// Subcommands available in REPL mode. -#[cfg(any(feature = "repl", target_arch = "wasm32"))] -#[derive(Debug, Parser)] -#[command(rename_all = "lower", multicall = true)] -pub enum ReplSubCommand { - /// Execute wallet commands. - Wallet { - #[command(subcommand)] - subcommand: WalletSubCommand, - }, - /// Execute key commands. - Key { - #[command(subcommand)] - subcommand: KeySubCommand, - }, - /// Generate descriptors - Descriptor { - /// Descriptor type (script type). - #[arg( - long = "type", - short = 't', - value_parser = ["pkh", "wpkh", "sh", "wsh", "tr"], - default_value = "wsh" - )] - desc_type: String, - /// Optional key: xprv, xpub, or mnemonic phrase - key: Option, - }, - /// Exit REPL loop. - Exit, -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..c8fb1a0 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,709 @@ +// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! bdk-cli Command structure +//! +//! This module defines all the bdk-cli commands structure. +//! All optional args are defined in the structs below. +//! All subcommands are defined in the below enums. + +#![allow(clippy::large_enum_variant)] +#[cfg(feature = "silent-payments")] +use {crate::utils::parse_sp_code_value_pairs, bdk_sp::encoding::SilentPaymentCode}; + +use bdk_wallet::bitcoin::{ + Address, Network, OutPoint, ScriptBuf, + bip32::{DerivationPath, Xpriv}, +}; +use clap::{Args, Parser, Subcommand, ValueEnum, value_parser}; +use clap_complete::Shell; + +use crate::utils::{parse_address, parse_outpoint, parse_recipient}; + +#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] +use crate::utils::parse_proxy_auth; + +/// The BDK Command Line Wallet App +/// +/// bdk-cli is a lightweight command line bitcoin wallet, powered by BDK. +/// This app can be used as a playground as well as testing environment to simulate +/// various wallet testing situations. If you are planning to use BDK in your wallet, bdk-cli +/// is also a great intro tool to get familiar with the BDK API. +/// +/// But this is not just any toy. +/// bdk-cli is also a fully functioning Bitcoin wallet with taproot support! +/// +/// For more information checkout +#[derive(PartialEq, Clone, Debug, Parser)] +#[command(version, about, long_about = None)] +pub struct CliOpts { + /// Sets the network. + #[arg( + env = "NETWORK", + short = 'n', + long = "network", + default_value = "testnet", + value_parser = value_parser!(Network) + )] + pub network: Network, + /// Sets the wallet data directory. + /// Default value : ~/.bdk-bitcoin + #[arg(env = "DATADIR", short = 'd', long = "datadir")] + pub datadir: Option, + /// Output results in pretty format (instead of JSON). + #[arg(long = "pretty", global = true)] + pub pretty: bool, + /// Top level cli sub-commands. + #[command(subcommand)] + pub subcommand: CliSubCommand, +} + +/// Top level cli sub-commands. +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[command(rename_all = "snake")] +pub enum CliSubCommand { + /// Wallet operations. + /// + /// bdk-cli wallet operations includes all the basic wallet level tasks. + /// Most commands can be used without connecting to any backend. To use commands that + /// needs backend like `sync` and `broadcast`, compile the binary with specific backend feature + /// and use the configuration options below to configure for that backend. + Wallet { + /// Selects the wallet to use. + #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)] + wallet: String, + + #[command(subcommand)] + subcommand: WalletSubCommand, + }, + /// Key management operations. + /// + /// Provides basic key operations that are not related to a specific wallet such as generating a + /// new random master extended key or restoring a master extended key from mnemonic words. + /// + /// These sub-commands are **EXPERIMENTAL** and should only be used for testing. Do not use this + /// feature to create keys that secure actual funds on the Bitcoin mainnet. + Key { + #[clap(subcommand)] + subcommand: KeySubCommand, + }, + /// Compile a miniscript policy to an output descriptor. + #[cfg(feature = "compiler")] + #[clap(long_about = "Miniscript policy compiler")] + Compile { + /// Sets the spending policy to compile. + #[arg(env = "POLICY", required = true, index = 1)] + policy: String, + /// Sets the script type used to embed the compiled policy. + #[arg(env = "TYPE", short = 't', long = "type", default_value = "wsh", value_parser = ["sh","wsh", "sh-wsh", "tr"] + )] + script_type: String, + }, + #[cfg(feature = "repl")] + /// REPL command loop mode. + /// + /// REPL command loop can be used to make recurring callbacks to an already loaded wallet. + /// This mode is useful for hands on live testing of wallet operations. + Repl { + /// Wallet name for this REPL session + #[arg(env = "WALLET_NAME", short = 'w', long = "wallet", required = true)] + wallet: String, + }, + /// Output Descriptors operations. + /// + /// Generate output descriptors from either extended key (Xprv/Xpub) or mnemonic phrase. + /// This feature is intended for development and testing purposes only. + Descriptor { + /// Descriptor type (script type) + #[arg( + long = "type", + short = 't', + value_parser = ["pkh", "wpkh", "sh", "wsh", "tr"], + default_value = "wsh" + )] + desc_type: String, + /// Optional key: xprv, xpub, or mnemonic phrase + key: Option, + }, + /// List all saved wallet configurations. + Wallets, + /// Generate tab-completion scripts for your shell. + /// + /// The completion script is output on stdout, allowing you to redirect + /// it to a file of your choosing. Where you place the file will depend + /// on your shell and operating system. + /// + /// Here are common setups for supported shells: + /// + /// Bash: + /// + /// Completion files are commonly stored in + /// `~/.local/share/bash-completion/completions` for user-specific commands. + /// Run the commands: + /// + /// $ mkdir -p ~/.local/share/bash-completion/completions + /// $ bdk-cli completions bash > ~/.local/share/bash-completion/completions/bdk-cli + /// + /// Zsh: + /// + /// Completion files are commonly stored in a directory listed in your `fpath`. + /// Run the commands: + /// + /// $ mkdir -p ~/.zfunc + /// $ bdk-cli completions zsh > ~/.zfunc/_bdk-cli + /// + /// Make sure `~/.zfunc` is in your fpath by adding to your `.zshrc`: + /// + /// fpath=(~/.zfunc $fpath) + /// autoload -Uz compinit && compinit + /// + /// Fish: + /// + /// Completion files are commonly stored in + /// `~/.config/fish/completions`. Run the commands: + /// + /// $ mkdir -p ~/.config/fish/completions + /// $ bdk-cli completions fish > ~/.config/fish/completions/bdk-cli.fish + /// + /// PowerShell: + /// + /// $ bdk-cli completions powershell >> $PROFILE + /// + /// Elvish: + /// + /// $ bdk-cli completions elvish >> ~/.elvish/rc.elv + /// + /// After installing the completion script, restart your shell or source + /// the configuration file for the changes to take effect. + #[command(verbatim_doc_comment)] + Completions { + /// Target shell syntax + #[arg(value_enum)] + shell: Shell, + }, + /// Silent payment code generation tool. + /// + /// Allows the encoding of two public keys into a silent payment code. + /// Useful to create silent payment transactions using fake silent payment codes. + #[cfg(feature = "silent-payments")] + SilentPaymentCode { + /// The scan public key to use on the silent payment code. + #[arg(long = "scan_key")] + scan: bdk_sp::bitcoin::secp256k1::PublicKey, + /// The spend public key to use on the silent payment code. + #[arg(long = "spend_key")] + spend: bdk_sp::bitcoin::secp256k1::PublicKey, + }, +} + +/// Wallet operation subcommands. +#[derive(Debug, Subcommand, Clone, PartialEq)] +pub enum WalletSubCommand { + /// Save wallet configuration to `config.toml`. + Config { + /// Overwrite existing wallet configuration if it exists. + #[arg(short = 'f', long = "force", default_value_t = false)] + force: bool, + + #[command(flatten)] + wallet_opts: WalletOpts, + }, + #[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "cbf", + feature = "rpc" + ))] + #[command(flatten)] + OnlineWalletSubCommand(OnlineWalletSubCommand), + #[command(flatten)] + OfflineWalletSubCommand(OfflineWalletSubCommand), +} + +#[derive(Clone, ValueEnum, Debug, Eq, PartialEq)] +pub enum DatabaseType { + /// Sqlite database + #[cfg(feature = "sqlite")] + Sqlite, + /// Redb database + #[cfg(feature = "redb")] + Redb, +} + +#[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, +} + +/// Config options wallet operations can take. +#[derive(Debug, Args, Clone, PartialEq, Eq)] +pub struct WalletOpts { + /// Selects the wallet to use. + #[arg(skip)] + pub wallet: Option, + /// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects. + #[arg(env = "VERBOSE", short = 'v', long = "verbose")] + pub verbose: bool, + /// Sets the descriptor to use for the external addresses. + #[arg(env = "EXT_DESCRIPTOR", short = 'e', long, required = true)] + pub ext_descriptor: String, + /// Sets the descriptor to use for internal/change addresses. + #[arg(env = "INT_DESCRIPTOR", short = 'i', long)] + pub int_descriptor: Option, + #[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf" + ))] + #[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)] + pub client_type: ClientType, + #[cfg(any(feature = "sqlite", feature = "redb"))] + #[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)] + pub database_type: DatabaseType, + /// Sets the server url. + #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] + #[arg(env = "SERVER_URL", short = 'u', long, required = true)] + pub url: String, + /// Electrum batch size. + #[cfg(feature = "electrum")] + #[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")] + pub batch_size: usize, + /// Esplora parallel requests. + #[cfg(feature = "esplora")] + #[arg( + env = "ESPLORA_PARALLEL_REQUESTS", + short = 'p', + long, + default_value = "5" + )] + pub parallel_requests: usize, + #[cfg(feature = "rpc")] + /// Sets the rpc basic authentication. + #[arg( + env = "USER:PASSWD", + short = 'a', + long, + value_parser = parse_proxy_auth, + default_value = "user:password", + )] + pub basic_auth: (String, String), + #[cfg(feature = "rpc")] + /// Sets an optional cookie authentication. + #[arg(env = "COOKIE")] + pub cookie: Option, + #[cfg(feature = "cbf")] + #[clap(flatten)] + pub compactfilter_opts: CompactFilterOpts, +} + +/// 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')] + pub proxy: Option, + + /// Sets the SOCKS5 proxy credential. + #[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)] + pub proxy_auth: Option<(String, String)>, + + /// Sets the SOCKS5 proxy retries for the blockchain client. + #[arg( + env = "PROXY_RETRIES", + short = 'r', + long = "retries", + default_value = "5" + )] + pub retries: u8, + + /// Sets the SOCKS5 proxy timeout for the blockchain client. + #[arg(env = "PROXY_TIMEOUT", short = 't', long = "timeout")] + pub timeout: Option, +} + +/// Options to configure a BIP157 Compact Filter backend. +#[cfg(feature = "cbf")] +#[derive(Debug, Args, Clone, PartialEq, Eq)] +pub struct CompactFilterOpts { + /// Sets the number of parallel node connections. + #[clap(name = "CONNECTIONS", long = "cbf-conn-count", default_value = "2", value_parser = value_parser!(u8).range(1..=15))] + pub conn_count: u8, +} + +/// Wallet subcommands that can be issued without a blockchain backend. +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[command(rename_all = "snake")] +pub enum OfflineWalletSubCommand { + /// Get a new external address. + NewAddress, + /// Get the first unused external address. + UnusedAddress, + /// Lists the available spendable UTXOs. + Unspent, + /// Lists all the incoming and outgoing transactions of the wallet. + Transactions, + /// Returns the current wallet balance. + Balance, + /// Creates a new unsigned transaction. + CreateTx { + /// Adds a recipient to the transaction. + // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704. + // Address and amount parsing is done at run time in handler function. + #[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)] + recipients: Vec<(ScriptBuf, u64)>, + /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0. + #[arg(long = "send_all", short = 'a')] + send_all: bool, + /// Enables Replace-By-Fee (BIP125). + #[arg(long = "enable_rbf", short = 'r', default_value_t = true)] + enable_rbf: bool, + /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. + #[arg(long = "offline_signer")] + offline_signer: bool, + /// Selects which utxos *must* be spent. + #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] + utxos: Option>, + /// Marks a utxo as unspendable. + #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] + unspendable: Option>, + /// Fee rate to use in sat/vbyte. + #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")] + fee_rate: Option, + /// Selects which policy should be used to satisfy the external descriptor. + #[arg(env = "EXT_POLICY", long = "external_policy")] + external_policy: Option, + /// Selects which policy should be used to satisfy the internal descriptor. + #[arg(env = "INT_POLICY", long = "internal_policy")] + internal_policy: Option, + /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes) + #[arg( + env = "ADD_STRING", + long = "add_string", + short = 's', + conflicts_with = "add_data" + )] + add_string: Option, + /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes) + #[arg( + env = "ADD_DATA", + long = "add_data", + short = 'o', + conflicts_with = "add_string" + )] + add_data: Option, //base 64 econding + }, + /// Creates a silent payment transaction + /// + /// This sub-command is **EXPERIMENTAL** and should only be used for testing. Do not use this + /// feature to create transactions that spend actual funds on the Bitcoin mainnet. + + // This command DOES NOT return a PSBT. Instead, it directly returns a signed transaction + // ready for broadcast, as it is not yet possible to perform a shared derivation of a silent + // payment script pubkey in a secure and trustless manner. + #[cfg(feature = "silent-payments")] + CreateSpTx { + /// Adds a recipient to the transaction. + // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704. + // Address and amount parsing is done at run time in handler function. + #[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)] + recipients: Option>, + /// Parse silent payment recipients + #[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)] + silent_payment_recipients: Vec<(SilentPaymentCode, u64)>, + /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0. + #[arg(long = "send_all", short = 'a')] + send_all: bool, + /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. + #[arg(long = "offline_signer")] + offline_signer: bool, + /// Selects which utxos *must* be spent. + #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] + utxos: Option>, + /// Marks a utxo as unspendable. + #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] + unspendable: Option>, + /// Fee rate to use in sat/vbyte. + #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")] + fee_rate: Option, + /// Selects which policy should be used to satisfy the external descriptor. + #[arg(env = "EXT_POLICY", long = "external_policy")] + external_policy: Option, + /// Selects which policy should be used to satisfy the internal descriptor. + #[arg(env = "INT_POLICY", long = "internal_policy")] + internal_policy: Option, + /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes) + #[arg( + env = "ADD_STRING", + long = "add_string", + short = 's', + conflicts_with = "add_data" + )] + add_string: Option, + /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes) + #[arg( + env = "ADD_DATA", + long = "add_data", + short = 'o', + conflicts_with = "add_string" + )] + add_data: Option, //base 64 econding + }, + /// Bumps the fees of an RBF transaction. + BumpFee { + /// TXID of the transaction to update. + #[arg(env = "TXID", long = "txid")] + txid: String, + /// 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)] + shrink_address: Option
, + /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. + #[arg(long = "offline_signer")] + offline_signer: bool, + /// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used. + #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] + utxos: Option>, + /// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees. + #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] + unspendable: Option>, + /// The new targeted fee rate in sat/vbyte. + #[arg( + env = "SATS_VBYTE", + short = 'f', + long = "fee_rate", + default_value = "1.0" + )] + fee_rate: f32, + }, + /// Returns the available spending policies for the descriptor. + Policies, + /// Returns the public version of the wallet's descriptor(s). + PublicDescriptor, + /// Signs and tries to finalize a PSBT. + Sign { + /// Sets the PSBT to sign. + #[arg(env = "BASE64_PSBT")] + psbt: String, + /// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor. + #[arg(env = "HEIGHT", long = "assume_height")] + assume_height: Option, + /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. + #[arg(env = "WITNESS", long = "trust_witness_utxo")] + trust_witness_utxo: Option, + }, + /// Extracts a raw transaction from a PSBT. + ExtractPsbt { + /// Sets the PSBT to extract + #[arg(env = "BASE64_PSBT")] + psbt: String, + }, + /// Finalizes a PSBT. + FinalizePsbt { + /// Sets the PSBT to finalize. + #[arg(env = "BASE64_PSBT")] + psbt: String, + /// Assume the blockchain has reached a specific height. + #[arg(env = "HEIGHT", long = "assume_height")] + assume_height: Option, + /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. + #[arg(env = "WITNESS", long = "trust_witness_utxo")] + trust_witness_utxo: Option, + }, + /// Combines multiple PSBTs into one. + CombinePsbt { + /// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT. + #[arg(env = "BASE64_PSBT", required = true)] + psbt: Vec, + }, + /// Sign a message using BIP322 + #[cfg(feature = "bip322")] + SignMessage { + /// The message to sign + #[arg(long)] + message: String, + /// The signature format (e.g., Legacy, Simple, Full) + #[arg(long, default_value = "simple")] + signature_type: String, + /// Address to sign + #[arg(long)] + address: String, + /// Optional list of specific UTXOs for proof-of-funds (only for `FullWithProofOfFunds`) + #[arg(long)] + utxos: Option>, + }, + /// Verify a BIP322 signature + #[cfg(feature = "bip322")] + VerifyMessage { + /// The signature proof to verify + #[arg(long)] + proof: String, + /// The message that was signed + #[arg(long)] + message: String, + /// The address associated with the signature + #[arg(long)] + address: String, + }, +} + +/// Wallet subcommands that needs a blockchain backend. +#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] +#[command(rename_all = "snake")] +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "cbf", + feature = "rpc" +))] +pub enum OnlineWalletSubCommand { + /// Full Scan with the chosen blockchain server. + FullScan { + /// 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, + }, + /// Syncs with the chosen blockchain server. + Sync, + /// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract. + Broadcast { + /// Sets the PSBT to sign. + #[arg( + env = "BASE64_PSBT", + long = "psbt", + required_unless_present = "tx", + conflicts_with = "tx" + )] + psbt: Option, + /// Sets the raw transaction to broadcast. + #[arg( + env = "RAWTX", + long = "tx", + required_unless_present = "psbt", + conflicts_with = "psbt" + )] + tx: Option, + }, + /// Generates a Payjoin receive URI and processes the sender's Payjoin proposal. + ReceivePayjoin { + /// Amount to be received in sats. + #[arg(env = "PAYJOIN_AMOUNT", long = "amount", required = true)] + amount: u64, + /// Payjoin directory which will be used to store the PSBTs which are pending action + /// from one of the parties. + #[arg(env = "PAYJOIN_DIRECTORY", long = "directory", required = true)] + directory: String, + /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the + /// operation with multiple relays for redundancy. + #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay", required = true)] + ohttp_relay: Vec, + /// Maximum effective fee rate the receiver is willing to pay for their own input/output contributions. + #[arg(env = "PAYJOIN_RECEIVER_MAX_FEE_RATE", long = "max_fee_rate")] + max_fee_rate: Option, + }, + /// Sends an original PSBT to a BIP 21 URI and broadcasts the returned Payjoin PSBT. + SendPayjoin { + /// BIP 21 URI for the Payjoin. + #[arg(env = "PAYJOIN_URI", long = "uri", required = true)] + uri: String, + /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the + /// operation with multiple relays for redundancy. + #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay", required = true)] + ohttp_relay: Vec, + /// Fee rate to use in sat/vbyte. + #[arg( + env = "PAYJOIN_SENDER_FEE_RATE", + short = 'f', + long = "fee_rate", + required = true + )] + fee_rate: u64, + }, +} + +/// Subcommands for Key operations. +#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] +pub enum KeySubCommand { + /// Generates new random seed mnemonic phrase and corresponding master extended key. + Generate { + /// Entropy level based on number of random seed mnemonic words. + #[arg( + env = "WORD_COUNT", + short = 'e', + long = "entropy", + default_value = "12" + )] + word_count: usize, + /// Seed password. + #[arg(env = "PASSWORD", short = 'p', long = "password")] + password: Option, + }, + /// Restore a master extended key from seed backup mnemonic words. + Restore { + /// Seed mnemonic words, must be quoted (eg. "word1 word2 ..."). + #[arg(env = "MNEMONIC", short = 'm', long = "mnemonic")] + mnemonic: String, + /// Seed password. + #[arg(env = "PASSWORD", short = 'p', long = "password")] + password: Option, + }, + /// Derive a child key pair from a master extended key and a derivation path string (eg. "m/84'/1'/0'/0" or "m/84h/1h/0h/0"). + Derive { + /// Extended private key to derive from. + #[arg(env = "XPRV", short = 'x', long = "xprv")] + xprv: Xpriv, + /// Path to use to derive extended public key from extended private key. + #[arg(env = "PATH", short = 'p', long = "path")] + path: DerivationPath, + }, +} + +/// Subcommands available in REPL mode. +#[cfg(any(feature = "repl", target_arch = "wasm32"))] +#[derive(Debug, Parser)] +#[command(rename_all = "lower", multicall = true)] +pub enum ReplSubCommand { + /// Execute wallet commands. + Wallet { + #[command(subcommand)] + subcommand: WalletSubCommand, + }, + /// Execute key commands. + Key { + #[command(subcommand)] + subcommand: KeySubCommand, + }, + /// Generate descriptors + Descriptor { + /// Descriptor type (script type). + #[arg( + long = "type", + short = 't', + value_parser = ["pkh", "wpkh", "sh", "wsh", "tr"], + default_value = "wsh" + )] + desc_type: String, + /// Optional key: xprv, xpub, or mnemonic phrase + key: Option, + }, + /// Exit REPL loop. + Exit, +}