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