fedimint_cli/cli.rs
1use std::path::PathBuf;
2
3use clap::{Args, Parser, Subcommand};
4use fedimint_core::config::FederationId;
5use fedimint_core::core::OperationId;
6use fedimint_core::invite_code::InviteCode;
7use fedimint_core::util::SafeUrl;
8use fedimint_core::{Amount, PeerId, TieredMulti};
9use fedimint_eventlog::EventLogId;
10use fedimint_mint_client::{OOBNotes, SpendableNote};
11use serde::{Deserialize, Serialize};
12
13use crate::client::{ClientCmd, ModuleSelector};
14#[cfg(feature = "tor")]
15use crate::envs::FM_USE_TOR_ENV;
16use crate::envs::{
17 FM_API_SECRET_ENV, FM_CLIENT_DIR_ENV, FM_DB_BACKEND_ENV, FM_FEDERATION_SECRET_HEX_ENV,
18 FM_IROH_ENABLE_DHT_ENV, FM_IROH_ENABLE_NEXT_ENV, FM_OUR_ID_ENV, FM_PASSWORD_ENV,
19};
20use crate::utils::parse_peer_id;
21
22#[derive(Debug, Clone, Copy, clap::ValueEnum)]
23pub(crate) enum DatabaseBackend {
24 /// Use RocksDB database backend
25 #[value(name = "rocksdb")]
26 RocksDb,
27 /// Use CursedRedb database backend (hybrid memory/redb)
28 #[value(name = "cursed-redb")]
29 CursedRedb,
30}
31
32#[derive(Parser, Clone)]
33#[command(version)]
34pub(crate) struct Opts {
35 /// The working directory of the client containing the config and db
36 #[arg(long = "data-dir", env = FM_CLIENT_DIR_ENV)]
37 pub data_dir: Option<PathBuf>,
38
39 /// Peer id of the guardian
40 #[arg(env = FM_OUR_ID_ENV, long, value_parser = parse_peer_id)]
41 pub our_id: Option<PeerId>,
42
43 /// Guardian password for authentication
44 #[arg(long, env = FM_PASSWORD_ENV)]
45 pub password: Option<String>,
46
47 /// Federation secret as consensus-encoded hex.
48 #[arg(long, env = FM_FEDERATION_SECRET_HEX_ENV)]
49 pub federation_secret_hex: Option<String>,
50
51 #[cfg(feature = "tor")]
52 /// Activate usage of Tor as the Connector when building the Client
53 #[arg(long, env = FM_USE_TOR_ENV)]
54 pub use_tor: bool,
55
56 // Enable using DHT name resolution in Iroh
57 #[arg(long, env = FM_IROH_ENABLE_DHT_ENV)]
58 pub iroh_enable_dht: Option<bool>,
59
60 // Enable using (in parallel) unstable/next Iroh stack
61 #[arg(long, env = FM_IROH_ENABLE_NEXT_ENV)]
62 pub iroh_enable_next: Option<bool>,
63
64 /// Database backend to use.
65 #[arg(long, env = FM_DB_BACKEND_ENV, value_enum, default_value = "rocksdb")]
66 pub db_backend: DatabaseBackend,
67
68 /// Activate more verbose logging, for full control use the RUST_LOG env
69 /// variable
70 #[arg(short = 'v', long)]
71 pub verbose: bool,
72
73 #[clap(subcommand)]
74 pub command: Command,
75}
76
77#[derive(Subcommand, Clone)]
78pub(crate) enum Command {
79 /// Print the latest Git commit hash this bin. was built with.
80 VersionHash,
81
82 #[clap(flatten)]
83 Client(ClientCmd),
84
85 #[clap(subcommand)]
86 Admin(AdminCmd),
87
88 #[clap(subcommand)]
89 Dev(DevCmd),
90
91 /// Config enabling client to establish websocket connection to federation
92 InviteCode {
93 peer: PeerId,
94 },
95
96 /// Join a federation using its InviteCode
97 #[clap(alias = "join-federation")]
98 Join {
99 invite_code: String,
100 },
101
102 Completion {
103 shell: clap_complete::Shell,
104 },
105}
106
107#[allow(clippy::large_enum_variant)]
108#[derive(Debug, Clone, Subcommand)]
109pub(crate) enum AdminCmd {
110 /// Store admin credentials (peer_id and password) in the client database.
111 ///
112 /// This allows subsequent admin commands to be run without specifying
113 /// `--our-id` and `--password` each time.
114 ///
115 /// The command will verify the credentials by making an authenticated
116 /// API call before storing them.
117 Auth {
118 /// Guardian's peer ID
119 #[arg(long, env = FM_OUR_ID_ENV)]
120 peer_id: u16,
121 /// Guardian password for authentication
122 #[arg(long, env = FM_PASSWORD_ENV)]
123 password: String,
124 /// Skip interactive endpoint verification
125 #[arg(long)]
126 no_verify: bool,
127 /// Force overwrite existing stored credentials
128 #[arg(long)]
129 force: bool,
130 },
131
132 /// Show the status according to the `status` endpoint
133 Status,
134
135 /// Show an audit across all modules
136 Audit,
137
138 /// Download guardian config to back it up
139 GuardianConfigBackup,
140
141 Setup(SetupAdminArgs),
142 /// Sign and announce a new API endpoint. The previous one will be
143 /// invalidated
144 SignApiAnnouncement {
145 /// New API URL to announce
146 api_url: SafeUrl,
147 /// Provide the API url for the guardian directly in case the old one
148 /// isn't reachable anymore
149 #[clap(long)]
150 override_url: Option<SafeUrl>,
151 },
152 /// Sign guardian metadata
153 SignGuardianMetadata {
154 /// API URLs (can be specified multiple times or comma-separated)
155 #[clap(long, value_delimiter = ',')]
156 api_urls: Vec<SafeUrl>,
157 /// Pkarr ID (z32 format)
158 #[clap(long)]
159 pkarr_id: String,
160 },
161 /// Stop fedimintd after the specified session to do a coordinated upgrade
162 Shutdown {
163 /// Session index to stop after
164 session_idx: u64,
165 },
166 /// Show statistics about client backups stored by the federation
167 BackupStatistics,
168 /// Change guardian password, will shut down fedimintd and require manual
169 /// restart
170 ChangePassword {
171 /// New password to set
172 new_password: String,
173 },
174}
175
176#[derive(Debug, Clone, Args)]
177pub(crate) struct SetupAdminArgs {
178 pub endpoint: SafeUrl,
179
180 #[clap(subcommand)]
181 pub subcommand: SetupAdminCmd,
182}
183
184#[derive(Debug, Clone, Subcommand)]
185pub(crate) enum SetupAdminCmd {
186 Status,
187 SetLocalParams {
188 name: String,
189 #[clap(long)]
190 federation_name: Option<String>,
191 #[clap(long)]
192 federation_size: Option<u32>,
193 },
194 AddPeer {
195 info: String,
196 },
197 StartDkg,
198}
199
200#[derive(Debug, Clone, Subcommand)]
201pub(crate) enum DecodeType {
202 /// Decode an invite code string into a JSON representation
203 InviteCode { invite_code: InviteCode },
204 /// Decode a string of ecash notes into a JSON representation
205 #[group(required = true, multiple = false)]
206 Notes {
207 /// Base64 e-cash notes to be decoded
208 notes: Option<OOBNotes>,
209 /// File containing base64 e-cash notes to be decoded
210 #[arg(long)]
211 file: Option<PathBuf>,
212 },
213 /// Decode a transaction hex string and print it to stdout
214 Transaction { hex_string: String },
215 /// Decode a setup code (as shared during a federation setup ceremony)
216 /// string into a JSON representation
217 SetupCode { setup_code: String },
218}
219
220#[derive(Debug, Clone, Deserialize, Serialize)]
221pub(crate) struct OOBNotesJson {
222 pub federation_id_prefix: String,
223 pub notes: TieredMulti<SpendableNote>,
224}
225
226#[derive(Debug, Clone, Subcommand)]
227pub(crate) enum EncodeType {
228 /// Encode connection info from its constituent parts
229 InviteCode {
230 #[clap(long)]
231 url: SafeUrl,
232 #[clap(long = "federation_id")]
233 federation_id: FederationId,
234 #[clap(long = "peer")]
235 peer: PeerId,
236 #[arg(env = FM_API_SECRET_ENV)]
237 api_secret: Option<String>,
238 },
239
240 /// Encode a JSON string of notes to an ecash string
241 Notes { notes_json: String },
242}
243
244#[derive(Debug, Clone, Subcommand)]
245pub(crate) enum DevCmd {
246 /// Send direct method call to the API. If you specify --peer-id, it will
247 /// just ask one server, otherwise it will try to get consensus from all
248 /// servers.
249 #[command(after_long_help = r#"
250Examples:
251
252 fedimint-cli dev api --peer-id 0 config '"fed114znk7uk7ppugdjuytr8venqf2tkywd65cqvg3u93um64tu5cw4yr0n3fvn7qmwvm4g48cpndgnm4gqq4waen5te0xyerwt3s9cczuvf6xyurzde597s7crdvsk2vmyarjw9gwyqjdzj"'
253 "#)]
254 Api {
255 /// JSON-RPC method to call
256 method: String,
257 /// JSON-RPC parameters for the request
258 ///
259 /// Note: single jsonrpc argument params string, which might require
260 /// double-quotes (see example above).
261 #[clap(default_value = "null")]
262 params: String,
263 /// Which server to send request to
264 #[clap(long = "peer-id")]
265 peer_id: Option<u16>,
266
267 /// Module selector (either module id or module kind)
268 #[clap(long = "module")]
269 module: Option<ModuleSelector>,
270
271 /// Guardian password in case authenticated API endpoints are being
272 /// called. Only use together with --peer-id.
273 #[clap(long, requires = "peer_id")]
274 password: Option<String>,
275 },
276
277 ApiAnnouncements,
278
279 GuardianMetadata,
280
281 /// Advance the note_idx
282 AdvanceNoteIdx {
283 #[clap(long, default_value = "1")]
284 count: usize,
285
286 #[clap(long)]
287 amount: Amount,
288 },
289
290 /// Wait for the fed to reach a consensus block count
291 WaitBlockCount {
292 count: u64,
293 },
294
295 /// Just start the `Client` and wait
296 Wait {
297 /// Limit the wait time
298 seconds: Option<f32>,
299 },
300
301 /// Wait for all state machines to complete
302 WaitComplete,
303
304 /// Decode invite code or ecash notes string into a JSON representation
305 Decode {
306 #[clap(subcommand)]
307 decode_type: DecodeType,
308 },
309
310 /// Encode an invite code or ecash notes into binary
311 Encode {
312 #[clap(subcommand)]
313 encode_type: EncodeType,
314 },
315
316 /// Gets the current fedimint AlephBFT block count
317 SessionCount,
318
319 /// Returns the client config
320 Config,
321
322 ConfigDecrypt {
323 /// Encrypted config file
324 #[arg(long = "in-file")]
325 in_file: PathBuf,
326 /// Plaintext config file output
327 #[arg(long = "out-file")]
328 out_file: PathBuf,
329 /// Encryption salt file, otherwise defaults to the salt file from the
330 /// `in_file` directory
331 #[arg(long = "salt-file")]
332 salt_file: Option<PathBuf>,
333 /// The password that encrypts the configs
334 #[arg(env = FM_PASSWORD_ENV)]
335 password: String,
336 },
337
338 ConfigEncrypt {
339 /// Plaintext config file
340 #[arg(long = "in-file")]
341 in_file: PathBuf,
342 /// Encrypted config file output
343 #[arg(long = "out-file")]
344 out_file: PathBuf,
345 /// Encryption salt file, otherwise defaults to the salt file from the
346 /// `out_file` directory
347 #[arg(long = "salt-file")]
348 salt_file: Option<PathBuf>,
349 /// The password that encrypts the configs
350 #[arg(env = FM_PASSWORD_ENV)]
351 password: String,
352 },
353
354 /// Lists active and inactive state machine states of the operation
355 /// chronologically
356 ListOperationStates {
357 operation_id: OperationId,
358 },
359 /// Returns the federation's meta fields. If they are set correctly via the
360 /// meta module these are returned, otherwise the legacy mechanism
361 /// (config+override file) is used.
362 MetaFields,
363 /// Gets the tagged fedimintd version for a peer
364 PeerVersion {
365 #[clap(long)]
366 peer_id: u16,
367 },
368 /// Dump Client's Event Log
369 ShowEventLog {
370 #[arg(long)]
371 pos: Option<EventLogId>,
372 #[arg(long, default_value = "10")]
373 limit: u64,
374 },
375 /// Dump Client's Trimable Event Log
376 ShowEventLogTrimable {
377 #[arg(long)]
378 pos: Option<EventLogId>,
379 #[arg(long, default_value = "10")]
380 limit: u64,
381 },
382 /// Test the built-in event handling and tracking by printing events to
383 /// console
384 TestEventLogHandling,
385 /// Manually submit a fedimint transaction to guardians
386 ///
387 /// This can be useful to check why a transaction may have been rejected
388 /// when debugging client issues.
389 SubmitTransaction {
390 /// Hex-encoded fedimint transaction
391 transaction: String,
392 },
393 /// Show the chain ID (bitcoin block hash at height 1) cached in the client
394 /// database
395 ChainId,
396 /// Trigger a panic to verify backtrace handling
397 Panic,
398 /// Visualize client internals for debugging
399 Visualize {
400 #[clap(subcommand)]
401 visualize_type: VisualizeCmd,
402 },
403}
404
405#[derive(Debug, Clone, Subcommand)]
406pub(crate) enum VisualizeCmd {
407 /// Show every e-cash note with creation/spending provenance
408 Notes {
409 #[arg(long)]
410 limit: Option<usize>,
411 },
412 /// Show transactions with inputs and outputs
413 Transactions {
414 /// Show a specific operation (by full ID)
415 operation_id: Option<OperationId>,
416 /// How many most-recent operations to show (ignored if operation_id is
417 /// given)
418 #[arg(long)]
419 limit: Option<usize>,
420 },
421 /// Show operations with their state machines
422 Operations {
423 /// Show a specific operation (by full ID)
424 operation_id: Option<OperationId>,
425 /// How many most-recent operations to show (ignored if operation_id is
426 /// given)
427 #[arg(long)]
428 limit: Option<usize>,
429 },
430}