fedimint_ln_client/
cli.rs

1use std::{ffi, iter};
2
3use anyhow::Context as _;
4use clap::Parser;
5use fedimint_core::Amount;
6use fedimint_core::core::OperationId;
7use fedimint_core::secp256k1::PublicKey;
8use lightning_invoice::{Bolt11InvoiceDescription, Description};
9use serde::{Deserialize, Serialize};
10use tracing::info;
11
12use crate::OutgoingLightningPayment;
13
14#[derive(Parser, Serialize)]
15enum Opts {
16    /// Create a lightning invoice to receive payment via gateway
17    Invoice {
18        amount: Amount,
19        #[clap(long, default_value = "")]
20        description: String,
21        #[clap(long)]
22        expiry_time: Option<u64>,
23        #[clap(long)]
24        gateway_id: Option<PublicKey>,
25        #[clap(long, default_value = "false")]
26        force_internal: bool,
27    },
28    /// Pay a lightning invoice or lnurl via a gateway
29    Pay {
30        /// Lightning invoice or lnurl
31        payment_info: String,
32        /// Amount to pay, used for lnurl
33        #[clap(long)]
34        amount: Option<Amount>,
35        /// Invoice comment/description, used on lnurl
36        #[clap(long)]
37        lnurl_comment: Option<String>,
38        /// Will return immediately after funding the payment
39        #[clap(long, action)]
40        finish_in_background: bool,
41        #[clap(long)]
42        gateway_id: Option<PublicKey>,
43        #[clap(long, default_value = "false")]
44        force_internal: bool,
45    },
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub struct LnInvoiceResponse {
51    pub operation_id: OperationId,
52    pub invoice: String,
53}
54
55pub(crate) async fn handle_cli_command(
56    module: &super::LightningClientModule,
57    args: &[ffi::OsString],
58) -> anyhow::Result<serde_json::Value> {
59    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("meta")).chain(args.iter()));
60
61    Ok(match opts {
62        Opts::Invoice {
63            amount,
64            description,
65            expiry_time,
66            gateway_id,
67            force_internal,
68        } => {
69            let ln_gateway = module.get_gateway(gateway_id, force_internal).await?;
70
71            let desc = Description::new(description)?;
72            let (operation_id, invoice, _) = module
73                .create_bolt11_invoice(
74                    amount,
75                    Bolt11InvoiceDescription::Direct(&desc),
76                    expiry_time,
77                    (),
78                    ln_gateway,
79                )
80                .await?;
81            serde_json::to_value(LnInvoiceResponse {
82                operation_id,
83                invoice: invoice.to_string(),
84            })
85            .expect("Can't fail")
86        }
87        Opts::Pay {
88            payment_info,
89            amount,
90            finish_in_background,
91            lnurl_comment,
92            gateway_id,
93            force_internal,
94        } => {
95            let bolt11 = crate::get_invoice(&payment_info, amount, lnurl_comment).await?;
96            info!("Paying invoice: {bolt11}");
97            let ln_gateway = module.get_gateway(gateway_id, force_internal).await?;
98
99            let OutgoingLightningPayment {
100                payment_type,
101                contract_id,
102                fee,
103            } = module.pay_bolt11_invoice(ln_gateway, bolt11, ()).await?;
104            let operation_id = payment_type.operation_id();
105            info!(
106                "Gateway fee: {fee}, payment operation id: {}",
107                operation_id.fmt_short()
108            );
109            if finish_in_background {
110                module
111                    .wait_for_ln_payment(payment_type, contract_id, true)
112                    .await?;
113                info!("Payment will finish in background, use await-ln-pay to get the result");
114                serde_json::json! {
115                    {
116                        "operation_id": operation_id,
117                        "payment_type": payment_type.payment_type(),
118                        "contract_id": contract_id,
119                        "fee": fee,
120                    }
121                }
122            } else {
123                module
124                    .wait_for_ln_payment(payment_type, contract_id, false)
125                    .await?
126                    .context("expected a response")?
127            }
128        }
129    })
130}