Skip to main content

fedimint_mintv2_client/
cli.rs

1use std::{ffi, iter};
2
3use clap::{Parser, Subcommand};
4use fedimint_client_module::error::OperationLookupError;
5use fedimint_core::Amount;
6use fedimint_core::base32::{self, FEDIMINT_PREFIX, PrefixedDecodeError};
7use serde::Serialize;
8use serde_json::Value;
9
10use crate::{ECash, MintClientModule, ReceiveECashError, SendECashError, SpendableNote};
11
12#[derive(Parser, Serialize)]
13enum Opts {
14    /// Count the `ECash` notes in the client's database by denomination.
15    Count,
16    /// Send `ECash` for the given amount.
17    Send {
18        amount: Amount,
19        /// Embed the federation's invite code in the serialized ecash so a
20        /// recipient that hasn't joined the federation can do so from it.
21        #[clap(long)]
22        include_invite: bool,
23    },
24    /// Receive the `ECash` by reissuing the notes and return the amount.
25    Receive { ecash: String },
26    /// Developer and debugging commands.
27    Dev {
28        #[clap(subcommand)]
29        command: DevOpts,
30    },
31}
32
33#[derive(Subcommand, Serialize)]
34enum DevOpts {
35    /// Decode an `ECash` string into a JSON representation without receiving
36    /// it.
37    Decode { ecash: String },
38}
39
40pub(crate) async fn handle_cli_command(
41    mint: &MintClientModule,
42    args: &[ffi::OsString],
43) -> Result<Value, CliCommandError> {
44    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("mintv2")).chain(args.iter()));
45
46    match opts {
47        Opts::Count => Ok(json(mint.get_count_by_denomination().await)),
48        Opts::Send {
49            amount,
50            include_invite,
51        } => {
52            let (_, ecash) = mint.send(amount, Value::Null, include_invite).await?;
53            let ecash = base32::encode_prefixed(FEDIMINT_PREFIX, &ecash);
54
55            Ok(json(ecash))
56        }
57        Opts::Receive { ecash } => {
58            let ecash = base32::decode_prefixed(FEDIMINT_PREFIX, &ecash)?;
59
60            let operation_id = mint.receive(ecash, Value::Null).await?;
61
62            let state = mint
63                .await_final_receive_operation_state(operation_id)
64                .await?;
65
66            Ok(json(state))
67        }
68        Opts::Dev {
69            command: DevOpts::Decode { ecash },
70        } => {
71            let ecash: ECash = base32::decode_prefixed(FEDIMINT_PREFIX, &ecash)?;
72            let notes = ecash.notes();
73
74            Ok(serde_json::json!({
75                "federation_id": ecash.mint(),
76                "unit": ecash.unit(),
77                "amount": ecash.amount(),
78                "note_count": notes.len(),
79                "notes": notes.iter().map(SpendableNote::note).collect::<Vec<_>>(),
80                "invite": ecash.federation_invite(),
81            }))
82        }
83    }
84}
85
86fn json<T: Serialize>(value: T) -> Value {
87    serde_json::to_value(value).expect("JSON serialization failed")
88}
89
90/// A failure of a `mintv2` module command.
91#[derive(Debug, thiserror::Error)]
92pub(crate) enum CliCommandError {
93    /// The e-cash could not be sent.
94    #[error(transparent)]
95    Send(#[from] SendECashError),
96
97    /// The e-cash string is not valid.
98    #[error(transparent)]
99    Decode(#[from] PrefixedDecodeError),
100
101    /// The e-cash could not be received.
102    #[error(transparent)]
103    Receive(#[from] ReceiveECashError),
104
105    /// The receive operation could not be looked up.
106    #[error(transparent)]
107    OperationLookup(#[from] OperationLookupError),
108}