Skip to main content

fedimint_walletv2_client/
cli.rs

1use std::{ffi, iter};
2
3use bitcoin::Address;
4use bitcoin::address::NetworkUnchecked;
5use clap::{Parser, Subcommand};
6use fedimint_core::BitcoinAmountOrAll;
7use fedimint_eventlog::EventLogId;
8use serde::Serialize;
9use serde_json::Value;
10
11use crate::WalletClientModule;
12
13#[derive(Parser, Serialize)]
14enum Opts {
15    /// Subcommands for operator to retrieve information about the wallet state.
16    #[command(subcommand)]
17    Info(InfoOpts),
18    /// Fetch the current fee required to send an onchain payment.
19    SendFee,
20    /// Fetch the current fee required to claim an onchain deposit (peg-in).
21    ReceiveFee,
22    /// Send an onchain payment.
23    Send {
24        address: Address<NetworkUnchecked>,
25        /// Value to send, or "all" to sweep the entire balance.
26        value: BitcoinAmountOrAll,
27        #[arg(long)]
28        fee: Option<bitcoin::Amount>,
29    },
30    /// Return the next unused receive address.
31    ///
32    /// To wait for a payment to this address, read the current event log
33    /// position with `dev next-event-log-id` *before* running this, then pass
34    /// that position to `await-receive`.
35    Receive,
36    /// Block until the next payment is received, starting from the given event
37    /// log position. Returns the receive's final state and the event log
38    /// position to pass to the following `await-receive`.
39    AwaitReceive {
40        /// Event log position to start scanning from, as returned by
41        /// `dev next-event-log-id` or a prior `await-receive`.
42        position: EventLogId,
43    },
44}
45
46#[derive(Clone, Subcommand, Serialize)]
47enum InfoOpts {
48    /// Fetch the total value of bitcoin controlled by the federation.
49    TotalValue,
50    /// Fetch the consensus block count of the federation.
51    BlockCount,
52    /// Fetch the current consensus feerate.
53    Feerate,
54    /// Display the chain of pending bitcoin transactions.
55    PendingTxChain,
56    /// Display the chain of bitcoin transactions.
57    TxChain,
58}
59
60pub(crate) async fn handle_cli_command(
61    wallet: &WalletClientModule,
62    args: &[ffi::OsString],
63) -> anyhow::Result<Value> {
64    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("walletv2")).chain(args.iter()));
65
66    let value = match opts {
67        Opts::Info(subcommand) => match subcommand {
68            InfoOpts::TotalValue => json(wallet.total_value().await?),
69            InfoOpts::BlockCount => json(wallet.block_count().await?),
70            InfoOpts::Feerate => json(wallet.feerate().await?),
71            InfoOpts::PendingTxChain => json(wallet.pending_tx_chain().await?),
72            InfoOpts::TxChain => json(wallet.tx_chain().await?),
73        },
74        Opts::SendFee => json(wallet.send_fee().await?),
75        Opts::ReceiveFee => json(wallet.receive_fee().await?),
76        Opts::Send {
77            address,
78            value,
79            fee,
80        } => {
81            // Resolve the on-chain fee up front so the same value sizes the
82            // sweep and funds the send: the required feerate rises with each
83            // pending federation transaction, and a value computed against a
84            // stale fee would be rejected.
85            let fee = match fee {
86                Some(fee) => fee,
87                None => wallet.send_fee().await?,
88            };
89
90            let value = match value {
91                // The on-chain fee is only part of the cost of sending
92                // everything: funding the wallet output also incurs the
93                // federation's per-note fees.
94                BitcoinAmountOrAll::All => {
95                    let balance = wallet.client_ctx.get_balance_for_btc().await?;
96                    wallet.max_sendable_amount(balance, fee).await?
97                }
98                BitcoinAmountOrAll::Amount(value) => value,
99            };
100
101            json(
102                wallet
103                    .await_final_send_operation_state(
104                        wallet
105                            .send(address, value, Some(fee), serde_json::Value::Null)
106                            .await?,
107                    )
108                    .await?,
109            )
110        }
111        Opts::Receive => json(wallet.receive().await),
112        Opts::AwaitReceive { position } => json(wallet.await_receive(position).await?),
113    };
114
115    Ok(value)
116}
117
118fn json<T: Serialize>(value: T) -> Value {
119    serde_json::to_value(value).expect("JSON serialization failed")
120}