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::GatewayRpcClient;
15use lightning_invoice::Bolt11Invoice;
16
17use crate::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        #[clap(long)]
61        force: bool,
62    },
63    /// List channels.
64    ListChannels,
65    /// List the Lightning transactions that the Lightning node has received and
66    /// sent
67    ListTransactions {
68        /// The timestamp to start listing transactions from (e.g.,
69        /// "2025-03-14T15:30:00Z")
70        #[arg(long, value_parser = parse_datetime)]
71        start_time: DateTime<Utc>,
72
73        /// The timestamp to end listing transactions from (e.g.,
74        /// "2025-03-15T15:30:00Z")
75        #[arg(long, value_parser = parse_datetime)]
76        end_time: DateTime<Utc>,
77    },
78    /// Get details about a specific invoice
79    GetInvoice {
80        /// The payment hash of the invoice
81        #[clap(long)]
82        payment_hash: sha256::Hash,
83    },
84    CreateOffer {
85        #[clap(long)]
86        amount_msat: Option<u64>,
87
88        #[clap(long)]
89        description: Option<String>,
90
91        #[clap(long)]
92        expiry_secs: Option<u32>,
93
94        #[clap(long)]
95        quantity: Option<u64>,
96    },
97    PayOffer {
98        #[clap(long)]
99        offer: String,
100
101        #[clap(long)]
102        amount_msat: Option<u64>,
103
104        #[clap(long)]
105        quantity: Option<u64>,
106
107        #[clap(long)]
108        payer_note: Option<String>,
109    },
110}
111
112fn parse_datetime(s: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
113    s.parse::<DateTime<Utc>>()
114}
115
116impl LightningCommands {
117    #![allow(clippy::too_many_lines)]
118    pub async fn handle(self, client: &GatewayRpcClient) -> anyhow::Result<()> {
119        match self {
120            Self::CreateInvoice {
121                amount_msats,
122                expiry_secs,
123                description,
124            } => {
125                let response = create_invoice_for_self(
126                    client,
127                    CreateInvoiceForOperatorPayload {
128                        amount_msats,
129                        expiry_secs,
130                        description,
131                    },
132                )
133                .await?;
134                println!("{response}");
135            }
136            Self::PayInvoice { invoice } => {
137                let response =
138                    pay_invoice(client, PayInvoiceForOperatorPayload { invoice }).await?;
139                println!("{response}");
140            }
141            Self::OpenChannel {
142                pubkey,
143                host,
144                channel_size_sats,
145                push_amount_sats,
146            } => {
147                let funding_txid = open_channel(
148                    client,
149                    OpenChannelRequest {
150                        pubkey,
151                        host,
152                        channel_size_sats,
153                        push_amount_sats: push_amount_sats.unwrap_or(0),
154                    },
155                )
156                .await?;
157                println!("{funding_txid}");
158            }
159            Self::CloseChannelsWithPeer { pubkey, force } => {
160                let response = close_channels_with_peer(
161                    client,
162                    CloseChannelsWithPeerRequest { pubkey, force },
163                )
164                .await?;
165                print_response(response);
166            }
167            Self::ListChannels => {
168                let response = list_channels(client).await?;
169                print_response(response);
170            }
171            Self::GetInvoice { payment_hash } => {
172                let response = get_invoice(client, GetInvoiceRequest { payment_hash }).await?;
173                print_response(response);
174            }
175            Self::ListTransactions {
176                start_time,
177                end_time,
178            } => {
179                let start_secs = start_time.timestamp().try_into()?;
180                let end_secs = end_time.timestamp().try_into()?;
181                let response = list_transactions(
182                    client,
183                    ListTransactionsPayload {
184                        start_secs,
185                        end_secs,
186                    },
187                )
188                .await?;
189                print_response(response);
190            }
191            Self::CreateOffer {
192                amount_msat,
193                description,
194                expiry_secs,
195                quantity,
196            } => {
197                let response = create_offer(
198                    client,
199                    CreateOfferPayload {
200                        amount: amount_msat.map(Amount::from_msats),
201                        description,
202                        expiry_secs,
203                        quantity,
204                    },
205                )
206                .await?;
207                print_response(response);
208            }
209            Self::PayOffer {
210                offer,
211                amount_msat,
212                quantity,
213                payer_note,
214            } => {
215                let response = pay_offer(
216                    client,
217                    PayOfferPayload {
218                        offer,
219                        amount: amount_msat.map(Amount::from_msats),
220                        quantity,
221                        payer_note,
222                    },
223                )
224                .await?;
225                print_response(response);
226            }
227        }
228
229        Ok(())
230    }
231}