Skip to main content

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}