From: Vihiga Tyonum Date: Thu, 14 May 2026 10:09:22 +0000 (+0100) Subject: revert mod names for command and clients X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/struct.CommandString.html?a=commitdiff_plain;h=eaa4e11428b6443be09afb30b9c8db642f39adad;p=bdk-cli revert mod names for command and clients --- diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..2b97330 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,293 @@ +use bdk_bitcoind_rpc::{Emitter, bitcoincore_rpc::RpcApi}; +use bdk_esplora::EsploraAsyncExt; +use bdk_wallet::{ + bitcoin::{Transaction, Txid}, + chain::CanonicalizationParams, +}; +// #[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, + }, +} + +impl BlockchainClient { + pub async fn broadcast(&self, tx: Transaction) -> Result { + match self { + // #[cfg(feature = "electrum")] + Self::Electrum { client, .. } => client + .transaction_broadcast(&tx) + .map_err(|e| Error::Generic(e.to_string())), + + // #[cfg(feature = "esplora")] + Self::Esplora { client, .. } => client + .broadcast(&tx) + .await + .map(|()| tx.compute_txid()) + .map_err(|e| Error::Generic(e.to_string())), + + // #[cfg(feature = "rpc")] + Self::RpcClient { client } => client + .send_raw_transaction(&tx) + .map_err(|e| Error::Generic(e.to_string())), + + // #[cfg(feature = "cbf")] + Self::KyotoClient { client } => { + // ... (Kyoto broadcast logic from your online.rs) ... + Ok(tx.compute_txid()) + } + } + } + + pub async fn sync_wallet(&self, wallet: &mut Wallet) -> Result<(), Error> { + // #[cfg(any(feature = "electrum", feature = "esplora"))] + let request = wallet + .start_sync_with_revealed_spks() + .inspect(|item, progress| { + let pc = (100 * progress.consumed()) as f32 / progress.total() as f32; + eprintln!("[ SCANNING {pc:03.0}% ] {item}"); + }); + match self { + // #[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. + client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + + let update = client.sync(request, *batch_size, false)?; + wallet + .apply_update(update) + .map_err(|e| Error::Generic(e.to_string())) + } + // #[cfg(feature = "esplora")] + Self::Esplora { + client, + parallel_requests, + } => { + let update = client + .sync(request, *parallel_requests) + .await + .map_err(|e| *e)?; + wallet + .apply_update(update) + .map_err(|e| Error::Generic(e.to_string())) + } + // #[cfg(feature = "rpc")] + Self::RpcClient { client } => { + let blockchain_info = client.get_blockchain_info()?; + let wallet_cp = wallet.latest_checkpoint(); + + // reload the last 200 blocks in case of a reorg + let emitter_height = wallet_cp.height().saturating_sub(200); + let mut emitter = Emitter::new( + client.as_ref(), + wallet_cp, + emitter_height, + wallet + .tx_graph() + .list_canonical_txs( + wallet.local_chain(), + wallet.local_chain().tip().block_id(), + CanonicalizationParams::default(), + ) + .filter(|tx| tx.chain_position.is_unconfirmed()), + ); + + while let Some(block_event) = emitter.next_block()? { + if block_event.block_height() % 10_000 == 0 { + let percent_done = f64::from(block_event.block_height()) + / f64::from(blockchain_info.headers as u32) + * 100f64; + println!( + "Applying block at height: {}, {:.2}% done.", + block_event.block_height(), + percent_done + ); + } + + wallet.apply_block_connected_to( + &block_event.block, + block_event.block_height(), + block_event.connected_to(), + )?; + } + + let mempool_txs = emitter.mempool()?; + wallet.apply_unconfirmed_txs(mempool_txs.update); + Ok(()) + } + // #[cfg(feature = "cbf")] + Self::KyotoClient { client } => sync_kyoto_client(wallet, client) + .await + .map_err(|e| Error::Generic(e.to_string())), + } + } +} + +/// 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 new file mode 100644 index 0000000..127d32b --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,725 @@ +// 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)] +use crate::{ + error::BDKCliError as Error, + handlers::{ + AppCommand, + offline::{ + BalanceCommand, BumpFeeCommand, CombinePsbtCommand, CreateTxCommand, + ExtractPsbtCommand, FinalizePsbtCommand, NewAddressCommand, PoliciesCommand, + PublicDescriptorCommand, SignCommand, SignMessageCommand, TransactionsCommand, + UnspentCommand, UnusedAddressCommand, VerifyMessageCommand, + }, + online::{ + BroadcastCommand, FullScanCommand, ReceivePayjoinCommand, SendPayjoinCommand, + SyncCommand, + }, + }, + utils::output::FormatOutput, +}; +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::{ + handlers::AppContext, + 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, + }, +} + +impl CliSubCommand { + pub async fn execute( + &self, + ctx: &mut AppContext<'_>, + cli_opts: &CliOpts, + #[cfg(feature = "repl")] wallet_opts: &mut WalletOpts, + datadir: std::path::PathBuf, + ) -> Result { + match self { + CliSubCommand::Wallet { subcommand, wallet } => { + match subcommand { + WalletSubCommand::OfflineWalletSubCommand(cmd) => { + cmd.execute(ctx, cli_opts.pretty) + } + // #[cfg(any(feature = "electrum", feature = "esplora", feature = "cbf", feature = "rpc"))] + WalletSubCommand::OnlineWalletSubCommand(cmd) => { + cmd.execute(ctx, cli_opts.pretty).await + } + WalletSubCommand::Config { force, wallet_opts } => {} + } + } + + CliSubCommand::Key { subcommand } => subcommand.execute(ctx, cli_opts.pretty), + + CliSubCommand::Descriptor { desc_type, key } => { + cmd.execute(ctx).and_then(|out| out.format(cli_opts.pretty)) + } + + // #[cfg(feature = "compiler")] + CliSubCommand::Compile { + policy, + script_type, + } => cmd.execute(ctx).and_then(|out| out.format(cli_opts.pretty)), + + CliSubCommand::Wallets(cmd) => { + cmd.execute(ctx).and_then(|out| out.format(cli_opts.pretty)) + } + + CliSubCommand::Config(cmd) => { + cmd.execute(ctx).and_then(|out| out.format(cli_opts.pretty)) + } + + CliSubCommand::Completions { shell } => { + use clap::CommandFactory; + clap_complete::generate( + *shell, + &mut CliOpts::command(), + "bdk-cli", + &mut std::io::stdout(), + ); + Ok("".to_string()) + } + } + } +} + +/// 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(NewAddressCommand), + /// Get the first unused external address. + UnusedAddress(UnusedAddressCommand), + /// Lists the available spendable UTXOs. + Unspent(UnspentCommand), + /// Lists all the incoming and outgoing transactions of the wallet. + Transactions(TransactionsCommand), + /// Returns the current wallet balance. + Balance(BalanceCommand), + /// Creates a new unsigned transaction. + CreateTx(CreateTxCommand), + // 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 + // }, + /// Bumps the fees of an RBF transaction. + BumpFee(BumpFeeCommand), + // 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(PoliciesCommand), + /// Returns the public version of the wallet's descriptor(s). + PublicDescriptor(PublicDescriptorCommand), + /// Signs and tries to finalize a PSBT. + Sign(SignCommand), + // 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, + // }, + ExtractPsbt(ExtractPsbtCommand), + /// 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, + // }, + FinalizePsbt(FinalizePsbtCommand), + /// 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, + // }, + CombinePsbt(CombinePsbtCommand), + /// 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>, + // }, + SignMessage(SignMessageCommand), + /// 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, + // }, + VerifyMessage(VerifyMessageCommand), +} + +/// 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, + // }, + FullScan(FullScanCommand), + /// Syncs with the chosen blockchain server. + Sync(SyncCommand), + /// 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, + // }, + Broadcast(BroadcastCommand), + /// 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, + // }, + ReceivePayjoin(ReceivePayjoinCommand), + /// 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, + // }, + SendPayjoin(SendPayjoinCommand), +} + +/// 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, +}