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