Skip to main content

gateway_cli/
general_commands.rs

1use std::time::{Duration, UNIX_EPOCH};
2
3use clap::Subcommand;
4use fedimint_connectors::error::ServerError;
5use fedimint_core::config::FederationId;
6use fedimint_core::fedimint_build_code_version_env;
7use fedimint_core::time::now;
8use fedimint_core::util::SafeUrl;
9use fedimint_eventlog::{EventKind, EventLogId};
10use fedimint_gateway_client::{
11    connect_federation, get_balances, get_info, get_invite_codes, get_mnemonic, leave_federation,
12    payment_log, payment_summary, stop,
13};
14use fedimint_gateway_common::{
15    ConnectFedPayload, LeaveFedPayload, PaymentLogPayload, PaymentSummaryPayload,
16};
17use fedimint_ln_common::client::GatewayApi;
18
19use crate::{CliOutput, CliOutputResult};
20
21/// General federation management commands including info, connecting, or
22/// leaving a federation.
23#[derive(Subcommand)]
24pub enum GeneralCommands {
25    /// Display the version hash of the CLI.
26    VersionHash,
27    /// Display high-level information about the gateway.
28    Info,
29    /// Get the total on-chain, lightning, and eCash balances of the gateway.
30    GetBalances,
31    /// Register the gateway with a federation.
32    ConnectFed {
33        /// Invite code to connect to the federation
34        invite_code: String,
35        /// Activate usage of Tor (or not) as the connector for the federation
36        /// client
37        #[cfg(feature = "tor")]
38        use_tor: Option<bool>,
39        /// Indicates if the client should be recovered from a mnemonic
40        #[clap(long)]
41        recover: Option<bool>,
42    },
43    /// Leave a federation.
44    LeaveFed {
45        #[clap(long)]
46        federation_id: FederationId,
47    },
48    /// Prints the seed phrase for the gateway
49    Seed,
50    /// Safely stop the gateway
51    Stop,
52    /// List the fedimint transactions that the gateway has processed
53    PaymentLog {
54        #[clap(long)]
55        end_position: Option<EventLogId>,
56
57        #[clap(long, default_value_t = 25)]
58        pagination_size: usize,
59
60        #[clap(long)]
61        federation_id: FederationId,
62
63        #[clap(long)]
64        event_kinds: Vec<EventKind>,
65    },
66    /// Create a bcrypt hash of a password, for use in gateway deployment
67    CreatePasswordHash {
68        password: String,
69
70        /// The bcrypt cost factor to use when hashing the password
71        #[clap(long)]
72        cost: Option<u32>,
73    },
74    /// List a payment summary for the last day
75    PaymentSummary {
76        #[clap(long)]
77        start: Option<u64>,
78
79        #[clap(long)]
80        end: Option<u64>,
81    },
82    /// List all invite codes of each federation the gateway has joined
83    InviteCodes,
84}
85
86impl GeneralCommands {
87    #[allow(clippy::too_many_lines)]
88    pub async fn handle(self, client: &GatewayApi, base_url: &SafeUrl) -> CliOutputResult {
89        match self {
90            Self::VersionHash => {
91                // Keep version-hash as raw string output for backward compatibility
92                println!("{}", fedimint_build_code_version_env!());
93                Ok(CliOutput::Empty)
94            }
95            Self::Info => {
96                let response = get_info(client, base_url).await?;
97                Ok(CliOutput::Info(response))
98            }
99            Self::GetBalances => {
100                let response = get_balances(client, base_url).await?;
101                Ok(CliOutput::Balances(response))
102            }
103            Self::ConnectFed {
104                invite_code,
105                #[cfg(feature = "tor")]
106                use_tor,
107                recover,
108            } => {
109                let response = connect_federation(
110                    client,
111                    base_url,
112                    ConnectFedPayload {
113                        invite_code,
114                        #[cfg(feature = "tor")]
115                        use_tor,
116                        #[cfg(not(feature = "tor"))]
117                        use_tor: None,
118                        recover,
119                    },
120                )
121                .await?;
122
123                Ok(CliOutput::Federation(response))
124            }
125            Self::LeaveFed { federation_id } => {
126                let response =
127                    leave_federation(client, base_url, LeaveFedPayload { federation_id }).await?;
128                Ok(CliOutput::Federation(response))
129            }
130            Self::Seed => {
131                let response = get_mnemonic(client, base_url).await?;
132                Ok(CliOutput::Mnemonic(response))
133            }
134            Self::Stop => {
135                stop(client, base_url).await?;
136                Ok(CliOutput::Empty)
137            }
138            Self::PaymentLog {
139                end_position,
140                pagination_size,
141                federation_id,
142                event_kinds,
143            } => {
144                let payment_log = payment_log(
145                    client,
146                    base_url,
147                    PaymentLogPayload {
148                        end_position,
149                        pagination_size,
150                        federation_id,
151                        event_kinds,
152                    },
153                )
154                .await?;
155                Ok(CliOutput::PaymentLog(payment_log))
156            }
157            Self::CreatePasswordHash { password, cost } => {
158                let hash = bcrypt::hash(password, cost.unwrap_or(bcrypt::DEFAULT_COST))
159                    .expect("Unable to create bcrypt hash");
160                Ok(CliOutput::PasswordHash(hash))
161            }
162            Self::PaymentSummary { start, end } => {
163                let now = now();
164                let now_millis: u64 = now
165                    .duration_since(UNIX_EPOCH)
166                    .expect("Before unix epoch")
167                    .as_millis()
168                    .try_into()
169                    .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
170                let one_day_ago = now
171                    .checked_sub(Duration::from_hours(24))
172                    .expect("Before unix epoch");
173                let one_day_ago_millis: u64 = one_day_ago
174                    .duration_since(UNIX_EPOCH)
175                    .expect("Before unix epoch")
176                    .as_millis()
177                    .try_into()
178                    .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
179                let end_millis = end.unwrap_or(now_millis);
180                let start_millis = start.unwrap_or(one_day_ago_millis);
181                let payment_summary = payment_summary(
182                    client,
183                    base_url,
184                    PaymentSummaryPayload {
185                        start_millis,
186                        end_millis,
187                    },
188                )
189                .await?;
190                Ok(CliOutput::PaymentSummary(payment_summary))
191            }
192            Self::InviteCodes => {
193                let invite_codes = get_invite_codes(client, base_url).await?;
194                Ok(CliOutput::InviteCodes(invite_codes))
195            }
196        }
197    }
198}