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::GatewayApi;
15use lightning_invoice::Bolt11Invoice;
16
17use crate::{SafeUrl, print_response};
18
19#[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) -> anyhow::Result<()> {
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 println!("{response}");
142 }
143 Self::PayInvoice { invoice } => {
144 let response =
145 pay_invoice(client, base_url, PayInvoiceForOperatorPayload { invoice }).await?;
146 println!("{response}");
147 }
148 Self::OpenChannel {
149 pubkey,
150 host,
151 channel_size_sats,
152 push_amount_sats,
153 } => {
154 let funding_txid = open_channel(
155 client,
156 base_url,
157 OpenChannelRequest {
158 pubkey,
159 host,
160 channel_size_sats,
161 push_amount_sats: push_amount_sats.unwrap_or(0),
162 },
163 )
164 .await?;
165 println!("{funding_txid}");
166 }
167 Self::CloseChannelsWithPeer {
168 pubkey,
169 force,
170 sats_per_vbyte,
171 } => {
172 let response = close_channels_with_peer(
173 client,
174 base_url,
175 CloseChannelsWithPeerRequest {
176 pubkey,
177 force,
178 sats_per_vbyte,
179 },
180 )
181 .await?;
182 print_response(response);
183 }
184 Self::ListChannels => {
185 let response = list_channels(client, base_url).await?;
186 print_response(response);
187 }
188 Self::GetInvoice { payment_hash } => {
189 let response =
190 get_invoice(client, base_url, GetInvoiceRequest { payment_hash }).await?;
191 print_response(response);
192 }
193 Self::ListTransactions {
194 start_time,
195 end_time,
196 } => {
197 let start_secs = start_time.timestamp().try_into()?;
198 let end_secs = end_time.timestamp().try_into()?;
199 let response = list_transactions(
200 client,
201 base_url,
202 ListTransactionsPayload {
203 start_secs,
204 end_secs,
205 },
206 )
207 .await?;
208 print_response(response);
209 }
210 Self::CreateOffer {
211 amount_msat,
212 description,
213 expiry_secs,
214 quantity,
215 } => {
216 let response = create_offer(
217 client,
218 base_url,
219 CreateOfferPayload {
220 amount: amount_msat.map(Amount::from_msats),
221 description,
222 expiry_secs,
223 quantity,
224 },
225 )
226 .await?;
227 print_response(response);
228 }
229 Self::PayOffer {
230 offer,
231 amount_msat,
232 quantity,
233 payer_note,
234 } => {
235 let response = pay_offer(
236 client,
237 base_url,
238 PayOfferPayload {
239 offer,
240 amount: amount_msat.map(Amount::from_msats),
241 quantity,
242 payer_note,
243 },
244 )
245 .await?;
246 print_response(response);
247 }
248 }
249
250 Ok(())
251 }
252}