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, NodeId};
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        let network_graph = self.node.network_graph();
654
655        for channel_details in self.node.list_channels().iter() {
656            // Look up peer alias from network graph
657            let remote_node_alias = {
658                let node_id = NodeId::from_pubkey(&channel_details.counterparty_node_id);
659                network_graph.node(&node_id).and_then(|node_info| {
660                    node_info
661                        .announcement_info
662                        .as_ref()
663                        .and_then(|announcement| {
664                            let alias = announcement.alias().to_string();
665                            if alias.is_empty() { None } else { Some(alias) }
666                        })
667                })
668            };
669
670            channels.push(ChannelInfo {
671                remote_pubkey: channel_details.counterparty_node_id,
672                channel_size_sats: channel_details.channel_value_sats,
673                outbound_liquidity_sats: channel_details.outbound_capacity_msat / 1000,
674                inbound_liquidity_sats: channel_details.inbound_capacity_msat / 1000,
675                is_active: channel_details.is_usable,
676                funding_outpoint: channel_details.funding_txo,
677                remote_node_alias,
678            });
679        }
680
681        Ok(ListChannelsResponse { channels })
682    }
683
684    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
685        let balances = self.node.list_balances();
686        let channel_lists = self
687            .node
688            .list_channels()
689            .into_iter()
690            .filter(|chan| chan.is_usable)
691            .collect::<Vec<_>>();
692        // map and get the total inbound_capacity_msat in the channels
693        let total_inbound_liquidity_balance_msat: u64 = channel_lists
694            .iter()
695            .map(|channel| channel.inbound_capacity_msat)
696            .sum();
697
698        Ok(GetBalancesResponse {
699            onchain_balance_sats: balances.total_onchain_balance_sats,
700            lightning_balance_msats: balances.total_lightning_balance_sats * 1000,
701            inbound_lightning_liquidity_msats: total_inbound_liquidity_balance_msat,
702        })
703    }
704
705    async fn get_invoice(
706        &self,
707        get_invoice_request: GetInvoiceRequest,
708    ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
709        let invoices = self
710            .node
711            .list_payments_with_filter(|details| {
712                details.direction == PaymentDirection::Inbound
713                    && details.id == PaymentId(get_invoice_request.payment_hash.to_byte_array())
714                    && !matches!(details.kind, PaymentKind::Onchain { .. })
715            })
716            .iter()
717            .map(|details| {
718                let (preimage, payment_hash, _) = get_preimage_and_payment_hash(&details.kind);
719                let status = match details.status {
720                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
721                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
722                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
723                };
724                GetInvoiceResponse {
725                    preimage: preimage.map(|p| p.to_string()),
726                    payment_hash,
727                    amount: Amount::from_msats(
728                        details
729                            .amount_msat
730                            .expect("amountless invoices are not supported"),
731                    ),
732                    created_at: UNIX_EPOCH + Duration::from_secs(details.latest_update_timestamp),
733                    status,
734                }
735            })
736            .collect::<Vec<_>>();
737
738        Ok(invoices.first().cloned())
739    }
740
741    async fn list_transactions(
742        &self,
743        start_secs: u64,
744        end_secs: u64,
745    ) -> Result<ListTransactionsResponse, LightningRpcError> {
746        let transactions = self
747            .node
748            .list_payments_with_filter(|details| {
749                !matches!(details.kind, PaymentKind::Onchain { .. })
750                    && details.latest_update_timestamp >= start_secs
751                    && details.latest_update_timestamp < end_secs
752            })
753            .iter()
754            .map(|details| {
755                let (preimage, payment_hash, payment_kind) =
756                    get_preimage_and_payment_hash(&details.kind);
757                let direction = match details.direction {
758                    PaymentDirection::Outbound => {
759                        fedimint_gateway_common::PaymentDirection::Outbound
760                    }
761                    PaymentDirection::Inbound => fedimint_gateway_common::PaymentDirection::Inbound,
762                };
763                let status = match details.status {
764                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
765                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
766                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
767                };
768                fedimint_gateway_common::PaymentDetails {
769                    payment_hash,
770                    preimage: preimage.map(|p| p.to_string()),
771                    payment_kind,
772                    amount: Amount::from_msats(
773                        details
774                            .amount_msat
775                            .expect("amountless invoices are not supported"),
776                    ),
777                    direction,
778                    status,
779                    timestamp_secs: details.latest_update_timestamp,
780                }
781            })
782            .collect::<Vec<_>>();
783        Ok(ListTransactionsResponse { transactions })
784    }
785
786    fn create_offer(
787        &self,
788        amount: Option<Amount>,
789        description: Option<String>,
790        expiry_secs: Option<u32>,
791        quantity: Option<u64>,
792    ) -> Result<String, LightningRpcError> {
793        let description = description.unwrap_or_default();
794        let offer = if let Some(amount) = amount {
795            self.node
796                .bolt12_payment()
797                .receive(amount.msats, &description, expiry_secs, quantity)
798                .map_err(|err| LightningRpcError::Bolt12Error {
799                    failure_reason: err.to_string(),
800                })?
801        } else {
802            self.node
803                .bolt12_payment()
804                .receive_variable_amount(&description, expiry_secs)
805                .map_err(|err| LightningRpcError::Bolt12Error {
806                    failure_reason: err.to_string(),
807                })?
808        };
809
810        Ok(offer.to_string())
811    }
812
813    async fn pay_offer(
814        &self,
815        offer: String,
816        quantity: Option<u64>,
817        amount: Option<Amount>,
818        payer_note: Option<String>,
819    ) -> Result<Preimage, LightningRpcError> {
820        let offer = Offer::from_str(&offer).map_err(|_| LightningRpcError::Bolt12Error {
821            failure_reason: "Failed to parse Bolt12 Offer".to_string(),
822        })?;
823
824        let _offer_lock_guard = self
825            .outbound_offer_lock_pool
826            .blocking_lock(LdkOfferId(offer.id()));
827
828        let payment_id = if let Some(amount) = amount {
829            self.node
830                .bolt12_payment()
831                .send_using_amount(&offer, amount.msats, quantity, payer_note)
832                .map_err(|err| LightningRpcError::Bolt12Error {
833                    failure_reason: err.to_string(),
834                })?
835        } else {
836            self.node
837                .bolt12_payment()
838                .send(&offer, quantity, payer_note)
839                .map_err(|err| LightningRpcError::Bolt12Error {
840                    failure_reason: err.to_string(),
841                })?
842        };
843
844        loop {
845            if let Some(payment_details) = self.node.payment(&payment_id) {
846                match payment_details.status {
847                    PaymentStatus::Pending => {}
848                    PaymentStatus::Succeeded => match payment_details.kind {
849                        PaymentKind::Bolt12Offer {
850                            preimage: Some(preimage),
851                            ..
852                        } => {
853                            info!(target: LOG_LIGHTNING, offer = %offer, payment_id = %payment_id, preimage = %preimage, "Successfully paid offer");
854                            return Ok(Preimage(preimage.0));
855                        }
856                        _ => {
857                            return Err(LightningRpcError::FailedPayment {
858                                failure_reason: "Unexpected payment kind".to_string(),
859                            });
860                        }
861                    },
862                    PaymentStatus::Failed => {
863                        return Err(LightningRpcError::FailedPayment {
864                            failure_reason: "Bolt12 payment failed".to_string(),
865                        });
866                    }
867                }
868            }
869            fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
870        }
871    }
872
873    fn sync_wallet(&self) -> Result<(), LightningRpcError> {
874        block_in_place(|| {
875            let _ = self.node.sync_wallets();
876        });
877        Ok(())
878    }
879}
880
881/// Maps LDK's `PaymentKind` to an optional preimage and an optional payment
882/// hash depending on the type of payment.
883fn get_preimage_and_payment_hash(
884    kind: &PaymentKind,
885) -> (
886    Option<Preimage>,
887    Option<sha256::Hash>,
888    fedimint_gateway_common::PaymentKind,
889) {
890    match kind {
891        PaymentKind::Bolt11 {
892            hash,
893            preimage,
894            secret: _,
895        } => (
896            preimage.map(|p| Preimage(p.0)),
897            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
898            fedimint_gateway_common::PaymentKind::Bolt11,
899        ),
900        PaymentKind::Bolt11Jit {
901            hash,
902            preimage,
903            secret: _,
904            lsp_fee_limits: _,
905            ..
906        } => (
907            preimage.map(|p| Preimage(p.0)),
908            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
909            fedimint_gateway_common::PaymentKind::Bolt11,
910        ),
911        PaymentKind::Bolt12Offer {
912            hash,
913            preimage,
914            secret: _,
915            offer_id: _,
916            payer_note: _,
917            quantity: _,
918        } => (
919            preimage.map(|p| Preimage(p.0)),
920            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
921            fedimint_gateway_common::PaymentKind::Bolt12Offer,
922        ),
923        PaymentKind::Bolt12Refund {
924            hash,
925            preimage,
926            secret: _,
927            payer_note: _,
928            quantity: _,
929        } => (
930            preimage.map(|p| Preimage(p.0)),
931            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
932            fedimint_gateway_common::PaymentKind::Bolt12Refund,
933        ),
934        PaymentKind::Spontaneous { hash, preimage } => (
935            preimage.map(|p| Preimage(p.0)),
936            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
937            fedimint_gateway_common::PaymentKind::Bolt11,
938        ),
939        PaymentKind::Onchain { .. } => (None, None, fedimint_gateway_common::PaymentKind::Onchain),
940    }
941}
942
943/// When a port is specified in the Esplora URL, the esplora client inside LDK
944/// node cannot connect to the lightning node when there is a trailing slash.
945/// The `SafeUrl::Display` function will always serialize the `SafeUrl` with a
946/// trailing slash, which causes the connection to fail.
947///
948/// To handle this, we explicitly construct the esplora URL when a port is
949/// specified.
950fn get_esplora_url(server_url: SafeUrl) -> anyhow::Result<String> {
951    // Esplora client cannot handle trailing slashes
952    let host = server_url
953        .host_str()
954        .ok_or(anyhow::anyhow!("Missing esplora host"))?;
955    let server_url = if let Some(port) = server_url.port() {
956        format!("{}://{}:{}", server_url.scheme(), host, port)
957    } else {
958        server_url.to_string()
959    };
960    Ok(server_url)
961}
962
963#[derive(Debug, Clone, Copy, Eq, PartialEq)]
964struct LdkOfferId(OfferId);
965
966impl std::hash::Hash for LdkOfferId {
967    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
968        state.write(&self.0.0);
969    }
970}
971
972#[derive(Debug, Copy, Clone, PartialEq, Eq)]
973pub struct UserChannelId(pub ldk_node::UserChannelId);
974
975impl PartialOrd for UserChannelId {
976    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
977        Some(self.cmp(other))
978    }
979}
980
981impl Ord for UserChannelId {
982    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
983        self.0.0.cmp(&other.0.0)
984    }
985}
986
987#[cfg(test)]
988mod tests;