]> Untitled Git - bdk-cli/commitdiff
ref(pretty): remove `--pretty` flag
authorVihiga Tyonum <withtvpeter@gmail.com>
Thu, 21 May 2026 15:17:39 +0000 (16:17 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:32:14 +0000 (17:32 +0100)
- remove --pretty flag
- move types to utils
- update commands

README.md
src/commands.rs
src/handlers/types.rs [deleted file]
src/utils/types.rs [new file with mode: 0644]

index 2073a67b1d3578af88cc41a206885648272120a8..35229999238c104a6d81bc748facee58c7cc40f2 100644 (file)
--- a/README.md
+++ b/README.md
@@ -254,15 +254,6 @@ Note: You can modify the `Justfile` to reflect your nodes' configuration values.
    cargo run --features rpc -- wallet -w regtest1 balance
    ```
 
-## Formatting Responses using `--pretty` flag
-
-You can optionally return outputs of commands in  human-readable, tabular format instead of `JSON`. To enable this option, simply add the `--pretty` flag as a top level flag. For instance, you wallet's balance in a pretty format, you can run:
-
-```shell
-cargo run -- --pretty -n signet wallet -w {wallet_name} balance
-```
-This is available for wallet, key, repl and compile features. When ommitted, outputs default to `JSON`.
-
 ## Shell Completions
 
 `bdk-cli` supports generating shell completions for Bash, Zsh, Fish, Elvish, and PowerShell. For setup instructions, run:
index 127d32bd5dcec1d81b2f35fd21d907a2b30c035f..cb1c925103fad65238d00bb44441575f030fbd73 100644 (file)
 //! 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,
-        },
+#[cfg(feature = "bip322")]
+use crate::handlers::offline::{SignMessageCommand, VerifyMessageCommand};
+use crate::handlers::{
+    config::{ListWalletsCommand, SaveConfigCommand},
+    descriptor::DescriptorCommand,
+    key::{DeriveKeyCommand, GenerateKeyCommand, RestoreKeyCommand},
+    offline::{
+        BalanceCommand, BumpFeeCommand, CombinePsbtCommand, CreateTxCommand, ExtractPsbtCommand,
+        FinalizePsbtCommand, NewAddressCommand, PoliciesCommand, PublicDescriptorCommand,
+        SignCommand, TransactionsCommand, UnspentCommand, UnusedAddressCommand,
+    },
+    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",
+    feature = "cbf"
+))]
+use crate::client::ClientType;
+
+#[cfg(feature = "compiler")]
+use crate::handlers::descriptor::CompileCommand;
+#[cfg(any(feature = "sqlite", feature = "redb"))]
+use crate::persister::DatabaseType;
+
+use bdk_wallet::bitcoin::Network;
+use clap::{Args, Parser, Subcommand, value_parser};
+use clap_complete::Shell;
 
 // #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
 use crate::utils::parse_proxy_auth;
@@ -72,9 +76,6 @@ pub struct CliOpts {
     /// 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,
@@ -110,17 +111,9 @@ pub enum CliSubCommand {
         subcommand: KeySubCommand,
     },
     /// Compile a miniscript policy to an output descriptor.
-    // #[cfg(feature = "compiler")]
+    #[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,
-    },
+    Compile(CompileCommand),
     // #[cfg(feature = "repl")]
     /// REPL command loop mode.
     ///
@@ -131,24 +124,15 @@ pub enum CliSubCommand {
         #[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>,
-    },
+    Descriptor(DescriptorCommand),
+
     /// List all saved wallet configurations.
-    Wallets,
+    Wallets(ListWalletsCommand),
     /// Generate tab-completion scripts for your shell.
     ///
     /// The completion script is output on stdout, allowing you to redirect
@@ -205,114 +189,23 @@ pub enum CliSubCommand {
     },
 }
 
-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"
-    // ))]
+    Config(SaveConfigCommand),
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "rpc",
+        feature = "cbf"
+    ))]
     #[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 {
@@ -328,27 +221,27 @@ pub struct WalletOpts {
     /// 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"
-    // ))]
+    #[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"))]
+    #[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")]
+    #[cfg(feature = "electrum")]
     #[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")]
     pub batch_size: usize,
     /// Esplora parallel requests.
-    // #[cfg(feature = "esplora")]
+    #[cfg(feature = "esplora")]
     #[arg(
         env = "ESPLORA_PARALLEL_REQUESTS",
         short = 'p',
@@ -356,7 +249,7 @@ pub struct WalletOpts {
         default_value = "5"
     )]
     pub parallel_requests: usize,
-    // #[cfg(feature = "rpc")]
+    #[cfg(feature = "rpc")]
     /// Sets the rpc basic authentication.
     #[arg(
         env = "USER:PASSWD",
@@ -366,11 +259,11 @@ pub struct WalletOpts {
         default_value = "user:password",
     )]
     pub basic_auth: (String, String),
-    // #[cfg(feature = "rpc")]
+    #[cfg(feature = "rpc")]
     /// Sets an optional cookie authentication.
     #[arg(env = "COOKIE")]
     pub cookie: Option<String>,
-    // #[cfg(feature = "cbf")]
+    #[cfg(feature = "cbf")]
     #[clap(flatten)]
     pub compactfilter_opts: CompactFilterOpts,
 }
@@ -426,126 +319,23 @@ pub enum OfflineWalletSubCommand {
     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")]
+    #[cfg(feature = "bip322")]
     // SignMessage {
     //     /// The message to sign
     //     #[arg(long)]
@@ -562,7 +352,7 @@ pub enum OfflineWalletSubCommand {
     // },
     SignMessage(SignMessageCommand),
     /// Verify a BIP322 signature
-    // #[cfg(feature = "bip322")]
+    #[cfg(feature = "bip322")]
     // VerifyMessage {
     //     /// The signature proof to verify
     //     #[arg(long)]
@@ -588,33 +378,10 @@ pub enum OfflineWalletSubCommand {
 // ))]
 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 {
@@ -659,37 +426,11 @@ pub enum OnlineWalletSubCommand {
 #[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>,
-    },
+    Generate(GenerateKeyCommand),
     /// 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>,
-    },
+    Restore(RestoreKeyCommand),
     /// 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,
-    },
+    Derive(DeriveKeyCommand),
 }
 
 /// Subcommands available in REPL mode.
@@ -708,18 +449,7 @@ pub enum ReplSubCommand {
         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>,
-    },
+    Descriptor(DescriptorCommand),
     /// Exit REPL loop.
     Exit,
 }
diff --git a/src/handlers/types.rs b/src/handlers/types.rs
deleted file mode 100644 (file)
index f6f03d4..0000000
+++ /dev/null
@@ -1,573 +0,0 @@
-use std::collections::HashMap;
-
-use crate::config::WalletConfigInner;
-use crate::utils::output::{FormatOutput, simple_table};
-use crate::{error::BDKCliError as Error, utils::shorten};
-use bdk_wallet::bitcoin::{
-    Address, Network, Psbt, Transaction, base64::Engine, consensus::encode::serialize_hex,
-};
-use bdk_wallet::{AddressInfo, Balance, LocalOutput, chain::ChainPosition};
-use cli_table::{Cell, CellStruct, Style, format::Justify};
-use serde::Serialize;
-use serde_json::json;
-
-/// Represent address result
-#[derive(Serialize)]
-pub struct AddressResult {
-    pub address: String,
-    pub index: u32,
-}
-
-impl From<AddressInfo> for AddressResult {
-    fn from(info: AddressInfo) -> Self {
-        Self {
-            address: info.address.to_string(),
-            index: info.index,
-        }
-    }
-}
-
-/// pretty presentation for address
-impl FormatOutput for AddressResult {
-    fn to_table(&self) -> Result<String, Error> {
-        simple_table(
-            vec![
-                vec!["Address".cell().bold(true), self.address.clone().cell()],
-                vec![
-                    "Index".cell().bold(true),
-                    self.index.cell().justify(Justify::Right),
-                ],
-            ],
-            None,
-        )
-    }
-}
-
-/// Represents the data for a single transaction
-#[derive(Serialize)]
-pub struct TransactionDetails {
-    pub txid: String,
-    pub is_coinbase: bool,
-    pub wtxid: String,
-    pub version: serde_json::Value,
-    pub is_rbf: bool,
-    pub inputs: serde_json::Value,
-    pub outputs: serde_json::Value,
-    #[serde(skip)]
-    pub version_display: String,
-    #[serde(skip)]
-    pub input_count: usize,
-    #[serde(skip)]
-    pub output_count: usize,
-    #[serde(skip)]
-    pub total_value: u64,
-}
-
-/// A wrapper type for a list of transactions.
-#[derive(Serialize)]
-#[serde(transparent)]
-pub struct TransactionListResult(pub Vec<TransactionDetails>);
-
-impl FormatOutput for TransactionListResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let rows = self.0.iter().map(|tx| {
-            vec![
-                tx.txid.clone().cell(),
-                tx.version_display.clone().cell().justify(Justify::Right),
-                tx.is_rbf.to_string().cell().justify(Justify::Center),
-                tx.input_count.to_string().cell().justify(Justify::Right),
-                tx.output_count.to_string().cell().justify(Justify::Right),
-                tx.total_value.to_string().cell().justify(Justify::Right),
-            ]
-        });
-
-        simple_table(
-            rows,
-            Some(vec![
-                "Txid".cell().bold(true),
-                "Version".cell().bold(true),
-                "Is RBF".cell().bold(true),
-                "Input Count".cell().bold(true),
-                "Output Count".cell().bold(true),
-                "Total Value (sat)".cell().bold(true),
-            ]),
-        )
-    }
-}
-
-/// Balance representation
-#[derive(Serialize)]
-pub struct BalanceResult {
-    pub total: u64,
-    pub trusted_pending: u64,
-    pub untrusted_pending: u64,
-    pub immature: u64,
-    pub confirmed: u64,
-}
-
-impl From<Balance> for BalanceResult {
-    fn from(b: Balance) -> Self {
-        Self {
-            total: b.total().to_sat(),
-            confirmed: b.confirmed.to_sat(),
-            trusted_pending: b.trusted_pending.to_sat(),
-            untrusted_pending: b.untrusted_pending.to_sat(),
-            immature: b.immature.to_sat(),
-        }
-    }
-}
-
-impl FormatOutput for BalanceResult {
-    fn to_table(&self) -> Result<String, Error> {
-        simple_table(
-            vec![
-                vec![
-                    "Total".cell().bold(true),
-                    self.total.cell().justify(Justify::Right),
-                ],
-                vec![
-                    "Confirmed".cell().bold(true),
-                    self.confirmed.cell().justify(Justify::Right),
-                ],
-                vec![
-                    "Trusted Pending".cell().bold(true),
-                    self.trusted_pending.cell().justify(Justify::Right),
-                ],
-                vec![
-                    "Untrusted Pending".cell().bold(true),
-                    self.untrusted_pending.cell().justify(Justify::Right),
-                ],
-                vec![
-                    "Immature".cell().bold(true),
-                    self.immature.cell().justify(Justify::Right),
-                ],
-            ],
-            Some(vec![
-                "Status".cell().bold(true),
-                "Amount (sat)".cell().bold(true),
-            ]),
-        )
-    }
-}
-
-/// single UTXO
-#[derive(Serialize)]
-pub struct UnspentDetails {
-    pub outpoint: String,
-    pub txout: serde_json::Value,
-    pub keychain: String,
-    pub is_spent: bool,
-    pub derivation_index: u32,
-    pub chain_position: serde_json::Value,
-
-    #[serde(skip)]
-    pub value_sat: u64,
-    #[serde(skip)]
-    pub address: String,
-    #[serde(skip)]
-    pub outpoint_display: String,
-    #[serde(skip)]
-    pub height_display: String,
-    #[serde(skip)]
-    pub block_hash_display: String,
-}
-
-impl UnspentDetails {
-    pub fn from_local_output(utxo: &LocalOutput, network: Network) -> Self {
-        let height = utxo.chain_position.confirmation_height_upper_bound();
-        let height_display = height
-            .map(|h| h.to_string())
-            .unwrap_or_else(|| "Pending".to_string());
-
-        let (_, block_hash_display) = match &utxo.chain_position {
-            ChainPosition::Confirmed { anchor, .. } => {
-                let hash = anchor.block_id.hash.to_string();
-                (Some(hash.clone()), shorten(&hash, 8, 8))
-            }
-            ChainPosition::Unconfirmed { .. } => (None, "Unconfirmed".to_string()),
-        };
-
-        let address = Address::from_script(&utxo.txout.script_pubkey, network)
-            .map(|a| a.to_string())
-            .unwrap_or_else(|_| "Unknown Script".to_string());
-
-        let outpoint_str = utxo.outpoint.to_string();
-
-        Self {
-            outpoint: outpoint_str.clone(),
-            txout: serde_json::to_value(&utxo.txout).unwrap_or(json!({})),
-            keychain: format!("{:?}", utxo.keychain),
-            is_spent: utxo.is_spent,
-            derivation_index: utxo.derivation_index,
-            chain_position: serde_json::to_value(utxo.chain_position).unwrap_or(json!({})),
-
-            value_sat: utxo.txout.value.to_sat(),
-            address,
-            outpoint_display: shorten(&outpoint_str, 8, 10),
-            height_display,
-            block_hash_display,
-        }
-    }
-}
-
-/// Wrapper for the list of UTXOs
-#[derive(Serialize)]
-#[serde(transparent)]
-pub struct UnspentListResult(pub Vec<UnspentDetails>);
-
-impl FormatOutput for UnspentListResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let mut rows: Vec<Vec<CellStruct>> = vec![];
-
-        for utxo in &self.0 {
-            rows.push(vec![
-                utxo.outpoint_display.clone().cell(),
-                utxo.value_sat.to_string().cell().justify(Justify::Right),
-                utxo.address.clone().cell(),
-                utxo.keychain.clone().cell(),
-                utxo.is_spent.cell(),
-                utxo.derivation_index.cell(),
-                utxo.height_display.clone().cell().justify(Justify::Right),
-                utxo.block_hash_display
-                    .clone()
-                    .cell()
-                    .justify(Justify::Right),
-            ]);
-        }
-
-        let title = vec![
-            "Outpoint".cell().bold(true),
-            "Output (sat)".cell().bold(true),
-            "Output Address".cell().bold(true),
-            "Keychain".cell().bold(true),
-            "Is Spent".cell().bold(true),
-            "Index".cell().bold(true),
-            "Block Height".cell().bold(true),
-            "Block Hash".cell().bold(true),
-        ];
-        simple_table(rows, Some(title))
-    }
-}
-
-#[derive(Serialize)]
-pub struct PsbtResult {
-    pub psbt: String,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub is_finalized: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub details: Option<serde_json::Value>,
-}
-
-impl PsbtResult {
-    pub fn new(psbt: &Psbt, verbose: bool, finalized: Option<bool>) -> Self {
-        Self {
-            psbt: bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD.encode(psbt.serialize()),
-            is_finalized: finalized,
-            details: if verbose {
-                Some(serde_json::to_value(psbt).unwrap_or_default())
-            } else {
-                None
-            },
-        }
-    }
-}
-
-impl FormatOutput for PsbtResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let mut rows = vec![vec![
-            "PSBT (Base64)".cell().bold(true),
-            self.psbt.clone().cell(),
-        ]];
-
-        if let Some(finalized) = self.is_finalized {
-            rows.push(vec!["Is Finalized".cell().bold(true), finalized.cell()]);
-        }
-
-        if self.details.is_some() {
-            rows.push(vec![
-                "Details".cell().bold(true),
-                "Run without --pretty to view verbose JSON details".cell(),
-            ]);
-        }
-
-        simple_table(rows, None)
-    }
-}
-
-#[derive(Serialize)]
-pub struct RawPsbt {
-    pub raw_tx: String,
-}
-
-impl RawPsbt {
-    pub fn new(tx: &Transaction) -> Self {
-        Self {
-            raw_tx: serialize_hex(tx),
-        }
-    }
-}
-
-impl FormatOutput for RawPsbt {
-    fn to_table(&self) -> Result<String, Error> {
-        let rows = vec![vec![
-            "Raw Transaction".cell().bold(true),
-            self.raw_tx.clone().cell(),
-        ]];
-        simple_table(rows, None)
-    }
-}
-
-#[derive(Serialize)]
-pub struct KeychainPair<T> {
-    pub external: T,
-    pub internal: T,
-}
-
-// Table formatting for string pairs (used by PublicDescriptor)
-impl FormatOutput for KeychainPair<String> {
-    fn to_table(&self) -> Result<String, Error> {
-        let rows = vec![
-            vec!["External".cell().bold(true), self.external.clone().cell()],
-            vec!["Internal".cell().bold(true), self.internal.clone().cell()],
-        ];
-        simple_table(rows, None)
-    }
-}
-
-// Table formatting for JSON value pairs (used by Policies)
-impl FormatOutput for KeychainPair<serde_json::Value> {
-    fn to_table(&self) -> Result<String, Error> {
-        let ext_str = serde_json::to_string_pretty(&self.external)
-            .map_err(|e| Error::Generic(e.to_string()))?;
-        let int_str = serde_json::to_string_pretty(&self.internal)
-            .map_err(|e| Error::Generic(e.to_string()))?;
-
-        let rows = vec![
-            vec!["External".cell().bold(true), ext_str.cell()],
-            vec!["Internal".cell().bold(true), int_str.cell()],
-        ];
-        simple_table(rows, None)
-    }
-}
-#[derive(Serialize)]
-pub struct KeyResult {
-    pub xprv: String,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub xpub: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mnemonic: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub fingerprint: Option<String>,
-}
-
-impl FormatOutput for KeyResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let mut rows: Vec<Vec<CellStruct>> = vec![];
-
-        if let Some(mnemonic) = &self.mnemonic {
-            rows.push(vec!["Mnemonic".cell().bold(true), mnemonic.clone().cell()]);
-        }
-        if let Some(xpub) = &self.xpub {
-            rows.push(vec!["Xpub".cell().bold(true), xpub.clone().cell()]);
-        }
-
-        rows.push(vec!["Xprv".cell().bold(true), self.xprv.clone().cell()]);
-
-        if let Some(fingerprint) = &self.fingerprint {
-            rows.push(vec![
-                "Fingerprint".cell().bold(true),
-                fingerprint.clone().cell(),
-            ]);
-        }
-
-        simple_table(rows, None)
-    }
-}
-
-#[derive(Serialize)]
-#[serde(transparent)]
-pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
-
-impl FormatOutput for WalletsListResult {
-    fn to_table(&self) -> Result<String, Error> {
-        if self.0.is_empty() {
-            return Ok("No wallets configured yet.".to_string());
-        }
-
-        let rows = self.0.iter().map(|(name, inner)| {
-            let desc: String = inner.ext_descriptor.chars().take(30).collect();
-            let desc_display = if inner.ext_descriptor.len() > 30 {
-                format!("{}...", desc)
-            } else {
-                desc
-            };
-
-            vec![
-                name.clone().cell(),
-                inner.network.clone().cell(),
-                desc_display.cell(),
-            ]
-        });
-
-        simple_table(
-            rows,
-            Some(vec![
-                "Wallet Name".cell().bold(true),
-                "Network".cell().bold(true),
-                "External Descriptor".cell().bold(true),
-            ]),
-        )
-    }
-}
-
-#[derive(Serialize)]
-pub struct DescriptorResult {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub descriptor: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub multipath_descriptor: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub public_descriptors: Option<KeychainPair<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub private_descriptors: Option<KeychainPair<String>>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub mnemonic: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub fingerprint: Option<String>,
-}
-
-impl FormatOutput for DescriptorResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let mut rows: Vec<Vec<CellStruct>> = vec![];
-
-        if let Some(desc) = &self.descriptor {
-            rows.push(vec!["Descriptor".cell().bold(true), desc.clone().cell()]);
-        }
-        if let Some(desc) = &self.multipath_descriptor {
-            rows.push(vec![
-                "Multipath Descriptor".cell().bold(true),
-                desc.clone().cell(),
-            ]);
-        }
-        if let Some(pub_desc) = &self.public_descriptors {
-            rows.push(vec![
-                "External Public".cell().bold(true),
-                pub_desc.external.clone().cell(),
-            ]);
-            rows.push(vec![
-                "Internal Public".cell().bold(true),
-                pub_desc.internal.clone().cell(),
-            ]);
-        }
-        if let Some(priv_desc) = &self.private_descriptors {
-            rows.push(vec![
-                "External Private".cell().bold(true),
-                priv_desc.external.clone().cell(),
-            ]);
-            rows.push(vec![
-                "Internal Private".cell().bold(true),
-                priv_desc.internal.clone().cell(),
-            ]);
-        }
-        if let Some(mnemonic) = &self.mnemonic {
-            rows.push(vec!["Mnemonic".cell().bold(true), mnemonic.clone().cell()]);
-        }
-
-        if let Some(fp) = &self.fingerprint {
-            rows.push(vec!["Fingerprint".cell().bold(true), fp.clone().cell()]);
-        }
-
-        simple_table(rows, None)
-    }
-}
-
-#[derive(Serialize, Debug, Default)]
-pub struct MessageResult {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub proof: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub valid: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub proven_amount: Option<u64>,
-}
-
-impl FormatOutput for MessageResult {
-    fn to_table(&self) -> Result<String, Error> {
-        let mut rows = vec![];
-
-        if let Some(proof) = &self.proof {
-            rows.push(vec!["Proof".cell().bold(true), proof.cell()]);
-        }
-        if let Some(valid) = self.valid {
-            rows.push(vec!["Is Valid".cell().bold(true), valid.to_string().cell()]);
-        }
-        if let Some(amount) = self.proven_amount {
-            rows.push(vec![
-                "Proven Amount (sats)".cell().bold(true),
-                amount.cell(),
-            ]);
-        }
-
-        let title = vec!["Property".cell().bold(true), "Value".cell().bold(true)];
-
-        simple_table(rows, Some(title))
-    }
-}
-
-#[derive(Serialize, Debug)]
-pub struct StatusResult {
-    pub message: String,
-}
-
-impl StatusResult {
-    pub fn new(msg: &str) -> Self {
-        Self {
-            message: msg.to_string(),
-        }
-    }
-}
-
-impl FormatOutput for StatusResult {
-    fn to_table(&self) -> Result<String, Error> {
-        Ok(self.message.clone())
-    }
-}
-
-#[derive(Serialize, Debug)]
-pub struct TransactionResult {
-    pub txid: String,
-}
-
-impl FormatOutput for TransactionResult {
-    fn to_table(&self) -> Result<String, Error> {
-        simple_table(
-            vec![vec!["TXID".cell().bold(true), self.txid.clone().cell()]],
-            None,
-        )
-    }
-}
-
-#[derive(Serialize, Debug)]
-pub struct ConfigResult {
-    pub wallet_name: String,
-    pub path: String,
-    pub message: String,
-}
-
-impl FormatOutput for ConfigResult {
-    fn to_table(&self) -> Result<String, Error> {
-        Ok(format!("{} saved to {}", self.message, self.path))
-    }
-}
diff --git a/src/utils/types.rs b/src/utils/types.rs
new file mode 100644 (file)
index 0000000..f3c5ddc
--- /dev/null
@@ -0,0 +1,272 @@
+use std::collections::HashMap;
+
+use crate::config::WalletConfigInner;
+use crate::utils::output::FormatOutput;
+use crate::utils::shorten;
+use bdk_wallet::Balance;
+use bdk_wallet::bitcoin::{
+    Address, Network, Psbt, Transaction, base64::Engine, consensus::encode::serialize_hex,
+};
+use bdk_wallet::{AddressInfo, LocalOutput, chain::ChainPosition};
+use serde::Serialize;
+use serde_json::json;
+
+/// Represent address result
+#[derive(Serialize)]
+pub struct AddressResult {
+    pub address: String,
+    pub index: u32,
+}
+
+impl From<AddressInfo> for AddressResult {
+    fn from(info: AddressInfo) -> Self {
+        Self {
+            address: info.address.to_string(),
+            index: info.index,
+        }
+    }
+}
+
+impl FormatOutput for AddressResult {}
+
+/// Represents the data for a single transaction
+#[derive(Serialize)]
+pub struct TransactionDetails {
+    pub txid: String,
+    pub is_coinbase: bool,
+    pub wtxid: String,
+    pub version: serde_json::Value,
+    pub is_rbf: bool,
+    pub inputs: serde_json::Value,
+    pub outputs: serde_json::Value,
+    #[serde(skip)]
+    pub version_display: String,
+    #[serde(skip)]
+    pub input_count: usize,
+    #[serde(skip)]
+    pub output_count: usize,
+    #[serde(skip)]
+    pub total_value: u64,
+}
+
+/// A wrapper type for a list of transactions.
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct TransactionListResult(pub Vec<TransactionDetails>);
+
+impl FormatOutput for TransactionListResult {}
+
+/// single UTXO
+#[derive(Serialize)]
+pub struct UnspentDetails {
+    pub outpoint: String,
+    pub txout: serde_json::Value,
+    pub keychain: String,
+    pub is_spent: bool,
+    pub derivation_index: u32,
+    pub chain_position: serde_json::Value,
+
+}
+
+impl UnspentDetails {
+    pub fn from_local_output(utxo: &LocalOutput, network: Network) -> Self {
+        let height = utxo.chain_position.confirmation_height_upper_bound();
+        let height_display = height
+            .map(|h| h.to_string())
+            .unwrap_or_else(|| "Pending".to_string());
+
+        let (_, block_hash_display) = match &utxo.chain_position {
+            ChainPosition::Confirmed { anchor, .. } => {
+                let hash = anchor.block_id.hash.to_string();
+                (Some(hash.clone()), shorten(&hash, 8, 8))
+            }
+            ChainPosition::Unconfirmed { .. } => (None, "Unconfirmed".to_string()),
+        };
+
+        let address = Address::from_script(&utxo.txout.script_pubkey, network)
+            .map(|a| a.to_string())
+            .unwrap_or_else(|_| "Unknown Script".to_string());
+
+        let outpoint_str = utxo.outpoint.to_string();
+
+        Self {
+            outpoint: outpoint_str.clone(),
+            txout: serde_json::to_value(&utxo.txout).unwrap_or(json!({})),
+            keychain: format!("{:?}", utxo.keychain),
+            is_spent: utxo.is_spent,
+            derivation_index: utxo.derivation_index,
+            chain_position: serde_json::to_value(utxo.chain_position).unwrap_or(json!({})),
+        }
+    }
+}
+
+/// Wrapper for the list of UTXOs
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct UnspentListResult(pub Vec<UnspentDetails>);
+
+impl FormatOutput for UnspentListResult {}
+
+#[derive(Serialize)]
+pub struct PsbtResult {
+    pub psbt: String,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub is_finalized: Option<bool>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub details: Option<serde_json::Value>,
+}
+
+impl PsbtResult {
+    pub fn new(psbt: &Psbt, verbose: bool, finalized: Option<bool>) -> Self {
+        Self {
+            psbt: bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD.encode(psbt.serialize()),
+            is_finalized: finalized,
+            details: if verbose {
+                Some(serde_json::to_value(psbt).unwrap_or_default())
+            } else {
+                None
+            },
+        }
+    }
+}
+
+impl FormatOutput for PsbtResult {}
+
+#[derive(Serialize)]
+pub struct RawPsbt {
+    pub raw_tx: String,
+}
+
+impl RawPsbt {
+    pub fn new(tx: &Transaction) -> Self {
+        Self {
+            raw_tx: serialize_hex(tx),
+        }
+    }
+}
+
+impl FormatOutput for RawPsbt {}
+
+#[derive(Serialize)]
+pub struct KeychainPair<T> {
+    pub external: T,
+    pub internal: T,
+}
+
+impl FormatOutput for KeychainPair<String> {}
+
+// Table formatting for JSON value pairs (used by Policies)
+impl FormatOutput for KeychainPair<serde_json::Value> {}
+
+#[derive(Serialize, Debug, Default)]
+pub struct MessageResult {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub proof: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub valid: Option<bool>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub proven_amount: Option<u64>,
+}
+
+impl FormatOutput for MessageResult {}
+
+#[derive(Serialize, Debug)]
+pub struct StatusResult {
+    pub message: String,
+}
+
+impl StatusResult {
+    pub fn new(msg: &str) -> Self {
+        Self {
+            message: msg.to_string(),
+        }
+    }
+}
+
+impl FormatOutput for StatusResult {}
+
+#[derive(Serialize, Debug)]
+pub struct TransactionResult {
+    pub txid: String,
+}
+
+impl FormatOutput for TransactionResult {}
+
+
+
+/// Return type definition
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
+
+impl FormatOutput for WalletsListResult {}
+
+
+/// return type
+#[derive(Serialize)]
+pub struct DescriptorResult {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub descriptor: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub multipath_descriptor: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub public_descriptors: Option<KeychainPair<String>>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub private_descriptors: Option<KeychainPair<String>>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub mnemonic: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub fingerprint: Option<String>,
+}
+
+impl FormatOutput for DescriptorResult {}
+
+#[derive(Serialize)]
+pub struct KeyResult {
+    pub xprv: String,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub xpub: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub mnemonic: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub fingerprint: Option<String>,
+}
+
+impl FormatOutput for KeyResult {}
+
+
+/// Balance representation
+#[derive(Serialize)]
+pub struct BalanceResult {
+    pub total: u64,
+    pub trusted_pending: u64,
+    pub untrusted_pending: u64,
+    pub immature: u64,
+    pub confirmed: u64,
+}
+
+impl From<Balance> for BalanceResult {
+    fn from(b: Balance) -> Self {
+        Self {
+            total: b.total().to_sat(),
+            confirmed: b.confirmed.to_sat(),
+            trusted_pending: b.trusted_pending.to_sat(),
+            untrusted_pending: b.untrusted_pending.to_sat(),
+            immature: b.immature.to_sat(),
+        }
+    }
+}
+
+impl FormatOutput for BalanceResult {}