fedimint_ln_client/
cli.rs1use 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 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 {
30 payment_info: String,
32 #[clap(long)]
34 amount: Option<Amount>,
35 #[clap(long)]
37 lnurl_comment: Option<String>,
38 #[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}