bdk-cli 4.0.0

An experimental CLI wallet application and playground, powered by BDK
#[cfg(feature = "repl")]
use {
    crate::commands::ReplSubCommand,
    crate::handlers::{AppCommand, AppContext},
    crate::utils::output::FormatOutput,
    bdk_wallet::{Wallet, bitcoin::Network},
    clap::Parser,
};

#[cfg(all(
    feature = "repl",
    any(
        feature = "electrum",
        feature = "esplora",
        feature = "rpc",
        feature = "cbf"
    )
))]
use crate::client::BlockchainClient;
#[cfg(feature = "repl")]
use {crate::commands::WalletSubCommand, crate::error::BDKCliError as Error, std::io::Write};

#[cfg(feature = "repl")]
pub(crate) async fn respond(
    network: Network,
    wallet: &mut Wallet,
    #[cfg(any(
        feature = "electrum",
        feature = "esplora",
        feature = "rpc",
        feature = "cbf"
    ))]
    client: Option<&BlockchainClient>,
    line: &str,
    datadir: std::path::PathBuf,
    #[cfg(any(
        feature = "electrum",
        feature = "esplora",
        feature = "rpc",
        feature = "cbf"
    ))]
    wallet_name: &str,
) -> Result<bool, String> {
    let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;

    let repl_subcommand = match ReplSubCommand::try_parse_from(&args) {
        Ok(cmd) => cmd,
        Err(e) => {
            writeln!(std::io::stdout(), "{}", e).map_err(|e| e.to_string())?;
            return Ok(false);
        }
    };

    let response = match repl_subcommand {
        ReplSubCommand::Wallet { subcommand } => match subcommand {
            WalletSubCommand::OfflineWalletSubCommand(cmd) => {
                let mut ctx = AppContext::new_offline_wallet(network, datadir, wallet);
                cmd.execute(&mut ctx).map_err(|e| e.to_string())?;
                Some(())
            }
            #[cfg(any(
                feature = "electrum",
                feature = "esplora",
                feature = "cbf",
                feature = "rpc"
            ))]
            WalletSubCommand::OnlineWalletSubCommand(cmd) => {
                let client_ref = client.ok_or("Online commands require a client.".to_string())?;
                let mut ctx = AppContext::new_online_wallet(
                    network,
                    datadir,
                    wallet,
                    client_ref,
                    wallet_name.to_string(),
                );

                cmd.execute(&mut ctx).await.map_err(|e| e.to_string())?;
                Some(())
            }
            WalletSubCommand::Config(_) => {
                writeln!(
                    std::io::stdout(),
                    "`config` is not available in REPL mode — the wallet for this session \
         is already loaded. Exit and run `bdk-cli wallet --wallet <name> config ...`."
                )
                .map_err(|e| e.to_string())?;
                Some(())
            }
        },

        ReplSubCommand::Descriptor(cmd) => {
            let mut ctx = AppContext::new(network, datadir);
            cmd.execute(&mut ctx)
                .map_err(|e| e.to_string())?
                .write_out(std::io::stdout())
                .map_err(|e| e.to_string())?;
            Some(())
        }

        ReplSubCommand::Key { subcommand } => {
            let mut ctx = AppContext::new(network, datadir);
            subcommand.execute(&mut ctx).map_err(|e| e.to_string())?;
            Some(())
        }

        ReplSubCommand::Exit => None,
    };

    if response.is_some() {
        std::io::stdout().flush().map_err(|e| e.to_string())?;
        Ok(false)
    } else {
        writeln!(std::io::stdout(), "Exiting...").map_err(|e| e.to_string())?;
        std::io::stdout().flush().map_err(|e| e.to_string())?;
        Ok(true)
    }
}

#[cfg(feature = "repl")]
pub(crate) fn readline() -> Result<String, Error> {
    write!(std::io::stdout(), "> ").map_err(|e| Error::Generic(e.to_string()))?;
    std::io::stdout()
        .flush()
        .map_err(|e| Error::Generic(e.to_string()))?;
    let mut buffer = String::new();
    std::io::stdin()
        .read_line(&mut buffer)
        .map_err(|e| Error::Generic(e.to_string()))?;
    Ok(buffer)
}