Skip to main content

fedimint_lnv2_client/
cli.rs

1use std::{ffi, iter};
2
3use clap::{Parser, Subcommand};
4use fedimint_api_client::api::FederationError;
5use fedimint_client_module::error::OperationLookupError;
6use fedimint_core::core::OperationId;
7use fedimint_core::util::SafeUrl;
8use fedimint_core::{Amount, PeerId};
9use lightning_invoice::Bolt11Invoice;
10use serde::Serialize;
11use serde_json::Value;
12
13use crate::api::LightningFederationApi;
14use crate::{
15    Bolt11InvoiceDescription, GenerateLnurlError, LightningClientModule, ListGatewaysError,
16    ReceiveError, SelectGatewayError, SendPaymentError,
17};
18
19#[derive(Parser, Serialize)]
20enum Opts {
21    /// Pay an invoice. For  testing  you can optionally specify a gateway to
22    /// route with, otherwise a gateway will be selected automatically.
23    Send {
24        invoice: Bolt11Invoice,
25        #[arg(long)]
26        gateway: Option<SafeUrl>,
27    },
28    /// Await the final state of the send operation.
29    AwaitSend { operation_id: OperationId },
30    /// Request an invoice. For testing you can optionally specify a gateway to
31    /// generate the invoice, otherwise a gateway will be selected
32    /// automatically.
33    Receive {
34        amount: Amount,
35        #[arg(long)]
36        gateway: Option<SafeUrl>,
37    },
38    /// Await the final state of the receive operation.
39    AwaitReceive { operation_id: OperationId },
40    /// Lnurl subcommands
41    #[command(subcommand)]
42    Lnurl(LnurlOpts),
43    /// Gateway subcommands
44    #[command(subcommand)]
45    Gateways(GatewaysOpts),
46}
47
48#[derive(Clone, Subcommand, Serialize)]
49enum LnurlOpts {
50    /// Generate a new lnurl.
51    Generate {
52        recurringd: SafeUrl,
53        #[arg(long)]
54        gateway: Option<SafeUrl>,
55    },
56}
57
58#[derive(Clone, Subcommand, Serialize)]
59enum GatewaysOpts {
60    /// Update the mapping from lightning node public keys to gateway api
61    /// endpoints maintained in the module database to optimise gateway
62    /// selection for a given invoice; this command is intended for testing.
63    Map,
64    /// Select an online vetted gateway; this command is intended for testing.
65    Select {
66        #[arg(long)]
67        invoice: Option<Bolt11Invoice>,
68    },
69    /// List all vetted gateways.
70    List {
71        #[arg(long)]
72        peer: Option<PeerId>,
73    },
74    /// Add a vetted gateway.
75    Add { gateway: SafeUrl },
76    /// Remove a vetted gateway.
77    Remove { gateway: SafeUrl },
78}
79
80pub(crate) async fn handle_cli_command(
81    lightning: &LightningClientModule,
82    args: &[ffi::OsString],
83) -> Result<serde_json::Value, CliCommandError> {
84    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("lnv2")).chain(args.iter()));
85
86    let value = match opts {
87        Opts::Send { gateway, invoice } => {
88            json(lightning.send(invoice, gateway, Value::Null).await?)
89        }
90        Opts::AwaitSend { operation_id } => json(
91            lightning
92                .await_final_send_operation_state(operation_id)
93                .await?,
94        ),
95        Opts::Receive { amount, gateway } => json(
96            lightning
97                .receive(
98                    amount,
99                    3600,
100                    Bolt11InvoiceDescription::Direct(String::new()),
101                    gateway,
102                    Value::Null,
103                )
104                .await?,
105        ),
106        Opts::AwaitReceive { operation_id } => json(
107            lightning
108                .await_final_receive_operation_state(operation_id)
109                .await?,
110        ),
111        Opts::Lnurl(lnurl_opts) => match lnurl_opts {
112            LnurlOpts::Generate {
113                recurringd,
114                gateway,
115            } => json(lightning.generate_lnurl(recurringd, gateway).await?),
116        },
117        Opts::Gateways(gateway_opts) => match gateway_opts {
118            #[allow(clippy::unit_arg)]
119            GatewaysOpts::Map => json(lightning.update_gateway_map().await),
120            GatewaysOpts::Select { invoice } => json(lightning.select_gateway(invoice).await?.0),
121            GatewaysOpts::List { peer } => json(lightning.list_gateways(peer).await?),
122            GatewaysOpts::Add { gateway } => {
123                let auth = lightning
124                    .admin_auth
125                    .clone()
126                    .ok_or(CliCommandError::AdminAuthNotSet)?;
127
128                json(lightning.module_api.add_gateway(auth, gateway).await?)
129            }
130            GatewaysOpts::Remove { gateway } => {
131                let auth = lightning
132                    .admin_auth
133                    .clone()
134                    .ok_or(CliCommandError::AdminAuthNotSet)?;
135
136                json(lightning.module_api.remove_gateway(auth, gateway).await?)
137            }
138        },
139    };
140
141    Ok(value)
142}
143
144fn json<T: Serialize>(value: T) -> Value {
145    serde_json::to_value(value).expect("JSON serialization failed")
146}
147
148/// A failure of an `lnv2` module command.
149#[derive(Debug, thiserror::Error)]
150pub(crate) enum CliCommandError {
151    /// The payment could not be started.
152    #[error(transparent)]
153    Send(#[from] SendPaymentError),
154
155    /// The operation to await could not be looked up.
156    #[error(transparent)]
157    OperationLookup(#[from] OperationLookupError),
158
159    /// The invoice could not be created.
160    #[error(transparent)]
161    Receive(#[from] ReceiveError),
162
163    /// The LNURL could not be generated.
164    #[error(transparent)]
165    GenerateLnurl(#[from] GenerateLnurlError),
166
167    /// No gateway could be selected.
168    #[error(transparent)]
169    SelectGateway(#[from] SelectGatewayError),
170
171    /// The vetted gateways could not be listed.
172    #[error(transparent)]
173    ListGateways(#[from] ListGatewaysError),
174
175    /// The federation did not serve an admin request.
176    #[error(transparent)]
177    Federation(#[from] FederationError),
178
179    /// The client has no admin credentials, which adding or removing a vetted
180    /// gateway needs.
181    #[error("Admin auth not set")]
182    AdminAuthNotSet,
183}