1pub mod ldk;
2pub mod lnd;
3
4use std::fmt::Debug;
5use std::str::FromStr;
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use bitcoin::Network;
10use bitcoin::hashes::sha256;
11use fedimint_core::Amount;
12use fedimint_core::encoding::{Decodable, Encodable};
13use fedimint_core::secp256k1::PublicKey;
14use fedimint_core::task::TaskGroup;
15use fedimint_core::util::{backoff_util, retry};
16use fedimint_gateway_common::{
17 ChannelInfo, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, GetInvoiceRequest,
18 GetInvoiceResponse, ListTransactionsResponse, OpenChannelRequest, SendOnchainRequest,
19};
20use fedimint_ln_common::PrunedInvoice;
21pub use fedimint_ln_common::contracts::Preimage;
22use fedimint_ln_common::route_hints::RouteHint;
23use fedimint_logging::LOG_LIGHTNING;
24use futures::stream::BoxStream;
25use lightning_invoice::Bolt11Invoice;
26use serde::{Deserialize, Serialize};
27use thiserror::Error;
28use tracing::{info, warn};
29
30pub const MAX_LIGHTNING_RETRIES: u32 = 10;
31
32pub type RouteHtlcStream<'a> = BoxStream<'a, InterceptPaymentRequest>;
33
34#[derive(
35 Error, Debug, Serialize, Deserialize, Encodable, Decodable, Clone, Eq, PartialEq, Hash,
36)]
37pub enum LightningRpcError {
38 #[error("Failed to connect to Lightning node")]
39 FailedToConnect,
40 #[error("Failed to retrieve node info: {failure_reason}")]
41 FailedToGetNodeInfo { failure_reason: String },
42 #[error("Failed to retrieve route hints: {failure_reason}")]
43 FailedToGetRouteHints { failure_reason: String },
44 #[error("Payment failed: {failure_reason}")]
45 FailedPayment { failure_reason: String },
46 #[error("Failed to route HTLCs: {failure_reason}")]
47 FailedToRouteHtlcs { failure_reason: String },
48 #[error("Failed to complete HTLC: {failure_reason}")]
49 FailedToCompleteHtlc { failure_reason: String },
50 #[error("Failed to open channel: {failure_reason}")]
51 FailedToOpenChannel { failure_reason: String },
52 #[error("Failed to close channel: {failure_reason}")]
53 FailedToCloseChannelsWithPeer { failure_reason: String },
54 #[error("Failed to get Invoice: {failure_reason}")]
55 FailedToGetInvoice { failure_reason: String },
56 #[error("Failed to list transactions: {failure_reason}")]
57 FailedToListTransactions { failure_reason: String },
58 #[error("Failed to get funding address: {failure_reason}")]
59 FailedToGetLnOnchainAddress { failure_reason: String },
60 #[error("Failed to withdraw funds on-chain: {failure_reason}")]
61 FailedToWithdrawOnchain { failure_reason: String },
62 #[error("Failed to connect to peer: {failure_reason}")]
63 FailedToConnectToPeer { failure_reason: String },
64 #[error("Failed to list active channels: {failure_reason}")]
65 FailedToListActiveChannels { failure_reason: String },
66 #[error("Failed to get balances: {failure_reason}")]
67 FailedToGetBalances { failure_reason: String },
68 #[error("Failed to sync to chain: {failure_reason}")]
69 FailedToSyncToChain { failure_reason: String },
70 #[error("Invalid metadata: {failure_reason}")]
71 InvalidMetadata { failure_reason: String },
72}
73
74#[derive(Clone, Debug)]
76pub struct LightningContext {
77 pub lnrpc: Arc<dyn ILnRpcClient>,
78 pub lightning_public_key: PublicKey,
79 pub lightning_alias: String,
80 pub lightning_network: Network,
81}
82
83#[async_trait]
87pub trait ILnRpcClient: Debug + Send + Sync {
88 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError>;
90
91 async fn routehints(
96 &self,
97 num_route_hints: usize,
98 ) -> Result<GetRouteHintsResponse, LightningRpcError>;
99
100 async fn pay(
117 &self,
118 invoice: Bolt11Invoice,
119 max_delay: u64,
120 max_fee: Amount,
121 ) -> Result<PayInvoiceResponse, LightningRpcError> {
122 self.pay_private(
123 PrunedInvoice::try_from(invoice).map_err(|_| LightningRpcError::FailedPayment {
124 failure_reason: "Invoice has no amount".to_string(),
125 })?,
126 max_delay,
127 max_fee,
128 )
129 .await
130 }
131
132 async fn pay_private(
142 &self,
143 _invoice: PrunedInvoice,
144 _max_delay: u64,
145 _max_fee: Amount,
146 ) -> Result<PayInvoiceResponse, LightningRpcError> {
147 Err(LightningRpcError::FailedPayment {
148 failure_reason: "Private payments not supported".to_string(),
149 })
150 }
151
152 fn supports_private_payments(&self) -> bool {
156 false
157 }
158
159 async fn route_htlcs<'a>(
170 self: Box<Self>,
171 task_group: &TaskGroup,
172 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError>;
173
174 async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError>;
178
179 async fn create_invoice(
184 &self,
185 create_invoice_request: CreateInvoiceRequest,
186 ) -> Result<CreateInvoiceResponse, LightningRpcError>;
187
188 async fn get_ln_onchain_address(
191 &self,
192 ) -> Result<GetLnOnchainAddressResponse, LightningRpcError>;
193
194 async fn send_onchain(
197 &self,
198 payload: SendOnchainRequest,
199 ) -> Result<SendOnchainResponse, LightningRpcError>;
200
201 async fn open_channel(
203 &self,
204 payload: OpenChannelRequest,
205 ) -> Result<OpenChannelResponse, LightningRpcError>;
206
207 async fn close_channels_with_peer(
209 &self,
210 payload: CloseChannelsWithPeerRequest,
211 ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError>;
212
213 async fn list_active_channels(&self) -> Result<ListActiveChannelsResponse, LightningRpcError>;
215
216 async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError>;
219
220 async fn get_invoice(
221 &self,
222 get_invoice_request: GetInvoiceRequest,
223 ) -> Result<Option<GetInvoiceResponse>, LightningRpcError>;
224
225 async fn list_transactions(
226 &self,
227 start_secs: u64,
228 end_secs: u64,
229 ) -> Result<ListTransactionsResponse, LightningRpcError>;
230}
231
232impl dyn ILnRpcClient {
233 pub async fn parsed_route_hints(&self, num_route_hints: u32) -> Vec<RouteHint> {
237 if num_route_hints == 0 {
238 return vec![];
239 }
240
241 let route_hints =
242 self.routehints(num_route_hints as usize)
243 .await
244 .unwrap_or(GetRouteHintsResponse {
245 route_hints: Vec::new(),
246 });
247 route_hints.route_hints
248 }
249
250 pub async fn parsed_node_info(
253 &self,
254 ) -> std::result::Result<(PublicKey, String, Network, u32, bool), LightningRpcError> {
255 let GetNodeInfoResponse {
256 pub_key,
257 alias,
258 network,
259 block_height,
260 synced_to_chain,
261 } = self.info().await?;
262 let network =
263 Network::from_str(&network).map_err(|e| LightningRpcError::InvalidMetadata {
264 failure_reason: format!("Invalid network {network}: {e}"),
265 })?;
266 Ok((pub_key, alias, network, block_height, synced_to_chain))
267 }
268
269 pub async fn wait_for_chain_sync(&self) -> std::result::Result<(), LightningRpcError> {
271 retry(
272 "Wait for chain sync",
273 backoff_util::background_backoff(),
274 || async {
275 let info = self.info().await?;
276 let block_height = info.block_height;
277 if info.synced_to_chain {
278 Ok(())
279 } else {
280 warn!(target: LOG_LIGHTNING, block_height = %block_height, "Lightning node is not synced yet");
281 Err(anyhow::anyhow!("Not synced yet"))
282 }
283 },
284 )
285 .await
286 .map_err(|e| LightningRpcError::FailedToSyncToChain {
287 failure_reason: format!("Failed to sync to chain: {e:?}"),
288 })?;
289
290 info!(target: LOG_LIGHTNING, "Gateway successfully synced with the chain");
291 Ok(())
292 }
293}
294
295#[derive(Debug, Serialize, Deserialize, Clone)]
296pub struct GetNodeInfoResponse {
297 pub pub_key: PublicKey,
298 pub alias: String,
299 pub network: String,
300 pub block_height: u32,
301 pub synced_to_chain: bool,
302}
303
304#[derive(Debug, Serialize, Deserialize, Clone)]
305pub struct InterceptPaymentRequest {
306 pub payment_hash: sha256::Hash,
307 pub amount_msat: u64,
308 pub expiry: u32,
309 pub incoming_chan_id: u64,
310 pub short_channel_id: Option<u64>,
311 pub htlc_id: u64,
312}
313
314#[derive(Debug, Serialize, Deserialize, Clone)]
315pub struct InterceptPaymentResponse {
316 pub incoming_chan_id: u64,
317 pub htlc_id: u64,
318 pub payment_hash: sha256::Hash,
319 pub action: PaymentAction,
320}
321
322#[derive(Debug, Serialize, Deserialize, Clone)]
323pub enum PaymentAction {
324 Settle(Preimage),
325 Cancel,
326 Forward,
327}
328
329#[derive(Debug, Serialize, Deserialize, Clone)]
330pub struct GetRouteHintsResponse {
331 pub route_hints: Vec<RouteHint>,
332}
333
334#[derive(Debug, Serialize, Deserialize, Clone)]
335pub struct PayInvoiceResponse {
336 pub preimage: Preimage,
337}
338
339#[derive(Debug, Serialize, Deserialize, Clone)]
340pub struct CreateInvoiceRequest {
341 pub payment_hash: Option<sha256::Hash>,
342 pub amount_msat: u64,
343 pub expiry_secs: u32,
344 pub description: Option<InvoiceDescription>,
345}
346
347#[derive(Debug, Serialize, Deserialize, Clone)]
348pub enum InvoiceDescription {
349 Direct(String),
350 Hash(sha256::Hash),
351}
352
353#[derive(Debug, Serialize, Deserialize, Clone)]
354pub struct CreateInvoiceResponse {
355 pub invoice: String,
356}
357
358#[derive(Debug, Serialize, Deserialize, Clone)]
359pub struct GetLnOnchainAddressResponse {
360 pub address: String,
361}
362
363#[derive(Debug, Serialize, Deserialize, Clone)]
364pub struct SendOnchainResponse {
365 pub txid: String,
366}
367
368#[derive(Debug, Serialize, Deserialize, Clone)]
369pub struct OpenChannelResponse {
370 pub funding_txid: String,
371}
372
373#[derive(Debug, Serialize, Deserialize, Clone)]
374pub struct ListActiveChannelsResponse {
375 pub channels: Vec<ChannelInfo>,
376}
377
378#[derive(Debug, Serialize, Deserialize, Clone)]
379pub struct GetBalancesResponse {
380 pub onchain_balance_sats: u64,
381 pub lightning_balance_msats: u64,
382 pub inbound_lightning_liquidity_msats: u64,
383}