1use bitcoin::hashes::sha256;
2use chrono::{DateTime, Utc};
3use clap::Subcommand;
4use fedimint_connectors::error::ServerError;
5use fedimint_core::Amount;
6use fedimint_gateway_client::{
7 close_channels_with_peer, connect_peer, create_invoice_for_self, create_offer, get_invoice,
8 list_channels, list_transactions, open_channel, open_channel_with_push, pay_invoice, pay_offer,
9 set_channel_fees,
10};
11use fedimint_gateway_common::{
12 CloseChannelsWithPeerRequest, ConnectPeerRequest, CreateInvoiceForOperatorPayload,
13 CreateOfferPayload, GetInvoiceRequest, ListTransactionsPayload, NodeAddress,
14 OpenChannelRequest, PayInvoiceForOperatorPayload, PayOfferPayload, SetChannelFeesRequest,
15};
16use fedimint_ln_common::client::GatewayApi;
17use lightning_invoice::Bolt11Invoice;
18
19use crate::{CliOutput, CliOutputResult, SafeUrl};
20
21#[derive(Subcommand)]
24pub enum LightningCommands {
25 CreateInvoice {
27 amount_msats: u64,
28
29 #[clap(long)]
30 expiry_secs: Option<u32>,
31
32 #[clap(long)]
33 description: Option<String>,
34 },
35 PayInvoice { invoice: Bolt11Invoice },
37 ConnectPeer {
39 #[clap(long)]
42 node_address: NodeAddress,
43 },
44 OpenChannel {
46 #[clap(long)]
48 pubkey: bitcoin::secp256k1::PublicKey,
49
50 #[clap(long)]
51 host: String,
52
53 #[clap(long)]
55 channel_size_sats: u64,
56
57 #[clap(long)]
59 push_amount_sats: Option<u64>,
60
61 #[clap(long)]
64 fee_rate_sats_per_vbyte: Option<u64>,
65
66 #[clap(long)]
68 base_fee_msat: Option<u64>,
69
70 #[clap(long)]
73 parts_per_million: Option<u64>,
74 },
75 CloseChannelsWithPeer {
78 #[clap(long)]
80 pubkey: bitcoin::secp256k1::PublicKey,
81
82 #[clap(long)]
84 force: bool,
85
86 #[clap(long, required_unless_present = "force")]
89 sats_per_vbyte: Option<u64>,
90 },
91 ListChannels,
93 SetChannelFees {
95 #[clap(long)]
97 funding_outpoint: bitcoin::OutPoint,
98
99 #[clap(long)]
101 base_fee_msat: u64,
102
103 #[clap(long)]
105 parts_per_million: u64,
106 },
107 ListTransactions {
110 #[arg(long, value_parser = parse_datetime)]
113 start_time: DateTime<Utc>,
114
115 #[arg(long, value_parser = parse_datetime)]
118 end_time: DateTime<Utc>,
119 },
120 GetInvoice {
122 #[clap(long)]
124 payment_hash: sha256::Hash,
125 },
126 CreateOffer {
127 #[clap(long)]
128 amount_msat: Option<u64>,
129
130 #[clap(long)]
131 description: Option<String>,
132
133 #[clap(long)]
134 expiry_secs: Option<u32>,
135
136 #[clap(long)]
137 quantity: Option<u64>,
138 },
139 PayOffer {
140 #[clap(long)]
141 offer: String,
142
143 #[clap(long)]
144 amount_msat: Option<u64>,
145
146 #[clap(long)]
147 quantity: Option<u64>,
148
149 #[clap(long)]
150 payer_note: Option<String>,
151 },
152}
153
154fn parse_datetime(s: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
155 s.parse::<DateTime<Utc>>()
156}
157
158impl LightningCommands {
159 #![allow(clippy::too_many_lines)]
160 pub async fn handle(self, client: &GatewayApi, base_url: &SafeUrl) -> CliOutputResult {
161 match self {
162 Self::CreateInvoice {
163 amount_msats,
164 expiry_secs,
165 description,
166 } => {
167 let response = create_invoice_for_self(
168 client,
169 base_url,
170 CreateInvoiceForOperatorPayload {
171 amount_msats,
172 expiry_secs,
173 description,
174 },
175 )
176 .await?;
177 Ok(CliOutput::Invoice {
178 invoice: response.to_string(),
179 })
180 }
181 Self::PayInvoice { invoice } => {
182 let preimage =
183 pay_invoice(client, base_url, PayInvoiceForOperatorPayload { invoice }).await?;
184 Ok(CliOutput::Preimage { preimage })
185 }
186 Self::ConnectPeer { node_address } => {
187 connect_peer(client, base_url, ConnectPeerRequest { node_address }).await?;
188 Ok(CliOutput::Empty)
189 }
190 Self::OpenChannel {
191 pubkey,
192 host,
193 channel_size_sats,
194 push_amount_sats,
195 fee_rate_sats_per_vbyte,
196 base_fee_msat,
197 parts_per_million,
198 } => {
199 let payload = OpenChannelRequest {
200 pubkey,
201 host,
202 channel_size_sats,
203 push_amount_sats: push_amount_sats.unwrap_or(0),
204 fee_rate_sats_per_vbyte,
205 base_fee_msat,
206 parts_per_million,
207 };
208 let funding_txid = if payload.push_amount_sats > 0 {
209 open_channel_with_push(client, base_url, payload).await?
210 } else {
211 open_channel(client, base_url, payload).await?
212 };
213 Ok(CliOutput::FundingTxid { funding_txid })
214 }
215 Self::CloseChannelsWithPeer {
216 pubkey,
217 force,
218 sats_per_vbyte,
219 } => {
220 let response = close_channels_with_peer(
221 client,
222 base_url,
223 CloseChannelsWithPeerRequest {
224 pubkey,
225 force,
226 sats_per_vbyte,
227 },
228 )
229 .await?;
230 Ok(CliOutput::CloseChannels(response))
231 }
232 Self::ListChannels => {
233 let response = list_channels(client, base_url).await?;
234 Ok(CliOutput::Channels(response))
235 }
236 Self::SetChannelFees {
237 funding_outpoint,
238 base_fee_msat,
239 parts_per_million,
240 } => {
241 set_channel_fees(
242 client,
243 base_url,
244 SetChannelFeesRequest {
245 funding_outpoint,
246 base_fee_msat,
247 parts_per_million,
248 },
249 )
250 .await?;
251 Ok(CliOutput::Empty)
252 }
253 Self::GetInvoice { payment_hash } => {
254 let response =
255 get_invoice(client, base_url, GetInvoiceRequest { payment_hash }).await?;
256 Ok(CliOutput::InvoiceDetails(response))
257 }
258 Self::ListTransactions {
259 start_time,
260 end_time,
261 } => {
262 let start_secs = start_time
263 .timestamp()
264 .try_into()
265 .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
266 let end_secs = end_time
267 .timestamp()
268 .try_into()
269 .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
270 let response = list_transactions(
271 client,
272 base_url,
273 ListTransactionsPayload {
274 start_secs,
275 end_secs,
276 },
277 )
278 .await?;
279 Ok(CliOutput::Transactions(response))
280 }
281 Self::CreateOffer {
282 amount_msat,
283 description,
284 expiry_secs,
285 quantity,
286 } => {
287 let response = create_offer(
288 client,
289 base_url,
290 CreateOfferPayload {
291 amount: amount_msat.map(Amount::from_msats),
292 description,
293 expiry_secs,
294 quantity,
295 },
296 )
297 .await?;
298 Ok(CliOutput::Offer(response))
299 }
300 Self::PayOffer {
301 offer,
302 amount_msat,
303 quantity,
304 payer_note,
305 } => {
306 let response = pay_offer(
307 client,
308 base_url,
309 PayOfferPayload {
310 offer,
311 amount: amount_msat.map(Amount::from_msats),
312 quantity,
313 payer_note,
314 },
315 )
316 .await?;
317 Ok(CliOutput::OfferPayment(response))
318 }
319 }
320 }
321}