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