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, create_invoice_for_self, create_offer, get_invoice, list_channels,
8 list_transactions, open_channel, open_channel_with_push, pay_invoice, pay_offer,
9};
10use fedimint_gateway_common::{
11 CloseChannelsWithPeerRequest, CreateInvoiceForOperatorPayload, CreateOfferPayload,
12 GetInvoiceRequest, ListTransactionsPayload, OpenChannelRequest, PayInvoiceForOperatorPayload,
13 PayOfferPayload,
14};
15use fedimint_ln_common::client::GatewayApi;
16use lightning_invoice::Bolt11Invoice;
17
18use crate::{CliOutput, CliOutputResult, SafeUrl};
19
20#[derive(Subcommand)]
23pub enum LightningCommands {
24 CreateInvoice {
26 amount_msats: u64,
27
28 #[clap(long)]
29 expiry_secs: Option<u32>,
30
31 #[clap(long)]
32 description: Option<String>,
33 },
34 PayInvoice { invoice: Bolt11Invoice },
36 OpenChannel {
38 #[clap(long)]
40 pubkey: bitcoin::secp256k1::PublicKey,
41
42 #[clap(long)]
43 host: String,
44
45 #[clap(long)]
47 channel_size_sats: u64,
48
49 #[clap(long)]
51 push_amount_sats: Option<u64>,
52 },
53 CloseChannelsWithPeer {
56 #[clap(long)]
58 pubkey: bitcoin::secp256k1::PublicKey,
59
60 #[clap(long)]
62 force: bool,
63
64 #[clap(long, required_unless_present = "force")]
67 sats_per_vbyte: Option<u64>,
68 },
69 ListChannels,
71 ListTransactions {
74 #[arg(long, value_parser = parse_datetime)]
77 start_time: DateTime<Utc>,
78
79 #[arg(long, value_parser = parse_datetime)]
82 end_time: DateTime<Utc>,
83 },
84 GetInvoice {
86 #[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) -> CliOutputResult {
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 Ok(CliOutput::Invoice {
142 invoice: response.to_string(),
143 })
144 }
145 Self::PayInvoice { invoice } => {
146 let preimage =
147 pay_invoice(client, base_url, PayInvoiceForOperatorPayload { invoice }).await?;
148 Ok(CliOutput::Preimage { preimage })
149 }
150 Self::OpenChannel {
151 pubkey,
152 host,
153 channel_size_sats,
154 push_amount_sats,
155 } => {
156 let payload = OpenChannelRequest {
157 pubkey,
158 host,
159 channel_size_sats,
160 push_amount_sats: push_amount_sats.unwrap_or(0),
161 };
162 let funding_txid = if payload.push_amount_sats > 0 {
163 open_channel_with_push(client, base_url, payload).await?
164 } else {
165 open_channel(client, base_url, payload).await?
166 };
167 Ok(CliOutput::FundingTxid { funding_txid })
168 }
169 Self::CloseChannelsWithPeer {
170 pubkey,
171 force,
172 sats_per_vbyte,
173 } => {
174 let response = close_channels_with_peer(
175 client,
176 base_url,
177 CloseChannelsWithPeerRequest {
178 pubkey,
179 force,
180 sats_per_vbyte,
181 },
182 )
183 .await?;
184 Ok(CliOutput::CloseChannels(response))
185 }
186 Self::ListChannels => {
187 let response = list_channels(client, base_url).await?;
188 Ok(CliOutput::Channels(response))
189 }
190 Self::GetInvoice { payment_hash } => {
191 let response =
192 get_invoice(client, base_url, GetInvoiceRequest { payment_hash }).await?;
193 Ok(CliOutput::InvoiceDetails(response))
194 }
195 Self::ListTransactions {
196 start_time,
197 end_time,
198 } => {
199 let start_secs = start_time
200 .timestamp()
201 .try_into()
202 .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
203 let end_secs = end_time
204 .timestamp()
205 .try_into()
206 .map_err(|e| ServerError::InternalClientError(anyhow::anyhow!("{e}")))?;
207 let response = list_transactions(
208 client,
209 base_url,
210 ListTransactionsPayload {
211 start_secs,
212 end_secs,
213 },
214 )
215 .await?;
216 Ok(CliOutput::Transactions(response))
217 }
218 Self::CreateOffer {
219 amount_msat,
220 description,
221 expiry_secs,
222 quantity,
223 } => {
224 let response = create_offer(
225 client,
226 base_url,
227 CreateOfferPayload {
228 amount: amount_msat.map(Amount::from_msats),
229 description,
230 expiry_secs,
231 quantity,
232 },
233 )
234 .await?;
235 Ok(CliOutput::Offer(response))
236 }
237 Self::PayOffer {
238 offer,
239 amount_msat,
240 quantity,
241 payer_note,
242 } => {
243 let response = pay_offer(
244 client,
245 base_url,
246 PayOfferPayload {
247 offer,
248 amount: amount_msat.map(Amount::from_msats),
249 quantity,
250 payer_note,
251 },
252 )
253 .await?;
254 Ok(CliOutput::OfferPayment(response))
255 }
256 }
257 }
258}