1pub mod cln;
2pub mod extension;
3pub mod ldk;
4pub mod lnd;
5
6use std::fmt::Debug;
7use std::path::PathBuf;
8use std::str::FromStr;
9use std::sync::Arc;
10
11use async_trait::async_trait;
12use bitcoin::Network;
13use clap::Subcommand;
14use fedimint_bip39::Mnemonic;
15use fedimint_core::db::Database;
16use fedimint_core::encoding::{Decodable, Encodable};
17use fedimint_core::envs::is_env_var_set;
18use fedimint_core::secp256k1::PublicKey;
19use fedimint_core::task::TaskGroup;
20use fedimint_core::util::{backoff_util, retry, SafeUrl};
21use fedimint_core::{secp256k1, Amount};
22use fedimint_ln_common::route_hints::RouteHint;
23use fedimint_ln_common::PrunedInvoice;
24use futures::stream::BoxStream;
25use lightning_invoice::Bolt11Invoice;
26use serde::{Deserialize, Serialize};
27use thiserror::Error;
28use tracing::{debug, info, warn};
29
30use self::cln::NetworkLnRpcClient;
31use self::lnd::GatewayLndClient;
32use crate::envs::{
33 FM_GATEWAY_LIGHTNING_ADDR_ENV, FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV, FM_LDK_ESPLORA_SERVER_URL,
34 FM_LDK_NETWORK, FM_LND_MACAROON_ENV, FM_LND_RPC_ADDR_ENV, FM_LND_TLS_CERT_ENV, FM_PORT_LDK,
35};
36use crate::rpc::{CloseChannelsWithPeerPayload, SendOnchainPayload};
37use crate::{OpenChannelPayload, Preimage};
38
39pub const MAX_LIGHTNING_RETRIES: u32 = 10;
40
41pub type RouteHtlcStream<'a> = BoxStream<'a, InterceptPaymentRequest>;
42
43#[derive(
44 Error, Debug, Serialize, Deserialize, Encodable, Decodable, Clone, Eq, PartialEq, Hash,
45)]
46pub enum LightningRpcError {
47 #[error("Failed to connect to Lightning node")]
48 FailedToConnect,
49 #[error("Failed to retrieve node info: {failure_reason}")]
50 FailedToGetNodeInfo { failure_reason: String },
51 #[error("Failed to retrieve route hints: {failure_reason}")]
52 FailedToGetRouteHints { failure_reason: String },
53 #[error("Payment failed: {failure_reason}")]
54 FailedPayment { failure_reason: String },
55 #[error("Failed to route HTLCs: {failure_reason}")]
56 FailedToRouteHtlcs { failure_reason: String },
57 #[error("Failed to complete HTLC: {failure_reason}")]
58 FailedToCompleteHtlc { failure_reason: String },
59 #[error("Failed to open channel: {failure_reason}")]
60 FailedToOpenChannel { failure_reason: String },
61 #[error("Failed to close channel: {failure_reason}")]
62 FailedToCloseChannelsWithPeer { failure_reason: String },
63 #[error("Failed to get Invoice: {failure_reason}")]
64 FailedToGetInvoice { failure_reason: String },
65 #[error("Failed to get funding address: {failure_reason}")]
66 FailedToGetLnOnchainAddress { failure_reason: String },
67 #[error("Failed to withdraw funds on-chain: {failure_reason}")]
68 FailedToWithdrawOnchain { failure_reason: String },
69 #[error("Failed to connect to peer: {failure_reason}")]
70 FailedToConnectToPeer { failure_reason: String },
71 #[error("Failed to list active channels: {failure_reason}")]
72 FailedToListActiveChannels { failure_reason: String },
73 #[error("Failed to get balances: {failure_reason}")]
74 FailedToGetBalances { failure_reason: String },
75 #[error("Failed to subscribe to invoice updates: {failure_reason}")]
76 FailedToSubscribeToInvoiceUpdates { failure_reason: String },
77 #[error("Failed to sync to chain: {failure_reason}")]
78 FailedToSyncToChain { failure_reason: String },
79 #[error("Invalid metadata: {failure_reason}")]
80 InvalidMetadata { failure_reason: String },
81}
82
83#[derive(Clone, Debug)]
85pub struct LightningContext {
86 pub lnrpc: Arc<dyn ILnRpcClient>,
87 pub lightning_public_key: PublicKey,
88 pub lightning_alias: String,
89 pub lightning_network: Network,
90}
91
92#[async_trait]
96pub trait ILnRpcClient: Debug + Send + Sync {
97 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError>;
99
100 async fn routehints(
105 &self,
106 num_route_hints: usize,
107 ) -> Result<GetRouteHintsResponse, LightningRpcError>;
108
109 async fn pay(
126 &self,
127 invoice: Bolt11Invoice,
128 max_delay: u64,
129 max_fee: Amount,
130 ) -> Result<PayInvoiceResponse, LightningRpcError> {
131 self.pay_private(
132 PrunedInvoice::try_from(invoice).map_err(|_| LightningRpcError::FailedPayment {
133 failure_reason: "Invoice has no amount".to_string(),
134 })?,
135 max_delay,
136 max_fee,
137 )
138 .await
139 }
140
141 async fn pay_private(
151 &self,
152 _invoice: PrunedInvoice,
153 _max_delay: u64,
154 _max_fee: Amount,
155 ) -> Result<PayInvoiceResponse, LightningRpcError> {
156 Err(LightningRpcError::FailedPayment {
157 failure_reason: "Private payments not supported".to_string(),
158 })
159 }
160
161 fn supports_private_payments(&self) -> bool {
165 false
166 }
167
168 async fn route_htlcs<'a>(
179 self: Box<Self>,
180 task_group: &TaskGroup,
181 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError>;
182
183 async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError>;
187
188 async fn create_invoice(
193 &self,
194 create_invoice_request: CreateInvoiceRequest,
195 ) -> Result<CreateInvoiceResponse, LightningRpcError>;
196
197 async fn get_ln_onchain_address(
200 &self,
201 ) -> Result<GetLnOnchainAddressResponse, LightningRpcError>;
202
203 async fn send_onchain(
206 &self,
207 payload: SendOnchainPayload,
208 ) -> Result<SendOnchainResponse, LightningRpcError>;
209
210 async fn open_channel(
212 &self,
213 payload: OpenChannelPayload,
214 ) -> Result<OpenChannelResponse, LightningRpcError>;
215
216 async fn close_channels_with_peer(
218 &self,
219 payload: CloseChannelsWithPeerPayload,
220 ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError>;
221
222 async fn list_active_channels(&self) -> Result<Vec<ChannelInfo>, LightningRpcError>;
224
225 async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError>;
228}
229
230impl dyn ILnRpcClient {
231 pub async fn parsed_route_hints(&self, num_route_hints: u32) -> Vec<RouteHint> {
235 if num_route_hints == 0 {
236 return vec![];
237 }
238
239 let route_hints =
240 self.routehints(num_route_hints as usize)
241 .await
242 .unwrap_or(GetRouteHintsResponse {
243 route_hints: Vec::new(),
244 });
245 route_hints.route_hints
246 }
247
248 pub async fn parsed_node_info(
251 &self,
252 ) -> std::result::Result<(PublicKey, String, Network, u32, bool), LightningRpcError> {
253 let GetNodeInfoResponse {
254 pub_key,
255 alias,
256 network,
257 block_height,
258 synced_to_chain,
259 } = self.info().await?;
260 let network =
261 Network::from_str(&network).map_err(|e| LightningRpcError::InvalidMetadata {
262 failure_reason: format!("Invalid network {network}: {e}"),
263 })?;
264 Ok((pub_key, alias, network, block_height, synced_to_chain))
265 }
266
267 pub async fn wait_for_chain_sync(&self) -> std::result::Result<(), LightningRpcError> {
269 if is_env_var_set(FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV) {
270 debug!("Skip waiting for gateway to sync to chain");
271 return Ok(());
272 }
273
274 retry(
275 "Wait for chain sync",
276 backoff_util::background_backoff(),
277 || async {
278 let info = self.info().await?;
279 let block_height = info.block_height;
280 if info.synced_to_chain {
281 Ok(())
282 } else {
283 warn!(?block_height, "Lightning node is not synced yet");
284 Err(anyhow::anyhow!("Not synced yet"))
285 }
286 },
287 )
288 .await
289 .map_err(|e| LightningRpcError::FailedToSyncToChain {
290 failure_reason: format!("Failed to sync to chain: {e:?}"),
291 })?;
292
293 info!("Gateway successfully synced with the chain");
294 Ok(())
295 }
296}
297
298#[derive(Serialize, Deserialize, Debug, Clone)]
299pub struct ChannelInfo {
300 pub remote_pubkey: secp256k1::PublicKey,
301 pub channel_size_sats: u64,
302 pub outbound_liquidity_sats: u64,
303 pub inbound_liquidity_sats: u64,
304 pub short_channel_id: u64,
305}
306
307#[derive(Debug, Clone, Subcommand, Serialize, Deserialize, Eq, PartialEq)]
308pub enum LightningMode {
309 #[clap(name = "lnd")]
310 Lnd {
311 #[arg(long = "lnd-rpc-host", env = FM_LND_RPC_ADDR_ENV)]
313 lnd_rpc_addr: String,
314
315 #[arg(long = "lnd-tls-cert", env = FM_LND_TLS_CERT_ENV)]
317 lnd_tls_cert: String,
318
319 #[arg(long = "lnd-macaroon", env = FM_LND_MACAROON_ENV)]
321 lnd_macaroon: String,
322 },
323 #[clap(name = "cln")]
324 Cln {
325 #[arg(long = "cln-extension-addr", env = FM_GATEWAY_LIGHTNING_ADDR_ENV)]
326 cln_extension_addr: SafeUrl,
327 },
328 #[clap(name = "ldk")]
329 Ldk {
330 #[arg(long = "ldk-esplora-server-url", env = FM_LDK_ESPLORA_SERVER_URL)]
332 esplora_server_url: String,
333
334 #[arg(long = "ldk-network", env = FM_LDK_NETWORK, default_value = "regtest")]
336 network: Network,
337
338 #[arg(long = "ldk-lightning-port", env = FM_PORT_LDK)]
340 lightning_port: u16,
341 },
342}
343
344#[async_trait]
345pub trait LightningBuilder {
346 async fn build(&self) -> Box<dyn ILnRpcClient>;
347 fn lightning_mode(&self) -> Option<LightningMode> {
348 None
349 }
350}
351
352#[derive(Clone)]
353pub struct GatewayLightningBuilder {
354 pub lightning_mode: LightningMode,
355 pub gateway_db: Database,
356 pub ldk_data_dir: PathBuf,
357 pub mnemonic: Mnemonic,
358}
359
360#[async_trait]
361impl LightningBuilder for GatewayLightningBuilder {
362 async fn build(&self) -> Box<dyn ILnRpcClient> {
363 match self.lightning_mode.clone() {
364 LightningMode::Cln { cln_extension_addr } => {
365 Box::new(NetworkLnRpcClient::new(cln_extension_addr))
366 }
367 LightningMode::Lnd {
368 lnd_rpc_addr,
369 lnd_tls_cert,
370 lnd_macaroon,
371 } => Box::new(GatewayLndClient::new(
372 lnd_rpc_addr,
373 lnd_tls_cert,
374 lnd_macaroon,
375 None,
376 self.gateway_db.clone(),
377 )),
378 LightningMode::Ldk {
379 esplora_server_url,
380 network,
381 lightning_port,
382 } => Box::new(
383 ldk::GatewayLdkClient::new(
384 &self.ldk_data_dir,
385 &esplora_server_url,
386 network,
387 lightning_port,
388 self.mnemonic.clone(),
389 )
390 .unwrap(),
391 ),
392 }
393 }
394
395 fn lightning_mode(&self) -> Option<LightningMode> {
396 Some(self.lightning_mode.clone())
397 }
398}
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct GetNodeInfoResponse {
402 pub pub_key: PublicKey,
403 pub alias: String,
404 pub network: String,
405 pub block_height: u32,
406 pub synced_to_chain: bool,
407}
408
409#[derive(Debug, Serialize, Deserialize, Clone)]
410pub struct InterceptPaymentRequest {
411 pub payment_hash: crate::sha256::Hash,
412 pub amount_msat: u64,
413 pub expiry: u32,
414 pub incoming_chan_id: u64,
415 pub short_channel_id: Option<u64>,
416 pub htlc_id: u64,
417}
418
419#[derive(Debug, Serialize, Deserialize, Clone)]
420pub struct InterceptPaymentResponse {
421 pub incoming_chan_id: u64,
422 pub htlc_id: u64,
423 pub payment_hash: crate::sha256::Hash,
424 pub action: PaymentAction,
425}
426
427#[derive(Debug, Serialize, Deserialize, Clone)]
428pub enum PaymentAction {
429 Settle(Preimage),
430 Cancel,
431 Forward,
432}
433
434#[derive(Debug, Serialize, Deserialize, Clone)]
435pub struct GetRouteHintsRequest {
436 pub num_route_hints: u64,
437}
438
439#[derive(Debug, Serialize, Deserialize, Clone)]
440pub struct GetRouteHintsResponse {
441 pub route_hints: Vec<RouteHint>,
442}
443
444#[derive(Debug, Serialize, Deserialize, Clone)]
445pub struct PayInvoiceRequest {
446 pub invoice: String,
447 pub max_delay: u64,
448 pub max_fee_msat: u64,
449 pub payment_hash: Vec<u8>,
450}
451
452#[derive(Debug, Serialize, Deserialize, Clone)]
453pub struct PayInvoiceResponse {
454 pub preimage: Preimage,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone)]
458pub struct PayPrunedInvoiceRequest {
459 pub pruned_invoice: Option<PrunedInvoice>,
460 pub max_delay: u64,
461 pub max_fee_msat: Amount,
462}
463
464#[derive(Debug, Serialize, Deserialize, Clone)]
465pub struct CreateInvoiceRequest {
466 pub payment_hash: Option<crate::sha256::Hash>,
467 pub amount_msat: u64,
468 pub expiry_secs: u32,
469 pub description: Option<InvoiceDescription>,
470}
471
472#[derive(Debug, Serialize, Deserialize, Clone)]
473pub enum InvoiceDescription {
474 Direct(String),
475 Hash(crate::sha256::Hash),
476}
477
478#[derive(Debug, Serialize, Deserialize, Clone)]
479pub struct CreateInvoiceResponse {
480 pub invoice: String,
481}
482
483#[derive(Debug, Serialize, Deserialize, Clone)]
484pub struct GetLnOnchainAddressResponse {
485 pub address: String,
486}
487
488#[derive(Debug, Serialize, Deserialize, Clone)]
489pub struct SendOnchainResponse {
490 pub txid: String,
491}
492
493#[derive(Debug, Serialize, Deserialize, Clone)]
494pub struct OpenChannelResponse {
495 pub funding_txid: String,
496}
497
498#[derive(Debug, Serialize, Deserialize, Clone)]
499pub struct CloseChannelsWithPeerResponse {
500 pub num_channels_closed: u32,
501}
502
503#[derive(Debug, Serialize, Deserialize, Clone)]
504pub struct ListActiveChannelsResponse {
505 pub channels: Vec<ChannelInfo>,
506}
507
508#[derive(Debug, Serialize, Deserialize, Clone)]
509pub struct GetBalancesResponse {
510 pub onchain_balance_sats: u64,
511 pub lightning_balance_msats: u64,
512 pub inbound_lightning_liquidity_msats: u64,
513}