]> Untitled Git - bdk-cli/commitdiff
revert mod names for command and clients
authorVihiga Tyonum <withtvpeter@gmail.com>
Thu, 14 May 2026 10:09:22 +0000 (11:09 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:16:41 +0000 (17:16 +0100)
src/client.rs [new file with mode: 0644]
src/commands.rs [new file with mode: 0644]

diff --git a/src/client.rs b/src/client.rs
new file mode 100644 (file)
index 0000000..2b97330
--- /dev/null
@@ -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<bdk_electrum::BdkElectrumClient<bdk_electrum::electrum_client::Client>>,
+        batch_size: usize,
+    },
+    // #[cfg(feature = "esplora")]
+    Esplora {
+        client: Box<bdk_esplora::esplora_client::AsyncClient>,
+        parallel_requests: usize,
+    },
+    // #[cfg(feature = "rpc")]
+    RpcClient {
+        client: Box<bdk_bitcoind_rpc::bitcoincore_rpc::Client>,
+    },
+
+    // #[cfg(feature = "cbf")]
+    KyotoClient {
+        client: Box<KyotoClientHandle>,
+    },
+}
+
+impl BlockchainClient {
+    pub async fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
+        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<bdk_kyoto::UpdateSubscriber>,
+}
+
+// #[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<BlockchainClient, Error> {
+    // #[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 (file)
index 0000000..127d32b
--- /dev/null
@@ -0,0 +1,725 @@
+// Copyright (c) 2020-2025 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 <https://bitcoindevkit.org/>
+#[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<std::path::PathBuf>,
+    /// 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<String>,
+    },
+    /// 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<String, Error> {
+        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<String>,
+    /// 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<String>,
+    // #[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<String>,
+    // #[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<String>,
+
+    /// 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<u8>,
+}
+
+/// 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<Vec<OutPoint>>,
+    //     /// Marks a utxo as unspendable.
+    //     #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+    //     unspendable: Option<Vec<OutPoint>>,
+    //     /// Fee rate to use in sat/vbyte.
+    //     #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
+    //     fee_rate: Option<f32>,
+    //     /// Selects which policy should be used to satisfy the external descriptor.
+    //     #[arg(env = "EXT_POLICY", long = "external_policy")]
+    //     external_policy: Option<String>,
+    //     /// Selects which policy should be used to satisfy the internal descriptor.
+    //     #[arg(env = "INT_POLICY", long = "internal_policy")]
+    //     internal_policy: Option<String>,
+    //     /// 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<String>,
+    //     /// 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<String>, //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<Address>,
+    //     /// 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<Vec<OutPoint>>,
+    //     /// 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<Vec<OutPoint>>,
+    //     /// 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<u32>,
+    //     /// 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<bool>,
+    // },
+    /// 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<u32>,
+    //     /// 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<bool>,
+    // },
+    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<String>,
+    // },
+    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<Vec<OutPoint>>,
+    // },
+    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<String>,
+    //     /// Sets the raw transaction to broadcast.
+    //     #[arg(
+    //         env = "RAWTX",
+    //         long = "tx",
+    //         required_unless_present = "psbt",
+    //         conflicts_with = "psbt"
+    //     )]
+    //     tx: Option<String>,
+    // },
+    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<String>,
+    //     /// 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<u64>,
+    // },
+    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<String>,
+    //     /// 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<String>,
+    },
+    /// 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<String>,
+    },
+    /// 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<String>,
+    },
+    /// Exit REPL loop.
+    Exit,
+}