gateway_cli/
lightning_commands.rs

1use bitcoin::hashes::sha256;
2use chrono::{DateTime, Utc};
3use clap::Subcommand;
4use fedimint_core::Amount;
5use fedimint_gateway_client::{
6    close_channels_with_peer, create_invoice_for_self, create_offer, get_invoice, list_channels,
7    list_transactions, open_channel, pay_invoice, pay_offer,
8};
9use fedimint_gateway_common::{
10    CloseChannelsWithPeerRequest, CreateInvoiceForOperatorPayload, CreateOfferPayload,
11    GetInvoiceRequest, ListTransactionsPayload, OpenChannelRequest, PayInvoiceForOperatorPayload,
12    PayOfferPayload,
13};
14use fedimint_ln_common::client::GatewayApi;
15use lightning_invoice::Bolt11Invoice;
16
17use crate::{SafeUrl, print_response};
18
19/// This API is intentionally kept very minimal, as its main purpose is to
20/// provide a simple and consistent way to establish liquidity between gateways
21/// in a test environment.
22#[derive(Subcommand)]
23pub enum LightningCommands {
24    /// Create an invoice to receive lightning funds to the gateway.
25    CreateInvoice {
26        amount_msats: u64,
27
28        #[clap(long)]
29        expiry_secs: Option<u32>,
30
31        #[clap(long)]
32        description: Option<String>,
33    },
34    /// Pay a lightning invoice as the gateway (i.e. no e-cash exchange).
35    PayInvoice { invoice: Bolt11Invoice },
36    /// Open a channel with another lightning node.
37    OpenChannel {
38        /// The public key of the node to open a channel with
39        #[clap(long)]
40        pubkey: bitcoin::secp256k1::PublicKey,
41
42        #[clap(long)]
43        host: String,
44
45        /// The amount to fund the channel with
46        #[clap(long)]
47        channel_size_sats: u64,
48
49        /// The amount to push to the other side of the channel
50        #[clap(long)]
51        push_amount_sats: Option<u64>,
52    },
53    /// Close all channels with a peer, claiming the funds to the lightning
54    /// node's on-chain wallet.
55    CloseChannelsWithPeer {
56        /// The public key of the node to close channels with
57        #[clap(long)]
58        pubkey: bitcoin::secp256k1::PublicKey,
59
60        /// Flag to specify if the channel should be force closed
61        #[clap(long)]
62        force: bool,
63
64        /// Fee rate to use when closing the channel (required unless --force is
65        /// set)
66        #[clap(long, required_unless_present = "force")]
67        sats_per_vbyte: Option<u64>,
68    },
69    /// List channels.
70    ListChannels,
71    /// List the Lightning transactions that the Lightning node has received and
72    /// sent
73    ListTransactions {
74        /// The timestamp to start listing transactions from (e.g.,
75        /// "2025-03-14T15:30:00Z")
76        #[arg(long, value_parser = parse_datetime)]
77        start_time: DateTime<Utc>,
78
79        /// The timestamp to end listing transactions from (e.g.,
80        /// "2025-03-15T15:30:00Z")
81        #[arg(long, value_parser = parse_datetime)]
82        end_time: DateTime<Utc>,
83    },
84    /// Get details about a specific invoice
85    GetInvoice {
86        /// The payment hash of the invoice
87        #[clap(long)]
88        payment_hash: sha256::Hash,
89    },
90    CreateOffer {
91        #[clap(long)]
92        amount_msat: Option<u64>,
93
94        #[clap(long)]
95        description: Option<String>,
96
97        #[clap(long)]
98        expiry_secs: Option<u32>,
99
100        #[clap(long)]
101        quantity: Option<u64>,
102    },
103    PayOffer {
104        #[clap(long)]
105        offer: String,
106
107        #[clap(long)]
108        amount_msat: Option<u64>,
109
110        #[clap(long)]
111        quantity: Option<u64>,
112
113        #[clap(long)]
114        payer_note: Option<String>,
115    },
116}
117
118fn parse_datetime(s: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
119    s.parse::<DateTime<Utc>>()
120}
121
122impl LightningCommands {
123    #![allow(clippy::too_many_lines)]
124    pub async fn handle(self, client: &GatewayApi, base_url: &SafeUrl) -> anyhow::Result<()> {
125        match self {
126            Self::CreateInvoice {
127                amount_msats,
128                expiry_secs,
129                description,
130            } => {
131                let response = create_invoice_for_self(
132                    client,
133                    base_url,
134                    CreateInvoiceForOperatorPayload {
135                        amount_msats,
136                        expiry_secs,
137                        description,
138                    },
139                )
140                .await?;
141                println!("{response}");
142            }
143            Self::PayInvoice { invoice } => {
144                let response =
145                    pay_invoice(client, base_url, PayInvoiceForOperatorPayload { invoice }).await?;
146                println!("{response}");
147            }
148            Self::OpenChannel {
149                pubkey,
150                host,
151                channel_size_sats,
152                push_amount_sats,
153            } => {
154                let funding_txid = open_channel(
155                    client,
156                    base_url,
157                    OpenChannelRequest {
158                        pubkey,
159                        host,
160                        channel_size_sats,
161                        push_amount_sats: push_amount_sats.unwrap_or(0),
162                    },
163                )
164                .await?;
165                println!("{funding_txid}");
166            }
167            Self::CloseChannelsWithPeer {
168                pubkey,
169                force,
170                sats_per_vbyte,
171            } => {
172                let response = close_channels_with_peer(
173                    client,
174                    base_url,
175                    CloseChannelsWithPeerRequest {
176                        pubkey,
177                        force,
178                        sats_per_vbyte,
179                    },
180                )
181                .await?;
182                print_response(response);
183            }
184            Self::ListChannels => {
185                let response = list_channels(client, base_url).await?;
186                print_response(response);
187            }
188            Self::GetInvoice { payment_hash } => {
189                let response =
190                    get_invoice(client, base_url, GetInvoiceRequest { payment_hash }).await?;
191                print_response(response);
192            }
193            Self::ListTransactions {
194                start_time,
195                end_time,
196            } => {
197                let start_secs = start_time.timestamp().try_into()?;
198                let end_secs = end_time.timestamp().try_into()?;
199                let response = list_transactions(
200                    client,
201                    base_url,
202                    ListTransactionsPayload {
203                        start_secs,
204                        end_secs,
205                    },
206                )
207                .await?;
208                print_response(response);
209            }
210            Self::CreateOffer {
211                amount_msat,
212                description,
213                expiry_secs,
214                quantity,
215            } => {
216                let response = create_offer(
217                    client,
218                    base_url,
219                    CreateOfferPayload {
220                        amount: amount_msat.map(Amount::from_msats),
221                        description,
222                        expiry_secs,
223                        quantity,
224                    },
225                )
226                .await?;
227                print_response(response);
228            }
229            Self::PayOffer {
230                offer,
231                amount_msat,
232                quantity,
233                payer_note,
234            } => {
235                let response = pay_offer(
236                    client,
237                    base_url,
238                    PayOfferPayload {
239                        offer,
240                        amount: amount_msat.map(Amount::from_msats),
241                        quantity,
242                        payer_note,
243                    },
244                )
245                .await?;
246                print_response(response);
247            }
248        }
249
250        Ok(())
251    }
252}