gateway_cli/
lightning_commands.rs

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