fedimint_wallet_client/
cli.rs

1use std::str::FromStr as _;
2use std::{ffi, iter};
3
4use anyhow::bail;
5use bitcoin::address::NetworkUnchecked;
6use clap::Parser;
7use fedimint_core::core::OperationId;
8use serde::Serialize;
9
10use super::WalletClientModule;
11use crate::api::WalletFederationApi;
12use crate::client_db::TweakIdx;
13
14#[derive(Parser, Serialize)]
15enum Opts {
16    /// Await a deposit on a given deposit address
17    AwaitDeposit {
18        addr: Option<String>,
19        #[arg(long)]
20        operation_id: Option<OperationId>,
21        #[arg(long)]
22        tweak_idx: Option<TweakIdx>,
23        /// Await more than just one deposit
24        #[arg(long, default_value = "1")]
25        num: usize,
26    },
27    GetConsensusBlockCount,
28    /// Returns the Bitcoin RPC kind
29    GetBitcoinRpcKind {
30        peer_id: u16,
31    },
32    /// Returns the Bitcoin RPC kind and URL, if authenticated
33    GetBitcoinRpcConfig,
34
35    NewDepositAddress,
36    /// Trigger wallet address check (in the background)
37    RecheckDepositAddress {
38        addr: Option<bitcoin::Address<NetworkUnchecked>>,
39        #[arg(long)]
40        operation_id: Option<OperationId>,
41        #[arg(long)]
42        tweak_idx: Option<TweakIdx>,
43    },
44}
45
46pub(crate) async fn handle_cli_command(
47    module: &WalletClientModule,
48    args: &[ffi::OsString],
49) -> anyhow::Result<serde_json::Value> {
50    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("wallet")).chain(args.iter()));
51
52    let res = match opts {
53        Opts::AwaitDeposit {
54            operation_id,
55            num,
56            addr,
57            tweak_idx,
58        } => {
59            if u32::from(addr.is_some())
60                + u32::from(operation_id.is_some())
61                + u32::from(tweak_idx.is_some())
62                != 1
63            {
64                bail!("One and only one of the selector arguments must be set")
65            }
66            if let Some(tweak_idx) = tweak_idx {
67                module.await_num_deposits(tweak_idx, num).await?;
68            } else if let Some(operation_id) = operation_id {
69                module
70                    .await_num_deposits_by_operation_id(operation_id, num)
71                    .await?;
72            } else if let Some(addr) = addr {
73                if addr.len() == 64 {
74                    eprintln!(
75                        "Interpreting addr as an operation_id for backward compatibility. Use `--operation-id` from now on."
76                    );
77                    let operation_id = OperationId::from_str(&addr)?;
78                    module
79                        .await_num_deposits_by_operation_id(operation_id, num)
80                        .await?;
81                } else {
82                    let addr = bitcoin::Address::from_str(&addr)?;
83                    module.await_num_deposits_by_address(addr, num).await?;
84                }
85            } else {
86                unreachable!()
87            }
88            serde_json::Value::Bool(true)
89        }
90        Opts::GetBitcoinRpcKind { peer_id } => {
91            let kind = module
92                .module_api
93                .fetch_bitcoin_rpc_kind(peer_id.into())
94                .await?;
95
96            serde_json::to_value(kind).expect("JSON serialization failed")
97        }
98        Opts::GetBitcoinRpcConfig => {
99            let auth = module
100                .admin_auth
101                .clone()
102                .ok_or(anyhow::anyhow!("Admin auth not set"))?;
103
104            serde_json::to_value(module.module_api.fetch_bitcoin_rpc_config(auth).await?)
105                .expect("JSON serialization failed")
106        }
107        Opts::GetConsensusBlockCount => {
108            serde_json::to_value(module.module_api.fetch_consensus_block_count().await?)
109                .expect("JSON serialization failed")
110        }
111        Opts::RecheckDepositAddress {
112            addr,
113            operation_id,
114            tweak_idx,
115        } => {
116            if u32::from(addr.is_some())
117                + u32::from(operation_id.is_some())
118                + u32::from(tweak_idx.is_some())
119                != 1
120            {
121                bail!("One and only one of the selector arguments must be set")
122            }
123            if let Some(tweak_idx) = tweak_idx {
124                module.recheck_pegin_address(tweak_idx).await?;
125            } else if let Some(operation_id) = operation_id {
126                module.recheck_pegin_address_by_op_id(operation_id).await?;
127            } else if let Some(addr) = addr {
128                module.recheck_pegin_address_by_address(addr).await?;
129            } else {
130                unreachable!()
131            }
132            serde_json::Value::Bool(true)
133        }
134        Opts::NewDepositAddress => {
135            let (operation_id, address, tweak_idx) =
136                module.allocate_deposit_address_expert_only(()).await?;
137            serde_json::json! {
138                {
139                    "address": address,
140                    "operation_id": operation_id,
141                    "tweak_idx": tweak_idx.0
142                }
143            }
144        }
145    };
146
147    Ok(res)
148}