fedimint_lightning/
ldk.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::{Duration, UNIX_EPOCH};
6
7use async_trait::async_trait;
8use bitcoin::hashes::{Hash, sha256};
9use bitcoin::{FeeRate, Network, OutPoint};
10use fedimint_bip39::Mnemonic;
11use fedimint_core::task::{TaskGroup, TaskHandle, block_in_place};
12use fedimint_core::util::{FmtCompact, SafeUrl};
13use fedimint_core::{Amount, BitcoinAmountOrAll, crit};
14use fedimint_gateway_common::{
15    ChainSource, GetInvoiceRequest, GetInvoiceResponse, ListTransactionsResponse,
16};
17use fedimint_ln_common::contracts::Preimage;
18use fedimint_logging::LOG_LIGHTNING;
19use ldk_node::lightning::ln::msgs::SocketAddress;
20use ldk_node::lightning::routing::gossip::NodeAlias;
21use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus, SendingParameters};
22use lightning::ln::channelmanager::PaymentId;
23use lightning::offers::offer::{Offer, OfferId};
24use lightning::types::payment::{PaymentHash, PaymentPreimage};
25use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
26use tokio::sync::mpsc::Sender;
27use tokio::sync::{RwLock, oneshot};
28use tokio_stream::wrappers::ReceiverStream;
29use tracing::{debug, error, info, warn};
30
31use super::{ChannelInfo, ILnRpcClient, LightningRpcError, ListChannelsResponse, RouteHtlcStream};
32use crate::{
33    CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, CreateInvoiceRequest,
34    CreateInvoiceResponse, GetBalancesResponse, GetLnOnchainAddressResponse, GetNodeInfoResponse,
35    GetRouteHintsResponse, InterceptPaymentRequest, InterceptPaymentResponse, InvoiceDescription,
36    OpenChannelRequest, OpenChannelResponse, PayInvoiceResponse, PaymentAction, SendOnchainRequest,
37    SendOnchainResponse,
38};
39
40pub struct GatewayLdkClient {
41    /// The underlying lightning node.
42    node: Arc<ldk_node::Node>,
43
44    task_group: TaskGroup,
45
46    /// The HTLC stream, until it is taken by calling
47    /// `ILnRpcClient::route_htlcs`.
48    htlc_stream_receiver_or: Option<tokio::sync::mpsc::Receiver<InterceptPaymentRequest>>,
49
50    /// Lock pool used to ensure that our implementation of `ILnRpcClient::pay`
51    /// doesn't allow for multiple simultaneous calls with the same invoice to
52    /// execute in parallel. This helps ensure that the function is idempotent.
53    outbound_lightning_payment_lock_pool: lockable::LockPool<PaymentId>,
54
55    /// Lock pool used to ensure that our implementation of
56    /// `ILnRpcClient::pay_offer` doesn't allow for multiple simultaneous
57    /// calls with the same offer to execute in parallel. This helps ensure
58    /// that the function is idempotent.
59    outbound_offer_lock_pool: lockable::LockPool<LdkOfferId>,
60
61    /// A map keyed by the `UserChannelId` of a channel that is currently
62    /// opening. The `Sender` is used to communicate the `OutPoint` back to
63    /// the API handler from the event handler when the channel has been
64    /// opened and is now pending.
65    pending_channels:
66        Arc<RwLock<BTreeMap<UserChannelId, oneshot::Sender<anyhow::Result<OutPoint>>>>>,
67}
68
69impl std::fmt::Debug for GatewayLdkClient {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        f.debug_struct("GatewayLdkClient").finish_non_exhaustive()
72    }
73}
74
75impl GatewayLdkClient {
76    /// Creates a new `GatewayLdkClient` instance and starts the underlying
77    /// lightning node. All resources, including the lightning node, will be
78    /// cleaned up when the returned `GatewayLdkClient` instance is dropped.
79    /// There's no need to manually stop the node.
80    pub fn new(
81        data_dir: &Path,
82        chain_source: ChainSource,
83        network: Network,
84        lightning_port: u16,
85        alias: String,
86        mnemonic: Mnemonic,
87        runtime: Arc<tokio::runtime::Runtime>,
88    ) -> anyhow::Result<Self> {
89        let mut bytes = [0u8; 32];
90        let alias = if alias.is_empty() {
91            "LDK Gateway".to_string()
92        } else {
93            alias
94        };
95        let alias_bytes = alias.as_bytes();
96        let truncated = &alias_bytes[..alias_bytes.len().min(32)];
97        bytes[..truncated.len()].copy_from_slice(truncated);
98        let node_alias = Some(NodeAlias(bytes));
99
100        let mut node_builder = ldk_node::Builder::from_config(ldk_node::config::Config {
101            network,
102            listening_addresses: Some(vec![SocketAddress::TcpIpV4 {
103                addr: [0, 0, 0, 0],
104                port: lightning_port,
105            }]),
106            node_alias,
107            ..Default::default()
108        });
109
110        node_builder.set_entropy_bip39_mnemonic(mnemonic, None);
111
112        match chain_source.clone() {
113            ChainSource::Bitcoind {
114                username,
115                password,
116                server_url,
117            } => {
118                node_builder.set_chain_source_bitcoind_rpc(
119                    server_url
120                        .host_str()
121                        .expect("Could not retrieve host from bitcoind RPC url")
122                        .to_string(),
123                    server_url
124                        .port()
125                        .expect("Could not retrieve port from bitcoind RPC url"),
126                    username,
127                    password,
128                );
129            }
130            ChainSource::Esplora { server_url } => {
131                node_builder.set_chain_source_esplora(get_esplora_url(server_url)?, None);
132            }
133        };
134        let Some(data_dir_str) = data_dir.to_str() else {
135            return Err(anyhow::anyhow!("Invalid data dir path"));
136        };
137        node_builder.set_storage_dir_path(data_dir_str.to_string());
138
139        info!(chain_source = %chain_source, data_dir = %data_dir_str, alias = %alias, "Starting LDK Node...");
140        let node = Arc::new(node_builder.build()?);
141        node.start_with_runtime(runtime).map_err(|err| {
142            crit!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to start LDK Node");
143            LightningRpcError::FailedToConnect
144        })?;
145
146        let (htlc_stream_sender, htlc_stream_receiver) = tokio::sync::mpsc::channel(1024);
147        let task_group = TaskGroup::new();
148
149        let node_clone = node.clone();
150        let pending_channels = Arc::new(RwLock::new(BTreeMap::new()));
151        let pending_channels_clone = pending_channels.clone();
152        task_group.spawn("ldk lightning node event handler", |handle| async move {
153            loop {
154                Self::handle_next_event(
155                    &node_clone,
156                    &htlc_stream_sender,
157                    &handle,
158                    pending_channels_clone.clone(),
159                )
160                .await;
161            }
162        });
163
164        info!("Successfully started LDK Gateway");
165        Ok(GatewayLdkClient {
166            node,
167            task_group,
168            htlc_stream_receiver_or: Some(htlc_stream_receiver),
169            outbound_lightning_payment_lock_pool: lockable::LockPool::new(),
170            outbound_offer_lock_pool: lockable::LockPool::new(),
171            pending_channels,
172        })
173    }
174
175    async fn handle_next_event(
176        node: &ldk_node::Node,
177        htlc_stream_sender: &Sender<InterceptPaymentRequest>,
178        handle: &TaskHandle,
179        pending_channels: Arc<
180            RwLock<BTreeMap<UserChannelId, oneshot::Sender<anyhow::Result<OutPoint>>>>,
181        >,
182    ) {
183        // We manually check for task termination in case we receive a payment while the
184        // task is shutting down. In that case, we want to finish the payment
185        // before shutting this task down.
186        let event = tokio::select! {
187            event = node.next_event_async() => {
188                event
189            }
190            () = handle.make_shutdown_rx() => {
191                return;
192            }
193        };
194
195        match event {
196            ldk_node::Event::PaymentClaimable {
197                payment_id: _,
198                payment_hash,
199                claimable_amount_msat,
200                claim_deadline,
201                custom_records: _,
202            } => {
203                if let Err(err) = htlc_stream_sender
204                    .send(InterceptPaymentRequest {
205                        payment_hash: Hash::from_slice(&payment_hash.0)
206                            .expect("Failed to create Hash"),
207                        amount_msat: claimable_amount_msat,
208                        expiry: claim_deadline.unwrap_or_default(),
209                        short_channel_id: None,
210                        incoming_chan_id: 0,
211                        htlc_id: 0,
212                    })
213                    .await
214                {
215                    warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed send InterceptHtlcRequest to stream");
216                }
217            }
218            ldk_node::Event::ChannelPending {
219                channel_id,
220                user_channel_id,
221                former_temporary_channel_id: _,
222                counterparty_node_id: _,
223                funding_txo,
224            } => {
225                info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is pending");
226                let mut channels = pending_channels.write().await;
227                if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
228                    let _ = sender.send(Ok(funding_txo));
229                } else {
230                    debug!(
231                        ?user_channel_id,
232                        "No channel pending channel open for user channel id"
233                    );
234                }
235            }
236            ldk_node::Event::ChannelClosed {
237                channel_id,
238                user_channel_id,
239                counterparty_node_id: _,
240                reason,
241            } => {
242                info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is closed");
243                let mut channels = pending_channels.write().await;
244                if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
245                    let reason = if let Some(reason) = reason {
246                        reason.to_string()
247                    } else {
248                        "Channel has been closed".to_string()
249                    };
250                    let _ = sender.send(Err(anyhow::anyhow!(reason)));
251                } else {
252                    debug!(
253                        ?user_channel_id,
254                        "No channel pending channel open for user channel id"
255                    );
256                }
257            }
258            _ => {}
259        }
260
261        // `PaymentClaimable` and `ChannelPending` events are the only event types that
262        // we are interested in. We can safely ignore all other events.
263        if let Err(err) = node.event_handled() {
264            warn!(err = %err.fmt_compact(), "LDK could not mark event handled");
265        }
266    }
267}
268
269impl Drop for GatewayLdkClient {
270    fn drop(&mut self) {
271        self.task_group.shutdown();
272
273        info!(target: LOG_LIGHTNING, "Stopping LDK Node...");
274        match self.node.stop() {
275            Err(err) => {
276                warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to stop LDK Node");
277            }
278            _ => {
279                info!(target: LOG_LIGHTNING, "LDK Node stopped.");
280            }
281        }
282    }
283}
284
285#[async_trait]
286impl ILnRpcClient for GatewayLdkClient {
287    async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
288        let node_status = self.node.status();
289        let ldk_block_height = node_status.current_best_block.height;
290        let onchain_sync = node_status.latest_onchain_wallet_sync_timestamp;
291        let lightning_sync = node_status.latest_lightning_wallet_sync_timestamp;
292        let is_running = node_status.is_running;
293        debug!(target: LOG_LIGHTNING, ?onchain_sync, ?lightning_sync, ?is_running, "LDK Sync Status");
294
295        Ok(GetNodeInfoResponse {
296            pub_key: self.node.node_id(),
297            alias: match self.node.node_alias() {
298                Some(alias) => alias.to_string(),
299                None => format!("LDK Fedimint Gateway Node {}", self.node.node_id()),
300            },
301            network: self.node.config().network.to_string(),
302            block_height: ldk_block_height,
303            // `synced_to_chain` is used for determining if the Lightning node is ready, so we care
304            // about the `lightning_sync` status.
305            synced_to_chain: lightning_sync.is_some(),
306        })
307    }
308
309    async fn routehints(
310        &self,
311        _num_route_hints: usize,
312    ) -> Result<GetRouteHintsResponse, LightningRpcError> {
313        // `ILnRpcClient::routehints()` is currently only ever used for LNv1 payment
314        // receives and will be removed when we switch to LNv2. The LDK gateway will
315        // never support LNv1 payment receives, only LNv2 payment receives, which
316        // require that the gateway's lightning node generates invoices rather than the
317        // fedimint client, so it is able to insert the proper route hints on its own.
318        Ok(GetRouteHintsResponse {
319            route_hints: vec![],
320        })
321    }
322
323    async fn pay(
324        &self,
325        invoice: Bolt11Invoice,
326        max_delay: u64,
327        max_fee: Amount,
328    ) -> Result<PayInvoiceResponse, LightningRpcError> {
329        let payment_id = PaymentId(*invoice.payment_hash().as_byte_array());
330
331        // Lock by the payment hash to prevent multiple simultaneous calls with the same
332        // invoice from executing. This prevents `ldk-node::Bolt11Payment::send()` from
333        // being called multiple times with the same invoice. This is important because
334        // `ldk-node::Bolt11Payment::send()` is not idempotent, but this function must
335        // be idempotent.
336        let _payment_lock_guard = self
337            .outbound_lightning_payment_lock_pool
338            .async_lock(payment_id)
339            .await;
340
341        // If a payment is not known to the node we can initiate it, and if it is known
342        // we can skip calling `ldk-node::Bolt11Payment::send()` and wait for the
343        // payment to complete. The lock guard above guarantees that this block is only
344        // executed once at a time for a given payment hash, ensuring that there is no
345        // race condition between checking if a payment is known and initiating a new
346        // payment if it isn't.
347        if self.node.payment(&payment_id).is_none() {
348            assert_eq!(
349                self.node
350                    .bolt11_payment()
351                    .send(
352                        &invoice,
353                        Some(SendingParameters {
354                            max_total_routing_fee_msat: Some(Some(max_fee.msats)),
355                            max_total_cltv_expiry_delta: Some(max_delay as u32),
356                            max_path_count: None,
357                            max_channel_saturation_power_of_half: None,
358                        }),
359                    )
360                    // TODO: Investigate whether all error types returned by `Bolt11Payment::send()`
361                    // result in idempotency.
362                    .map_err(|e| LightningRpcError::FailedPayment {
363                        failure_reason: format!("LDK payment failed to initialize: {e:?}"),
364                    })?,
365                payment_id
366            );
367        }
368
369        // TODO: Find a way to avoid looping/polling to know when a payment is
370        // completed. `ldk-node` provides `PaymentSuccessful` and `PaymentFailed`
371        // events, but interacting with the node event queue here isn't
372        // straightforward.
373        loop {
374            if let Some(payment_details) = self.node.payment(&payment_id) {
375                match payment_details.status {
376                    PaymentStatus::Pending => {}
377                    PaymentStatus::Succeeded => {
378                        if let PaymentKind::Bolt11 {
379                            preimage: Some(preimage),
380                            ..
381                        } = payment_details.kind
382                        {
383                            return Ok(PayInvoiceResponse {
384                                preimage: Preimage(preimage.0),
385                            });
386                        }
387                    }
388                    PaymentStatus::Failed => {
389                        return Err(LightningRpcError::FailedPayment {
390                            failure_reason: "LDK payment failed".to_string(),
391                        });
392                    }
393                }
394            }
395            fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
396        }
397    }
398
399    async fn route_htlcs<'a>(
400        mut self: Box<Self>,
401        _task_group: &TaskGroup,
402    ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
403        let route_htlc_stream = match self.htlc_stream_receiver_or.take() {
404            Some(stream) => Ok(Box::pin(ReceiverStream::new(stream))),
405            None => Err(LightningRpcError::FailedToRouteHtlcs {
406                failure_reason:
407                    "Stream does not exist. Likely was already taken by calling `route_htlcs()`."
408                        .to_string(),
409            }),
410        }?;
411
412        Ok((route_htlc_stream, Arc::new(*self)))
413    }
414
415    async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
416        let InterceptPaymentResponse {
417            action,
418            payment_hash,
419            incoming_chan_id: _,
420            htlc_id: _,
421        } = htlc;
422
423        let ph = PaymentHash(*payment_hash.clone().as_byte_array());
424
425        // TODO: Get the actual amount from the LDK node. Probably makes the
426        // most sense to pipe it through the `InterceptHtlcResponse` struct.
427        // This value is only used by `ldk-node` to ensure that the amount
428        // claimed isn't less than the amount expected, but we've already
429        // verified that the amount is correct when we intercepted the payment.
430        let claimable_amount_msat = 999_999_999_999_999;
431
432        let ph_hex_str = hex::encode(payment_hash);
433
434        if let PaymentAction::Settle(preimage) = action {
435            self.node
436                .bolt11_payment()
437                .claim_for_hash(ph, claimable_amount_msat, PaymentPreimage(preimage.0))
438                .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
439                    failure_reason: format!("Failed to claim LDK payment with hash {ph_hex_str}"),
440                })?;
441        } else {
442            warn!(target: LOG_LIGHTNING, payment_hash = %ph_hex_str, "Unwinding payment because the action was not `Settle`");
443            self.node.bolt11_payment().fail_for_hash(ph).map_err(|_| {
444                LightningRpcError::FailedToCompleteHtlc {
445                    failure_reason: format!("Failed to unwind LDK payment with hash {ph_hex_str}"),
446                }
447            })?;
448        }
449
450        return Ok(());
451    }
452
453    async fn create_invoice(
454        &self,
455        create_invoice_request: CreateInvoiceRequest,
456    ) -> Result<CreateInvoiceResponse, LightningRpcError> {
457        let payment_hash_or = if let Some(payment_hash) = create_invoice_request.payment_hash {
458            let ph = PaymentHash(*payment_hash.as_byte_array());
459            Some(ph)
460        } else {
461            None
462        };
463
464        let description = match create_invoice_request.description {
465            Some(InvoiceDescription::Direct(desc)) => {
466                Bolt11InvoiceDescription::Direct(Description::new(desc).map_err(|_| {
467                    LightningRpcError::FailedToGetInvoice {
468                        failure_reason: "Invalid description".to_string(),
469                    }
470                })?)
471            }
472            Some(InvoiceDescription::Hash(hash)) => {
473                Bolt11InvoiceDescription::Hash(lightning_invoice::Sha256(hash))
474            }
475            None => Bolt11InvoiceDescription::Direct(Description::empty()),
476        };
477
478        let invoice = match payment_hash_or {
479            Some(payment_hash) => self.node.bolt11_payment().receive_for_hash(
480                create_invoice_request.amount_msat,
481                &description,
482                create_invoice_request.expiry_secs,
483                payment_hash,
484            ),
485            None => self.node.bolt11_payment().receive(
486                create_invoice_request.amount_msat,
487                &description,
488                create_invoice_request.expiry_secs,
489            ),
490        }
491        .map_err(|e| LightningRpcError::FailedToGetInvoice {
492            failure_reason: e.to_string(),
493        })?;
494
495        Ok(CreateInvoiceResponse {
496            invoice: invoice.to_string(),
497        })
498    }
499
500    async fn get_ln_onchain_address(
501        &self,
502    ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
503        self.node
504            .onchain_payment()
505            .new_address()
506            .map(|address| GetLnOnchainAddressResponse {
507                address: address.to_string(),
508            })
509            .map_err(|e| LightningRpcError::FailedToGetLnOnchainAddress {
510                failure_reason: e.to_string(),
511            })
512    }
513
514    async fn send_onchain(
515        &self,
516        SendOnchainRequest {
517            address,
518            amount,
519            fee_rate_sats_per_vbyte,
520        }: SendOnchainRequest,
521    ) -> Result<SendOnchainResponse, LightningRpcError> {
522        let onchain = self.node.onchain_payment();
523
524        let retain_reserves = false;
525        let txid = match amount {
526            BitcoinAmountOrAll::All => onchain.send_all_to_address(
527                &address.assume_checked(),
528                retain_reserves,
529                FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
530            ),
531            BitcoinAmountOrAll::Amount(amount_sats) => onchain.send_to_address(
532                &address.assume_checked(),
533                amount_sats.to_sat(),
534                FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
535            ),
536        }
537        .map_err(|e| LightningRpcError::FailedToWithdrawOnchain {
538            failure_reason: e.to_string(),
539        })?;
540
541        Ok(SendOnchainResponse {
542            txid: txid.to_string(),
543        })
544    }
545
546    async fn open_channel(
547        &self,
548        OpenChannelRequest {
549            pubkey,
550            host,
551            channel_size_sats,
552            push_amount_sats,
553        }: OpenChannelRequest,
554    ) -> Result<OpenChannelResponse, LightningRpcError> {
555        let push_amount_msats_or = if push_amount_sats == 0 {
556            None
557        } else {
558            Some(push_amount_sats * 1000)
559        };
560
561        let (tx, rx) = oneshot::channel::<anyhow::Result<OutPoint>>();
562
563        {
564            let mut channels = self.pending_channels.write().await;
565            let user_channel_id = self
566                .node
567                .open_announced_channel(
568                    pubkey,
569                    SocketAddress::from_str(&host).map_err(|e| {
570                        LightningRpcError::FailedToConnectToPeer {
571                            failure_reason: e.to_string(),
572                        }
573                    })?,
574                    channel_size_sats,
575                    push_amount_msats_or,
576                    None,
577                )
578                .map_err(|e| LightningRpcError::FailedToOpenChannel {
579                    failure_reason: e.to_string(),
580                })?;
581
582            channels.insert(UserChannelId(user_channel_id), tx);
583        }
584
585        match rx
586            .await
587            .map_err(|err| LightningRpcError::FailedToOpenChannel {
588                failure_reason: err.to_string(),
589            })? {
590            Ok(outpoint) => {
591                let funding_txid = outpoint.txid;
592
593                Ok(OpenChannelResponse {
594                    funding_txid: funding_txid.to_string(),
595                })
596            }
597            Err(err) => Err(LightningRpcError::FailedToOpenChannel {
598                failure_reason: err.to_string(),
599            }),
600        }
601    }
602
603    async fn close_channels_with_peer(
604        &self,
605        CloseChannelsWithPeerRequest {
606            pubkey,
607            force,
608            sats_per_vbyte: _,
609        }: CloseChannelsWithPeerRequest,
610    ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
611        let mut num_channels_closed = 0;
612
613        info!(%pubkey, "Closing all channels with peer");
614        for channel_with_peer in self
615            .node
616            .list_channels()
617            .iter()
618            .filter(|channel| channel.counterparty_node_id == pubkey)
619        {
620            if force {
621                match self.node.force_close_channel(
622                    &channel_with_peer.user_channel_id,
623                    pubkey,
624                    Some("User initiated force close".to_string()),
625                ) {
626                    Ok(()) => num_channels_closed += 1,
627                    Err(err) => {
628                        error!(%pubkey, err = %err.fmt_compact(), "Could not force close channel");
629                    }
630                }
631            } else {
632                match self
633                    .node
634                    .close_channel(&channel_with_peer.user_channel_id, pubkey)
635                {
636                    Ok(()) => {
637                        num_channels_closed += 1;
638                    }
639                    Err(err) => {
640                        error!(%pubkey, err = %err.fmt_compact(), "Could not close channel");
641                    }
642                }
643            }
644        }
645
646        Ok(CloseChannelsWithPeerResponse {
647            num_channels_closed,
648        })
649    }
650
651    async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError> {
652        let mut channels = Vec::new();
653
654        for channel_details in self.node.list_channels().iter() {
655            channels.push(ChannelInfo {
656                remote_pubkey: channel_details.counterparty_node_id,
657                channel_size_sats: channel_details.channel_value_sats,
658                outbound_liquidity_sats: channel_details.outbound_capacity_msat / 1000,
659                inbound_liquidity_sats: channel_details.inbound_capacity_msat / 1000,
660                is_active: channel_details.is_usable,
661                funding_outpoint: channel_details.funding_txo,
662            });
663        }
664
665        Ok(ListChannelsResponse { channels })
666    }
667
668    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
669        let balances = self.node.list_balances();
670        let channel_lists = self
671            .node
672            .list_channels()
673            .into_iter()
674            .filter(|chan| chan.is_usable)
675            .collect::<Vec<_>>();
676        // map and get the total inbound_capacity_msat in the channels
677        let total_inbound_liquidity_balance_msat: u64 = channel_lists
678            .iter()
679            .map(|channel| channel.inbound_capacity_msat)
680            .sum();
681
682        Ok(GetBalancesResponse {
683            onchain_balance_sats: balances.total_onchain_balance_sats,
684            lightning_balance_msats: balances.total_lightning_balance_sats * 1000,
685            inbound_lightning_liquidity_msats: total_inbound_liquidity_balance_msat,
686        })
687    }
688
689    async fn get_invoice(
690        &self,
691        get_invoice_request: GetInvoiceRequest,
692    ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
693        let invoices = self
694            .node
695            .list_payments_with_filter(|details| {
696                details.direction == PaymentDirection::Inbound
697                    && details.id == PaymentId(get_invoice_request.payment_hash.to_byte_array())
698                    && !matches!(details.kind, PaymentKind::Onchain { .. })
699            })
700            .iter()
701            .map(|details| {
702                let (preimage, payment_hash, _) = get_preimage_and_payment_hash(&details.kind);
703                let status = match details.status {
704                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
705                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
706                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
707                };
708                GetInvoiceResponse {
709                    preimage: preimage.map(|p| p.to_string()),
710                    payment_hash,
711                    amount: Amount::from_msats(
712                        details
713                            .amount_msat
714                            .expect("amountless invoices are not supported"),
715                    ),
716                    created_at: UNIX_EPOCH + Duration::from_secs(details.latest_update_timestamp),
717                    status,
718                }
719            })
720            .collect::<Vec<_>>();
721
722        Ok(invoices.first().cloned())
723    }
724
725    async fn list_transactions(
726        &self,
727        start_secs: u64,
728        end_secs: u64,
729    ) -> Result<ListTransactionsResponse, LightningRpcError> {
730        let transactions = self
731            .node
732            .list_payments_with_filter(|details| {
733                !matches!(details.kind, PaymentKind::Onchain { .. })
734                    && details.latest_update_timestamp >= start_secs
735                    && details.latest_update_timestamp < end_secs
736            })
737            .iter()
738            .map(|details| {
739                let (preimage, payment_hash, payment_kind) =
740                    get_preimage_and_payment_hash(&details.kind);
741                let direction = match details.direction {
742                    PaymentDirection::Outbound => {
743                        fedimint_gateway_common::PaymentDirection::Outbound
744                    }
745                    PaymentDirection::Inbound => fedimint_gateway_common::PaymentDirection::Inbound,
746                };
747                let status = match details.status {
748                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
749                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
750                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
751                };
752                fedimint_gateway_common::PaymentDetails {
753                    payment_hash,
754                    preimage: preimage.map(|p| p.to_string()),
755                    payment_kind,
756                    amount: Amount::from_msats(
757                        details
758                            .amount_msat
759                            .expect("amountless invoices are not supported"),
760                    ),
761                    direction,
762                    status,
763                    timestamp_secs: details.latest_update_timestamp,
764                }
765            })
766            .collect::<Vec<_>>();
767        Ok(ListTransactionsResponse { transactions })
768    }
769
770    fn create_offer(
771        &self,
772        amount: Option<Amount>,
773        description: Option<String>,
774        expiry_secs: Option<u32>,
775        quantity: Option<u64>,
776    ) -> Result<String, LightningRpcError> {
777        let description = description.unwrap_or_default();
778        let offer = if let Some(amount) = amount {
779            self.node
780                .bolt12_payment()
781                .receive(amount.msats, &description, expiry_secs, quantity)
782                .map_err(|err| LightningRpcError::Bolt12Error {
783                    failure_reason: err.to_string(),
784                })?
785        } else {
786            self.node
787                .bolt12_payment()
788                .receive_variable_amount(&description, expiry_secs)
789                .map_err(|err| LightningRpcError::Bolt12Error {
790                    failure_reason: err.to_string(),
791                })?
792        };
793
794        Ok(offer.to_string())
795    }
796
797    async fn pay_offer(
798        &self,
799        offer: String,
800        quantity: Option<u64>,
801        amount: Option<Amount>,
802        payer_note: Option<String>,
803    ) -> Result<Preimage, LightningRpcError> {
804        let offer = Offer::from_str(&offer).map_err(|_| LightningRpcError::Bolt12Error {
805            failure_reason: "Failed to parse Bolt12 Offer".to_string(),
806        })?;
807
808        let _offer_lock_guard = self
809            .outbound_offer_lock_pool
810            .blocking_lock(LdkOfferId(offer.id()));
811
812        let payment_id = if let Some(amount) = amount {
813            self.node
814                .bolt12_payment()
815                .send_using_amount(&offer, amount.msats, quantity, payer_note)
816                .map_err(|err| LightningRpcError::Bolt12Error {
817                    failure_reason: err.to_string(),
818                })?
819        } else {
820            self.node
821                .bolt12_payment()
822                .send(&offer, quantity, payer_note)
823                .map_err(|err| LightningRpcError::Bolt12Error {
824                    failure_reason: err.to_string(),
825                })?
826        };
827
828        loop {
829            if let Some(payment_details) = self.node.payment(&payment_id) {
830                match payment_details.status {
831                    PaymentStatus::Pending => {}
832                    PaymentStatus::Succeeded => match payment_details.kind {
833                        PaymentKind::Bolt12Offer {
834                            preimage: Some(preimage),
835                            ..
836                        } => {
837                            info!(target: LOG_LIGHTNING, offer = %offer, payment_id = %payment_id, preimage = %preimage, "Successfully paid offer");
838                            return Ok(Preimage(preimage.0));
839                        }
840                        _ => {
841                            return Err(LightningRpcError::FailedPayment {
842                                failure_reason: "Unexpected payment kind".to_string(),
843                            });
844                        }
845                    },
846                    PaymentStatus::Failed => {
847                        return Err(LightningRpcError::FailedPayment {
848                            failure_reason: "Bolt12 payment failed".to_string(),
849                        });
850                    }
851                }
852            }
853            fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
854        }
855    }
856
857    fn sync_wallet(&self) -> Result<(), LightningRpcError> {
858        block_in_place(|| {
859            let _ = self.node.sync_wallets();
860        });
861        Ok(())
862    }
863}
864
865/// Maps LDK's `PaymentKind` to an optional preimage and an optional payment
866/// hash depending on the type of payment.
867fn get_preimage_and_payment_hash(
868    kind: &PaymentKind,
869) -> (
870    Option<Preimage>,
871    Option<sha256::Hash>,
872    fedimint_gateway_common::PaymentKind,
873) {
874    match kind {
875        PaymentKind::Bolt11 {
876            hash,
877            preimage,
878            secret: _,
879        } => (
880            preimage.map(|p| Preimage(p.0)),
881            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
882            fedimint_gateway_common::PaymentKind::Bolt11,
883        ),
884        PaymentKind::Bolt11Jit {
885            hash,
886            preimage,
887            secret: _,
888            lsp_fee_limits: _,
889            ..
890        } => (
891            preimage.map(|p| Preimage(p.0)),
892            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
893            fedimint_gateway_common::PaymentKind::Bolt11,
894        ),
895        PaymentKind::Bolt12Offer {
896            hash,
897            preimage,
898            secret: _,
899            offer_id: _,
900            payer_note: _,
901            quantity: _,
902        } => (
903            preimage.map(|p| Preimage(p.0)),
904            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
905            fedimint_gateway_common::PaymentKind::Bolt12Offer,
906        ),
907        PaymentKind::Bolt12Refund {
908            hash,
909            preimage,
910            secret: _,
911            payer_note: _,
912            quantity: _,
913        } => (
914            preimage.map(|p| Preimage(p.0)),
915            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
916            fedimint_gateway_common::PaymentKind::Bolt12Refund,
917        ),
918        PaymentKind::Spontaneous { hash, preimage } => (
919            preimage.map(|p| Preimage(p.0)),
920            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
921            fedimint_gateway_common::PaymentKind::Bolt11,
922        ),
923        PaymentKind::Onchain { .. } => (None, None, fedimint_gateway_common::PaymentKind::Onchain),
924    }
925}
926
927/// When a port is specified in the Esplora URL, the esplora client inside LDK
928/// node cannot connect to the lightning node when there is a trailing slash.
929/// The `SafeUrl::Display` function will always serialize the `SafeUrl` with a
930/// trailing slash, which causes the connection to fail.
931///
932/// To handle this, we explicitly construct the esplora URL when a port is
933/// specified.
934fn get_esplora_url(server_url: SafeUrl) -> anyhow::Result<String> {
935    // Esplora client cannot handle trailing slashes
936    let host = server_url
937        .host_str()
938        .ok_or(anyhow::anyhow!("Missing esplora host"))?;
939    let server_url = if let Some(port) = server_url.port() {
940        format!("{}://{}:{}", server_url.scheme(), host, port)
941    } else {
942        server_url.to_string()
943    };
944    Ok(server_url)
945}
946
947#[derive(Debug, Clone, Copy, Eq, PartialEq)]
948struct LdkOfferId(OfferId);
949
950impl std::hash::Hash for LdkOfferId {
951    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
952        state.write(&self.0.0);
953    }
954}
955
956#[derive(Debug, Copy, Clone, PartialEq, Eq)]
957pub struct UserChannelId(pub ldk_node::UserChannelId);
958
959impl PartialOrd for UserChannelId {
960    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
961        Some(self.cmp(other))
962    }
963}
964
965impl Ord for UserChannelId {
966    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
967        self.0.0.cmp(&other.0.0)
968    }
969}
970
971#[cfg(test)]
972mod tests;