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 {
579            pubkey,
580            force,
581            sats_per_vbyte: _,
582        }: CloseChannelsWithPeerRequest,
583    ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
584        let mut num_channels_closed = 0;
585
586        info!(%pubkey, "Closing all channels with peer");
587        for channel_with_peer in self
588            .node
589            .list_channels()
590            .iter()
591            .filter(|channel| channel.counterparty_node_id == pubkey)
592        {
593            if force {
594                match self.node.force_close_channel(
595                    &channel_with_peer.user_channel_id,
596                    pubkey,
597                    Some("User initiated force close".to_string()),
598                ) {
599                    Ok(()) => num_channels_closed += 1,
600                    Err(err) => {
601                        error!(%pubkey, err = %err.fmt_compact(), "Could not force close channel");
602                    }
603                }
604            } else {
605                match self
606                    .node
607                    .close_channel(&channel_with_peer.user_channel_id, pubkey)
608                {
609                    Ok(()) => {
610                        num_channels_closed += 1;
611                    }
612                    Err(err) => {
613                        error!(%pubkey, err = %err.fmt_compact(), "Could not close channel");
614                    }
615                }
616            }
617        }
618
619        Ok(CloseChannelsWithPeerResponse {
620            num_channels_closed,
621        })
622    }
623
624    async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError> {
625        let mut channels = Vec::new();
626
627        for channel_details in self.node.list_channels().iter() {
628            channels.push(ChannelInfo {
629                remote_pubkey: channel_details.counterparty_node_id,
630                channel_size_sats: channel_details.channel_value_sats,
631                outbound_liquidity_sats: channel_details.outbound_capacity_msat / 1000,
632                inbound_liquidity_sats: channel_details.inbound_capacity_msat / 1000,
633                is_active: channel_details.is_usable,
634                funding_outpoint: channel_details.funding_txo,
635            });
636        }
637
638        Ok(ListChannelsResponse { channels })
639    }
640
641    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
642        let balances = self.node.list_balances();
643        let channel_lists = self
644            .node
645            .list_channels()
646            .into_iter()
647            .filter(|chan| chan.is_usable)
648            .collect::<Vec<_>>();
649        // map and get the total inbound_capacity_msat in the channels
650        let total_inbound_liquidity_balance_msat: u64 = channel_lists
651            .iter()
652            .map(|channel| channel.inbound_capacity_msat)
653            .sum();
654
655        Ok(GetBalancesResponse {
656            onchain_balance_sats: balances.total_onchain_balance_sats,
657            lightning_balance_msats: balances.total_lightning_balance_sats * 1000,
658            inbound_lightning_liquidity_msats: total_inbound_liquidity_balance_msat,
659        })
660    }
661
662    async fn get_invoice(
663        &self,
664        get_invoice_request: GetInvoiceRequest,
665    ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
666        let invoices = self
667            .node
668            .list_payments_with_filter(|details| {
669                details.direction == PaymentDirection::Inbound
670                    && details.id == PaymentId(get_invoice_request.payment_hash.to_byte_array())
671                    && !matches!(details.kind, PaymentKind::Onchain { .. })
672            })
673            .iter()
674            .map(|details| {
675                let (preimage, payment_hash, _) = get_preimage_and_payment_hash(&details.kind);
676                let status = match details.status {
677                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
678                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
679                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
680                };
681                GetInvoiceResponse {
682                    preimage: preimage.map(|p| p.to_string()),
683                    payment_hash,
684                    amount: Amount::from_msats(
685                        details
686                            .amount_msat
687                            .expect("amountless invoices are not supported"),
688                    ),
689                    created_at: UNIX_EPOCH + Duration::from_secs(details.latest_update_timestamp),
690                    status,
691                }
692            })
693            .collect::<Vec<_>>();
694
695        Ok(invoices.first().cloned())
696    }
697
698    async fn list_transactions(
699        &self,
700        start_secs: u64,
701        end_secs: u64,
702    ) -> Result<ListTransactionsResponse, LightningRpcError> {
703        let transactions = self
704            .node
705            .list_payments_with_filter(|details| {
706                !matches!(details.kind, PaymentKind::Onchain { .. })
707                    && details.latest_update_timestamp >= start_secs
708                    && details.latest_update_timestamp < end_secs
709            })
710            .iter()
711            .map(|details| {
712                let (preimage, payment_hash, payment_kind) =
713                    get_preimage_and_payment_hash(&details.kind);
714                let direction = match details.direction {
715                    PaymentDirection::Outbound => {
716                        fedimint_gateway_common::PaymentDirection::Outbound
717                    }
718                    PaymentDirection::Inbound => fedimint_gateway_common::PaymentDirection::Inbound,
719                };
720                let status = match details.status {
721                    PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
722                    PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
723                    PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
724                };
725                fedimint_gateway_common::PaymentDetails {
726                    payment_hash,
727                    preimage: preimage.map(|p| p.to_string()),
728                    payment_kind,
729                    amount: Amount::from_msats(
730                        details
731                            .amount_msat
732                            .expect("amountless invoices are not supported"),
733                    ),
734                    direction,
735                    status,
736                    timestamp_secs: details.latest_update_timestamp,
737                }
738            })
739            .collect::<Vec<_>>();
740        Ok(ListTransactionsResponse { transactions })
741    }
742
743    fn create_offer(
744        &self,
745        amount: Option<Amount>,
746        description: Option<String>,
747        expiry_secs: Option<u32>,
748        quantity: Option<u64>,
749    ) -> Result<String, LightningRpcError> {
750        let description = description.unwrap_or_default();
751        let offer = if let Some(amount) = amount {
752            self.node
753                .bolt12_payment()
754                .receive(amount.msats, &description, expiry_secs, quantity)
755                .map_err(|err| LightningRpcError::Bolt12Error {
756                    failure_reason: err.to_string(),
757                })?
758        } else {
759            self.node
760                .bolt12_payment()
761                .receive_variable_amount(&description, expiry_secs)
762                .map_err(|err| LightningRpcError::Bolt12Error {
763                    failure_reason: err.to_string(),
764                })?
765        };
766
767        Ok(offer.to_string())
768    }
769
770    async fn pay_offer(
771        &self,
772        offer: String,
773        quantity: Option<u64>,
774        amount: Option<Amount>,
775        payer_note: Option<String>,
776    ) -> Result<Preimage, LightningRpcError> {
777        let offer = Offer::from_str(&offer).map_err(|_| LightningRpcError::Bolt12Error {
778            failure_reason: "Failed to parse Bolt12 Offer".to_string(),
779        })?;
780
781        let _offer_lock_guard = self
782            .outbound_offer_lock_pool
783            .blocking_lock(LdkOfferId(offer.id()));
784
785        let payment_id = if let Some(amount) = amount {
786            self.node
787                .bolt12_payment()
788                .send_using_amount(&offer, amount.msats, quantity, payer_note)
789                .map_err(|err| LightningRpcError::Bolt12Error {
790                    failure_reason: err.to_string(),
791                })?
792        } else {
793            self.node
794                .bolt12_payment()
795                .send(&offer, quantity, payer_note)
796                .map_err(|err| LightningRpcError::Bolt12Error {
797                    failure_reason: err.to_string(),
798                })?
799        };
800
801        loop {
802            if let Some(payment_details) = self.node.payment(&payment_id) {
803                match payment_details.status {
804                    PaymentStatus::Pending => {}
805                    PaymentStatus::Succeeded => match payment_details.kind {
806                        PaymentKind::Bolt12Offer {
807                            preimage: Some(preimage),
808                            ..
809                        } => {
810                            info!(target: LOG_LIGHTNING, offer = %offer, payment_id = %payment_id, preimage = %preimage, "Successfully paid offer");
811                            return Ok(Preimage(preimage.0));
812                        }
813                        _ => {
814                            return Err(LightningRpcError::FailedPayment {
815                                failure_reason: "Unexpected payment kind".to_string(),
816                            });
817                        }
818                    },
819                    PaymentStatus::Failed => {
820                        return Err(LightningRpcError::FailedPayment {
821                            failure_reason: "Bolt12 payment failed".to_string(),
822                        });
823                    }
824                }
825            }
826            fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
827        }
828    }
829}
830
831/// Maps LDK's `PaymentKind` to an optional preimage and an optional payment
832/// hash depending on the type of payment.
833fn get_preimage_and_payment_hash(
834    kind: &PaymentKind,
835) -> (
836    Option<Preimage>,
837    Option<sha256::Hash>,
838    fedimint_gateway_common::PaymentKind,
839) {
840    match kind {
841        PaymentKind::Bolt11 {
842            hash,
843            preimage,
844            secret: _,
845        } => (
846            preimage.map(|p| Preimage(p.0)),
847            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
848            fedimint_gateway_common::PaymentKind::Bolt11,
849        ),
850        PaymentKind::Bolt11Jit {
851            hash,
852            preimage,
853            secret: _,
854            lsp_fee_limits: _,
855            ..
856        } => (
857            preimage.map(|p| Preimage(p.0)),
858            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
859            fedimint_gateway_common::PaymentKind::Bolt11,
860        ),
861        PaymentKind::Bolt12Offer {
862            hash,
863            preimage,
864            secret: _,
865            offer_id: _,
866            payer_note: _,
867            quantity: _,
868        } => (
869            preimage.map(|p| Preimage(p.0)),
870            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
871            fedimint_gateway_common::PaymentKind::Bolt12Offer,
872        ),
873        PaymentKind::Bolt12Refund {
874            hash,
875            preimage,
876            secret: _,
877            payer_note: _,
878            quantity: _,
879        } => (
880            preimage.map(|p| Preimage(p.0)),
881            hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
882            fedimint_gateway_common::PaymentKind::Bolt12Refund,
883        ),
884        PaymentKind::Spontaneous { hash, preimage } => (
885            preimage.map(|p| Preimage(p.0)),
886            Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
887            fedimint_gateway_common::PaymentKind::Bolt11,
888        ),
889        PaymentKind::Onchain { .. } => (None, None, fedimint_gateway_common::PaymentKind::Onchain),
890    }
891}
892
893/// When a port is specified in the Esplora URL, the esplora client inside LDK
894/// node cannot connect to the lightning node when there is a trailing slash.
895/// The `SafeUrl::Display` function will always serialize the `SafeUrl` with a
896/// trailing slash, which causes the connection to fail.
897///
898/// To handle this, we explicitly construct the esplora URL when a port is
899/// specified.
900fn get_esplora_url(server_url: SafeUrl) -> anyhow::Result<String> {
901    // Esplora client cannot handle trailing slashes
902    let host = server_url
903        .host_str()
904        .ok_or(anyhow::anyhow!("Missing esplora host"))?;
905    let server_url = if let Some(port) = server_url.port() {
906        format!("{}://{}:{}", server_url.scheme(), host, port)
907    } else {
908        server_url.to_string()
909    };
910    Ok(server_url)
911}
912
913#[derive(Debug, Clone, Copy, Eq, PartialEq)]
914struct LdkOfferId(OfferId);
915
916impl std::hash::Hash for LdkOfferId {
917    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
918        state.write(&self.0.0);
919    }
920}
921
922#[derive(Debug, Copy, Clone, PartialEq, Eq)]
923pub struct UserChannelId(pub ldk_node::UserChannelId);
924
925impl PartialOrd for UserChannelId {
926    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
927        Some(self.cmp(other))
928    }
929}
930
931impl Ord for UserChannelId {
932    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
933        self.0.0.cmp(&other.0.0)
934    }
935}
936
937#[cfg(test)]
938mod tests;