Skip to main content

cdk_ldk_node/
lib.rs

1//! CDK lightning backend for ldk-node
2
3#![doc = include_str!("../README.md")]
4
5use std::net::SocketAddr;
6use std::pin::Pin;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9use std::time::Duration;
10
11use async_trait::async_trait;
12use bip39::Mnemonic;
13use cdk_common::common::FeeReserve;
14use cdk_common::payment::{self, *};
15use cdk_common::util::{hex, unix_time};
16use cdk_common::{Amount, CurrencyUnit, MeltOptions, MeltQuoteState};
17use futures::{Stream, StreamExt};
18use ldk_node::bitcoin::hashes::Hash;
19use ldk_node::bitcoin::Network;
20use ldk_node::lightning::ln::channelmanager::PaymentId;
21use ldk_node::lightning::ln::msgs::SocketAddress;
22use ldk_node::lightning::routing::router::RouteParametersConfig;
23use ldk_node::lightning_invoice::{Bolt11InvoiceDescription, Description};
24use ldk_node::lightning_types::payment::PaymentHash;
25use ldk_node::logger::{LogLevel, LogWriter};
26use ldk_node::payment::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
27use ldk_node::{Builder, Event, Node};
28use tokio_stream::wrappers::BroadcastStream;
29use tokio_util::sync::CancellationToken;
30use tracing::instrument;
31
32use crate::error::Error;
33use crate::log::StdoutLogWriter;
34
35mod error;
36mod log;
37mod web;
38
39/// Maximum time a synchronous payment request waits for an LDK terminal event
40const PAYMENT_WAIT_TIMEOUT: Duration = Duration::from_secs(10);
41/// Capacity for terminal outgoing payment notifications
42const PAYMENT_EVENT_CHANNEL_CAPACITY: usize = 64;
43
44/// CDK Lightning backend using LDK Node
45///
46/// Provides Lightning Network functionality for CDK with support for Cashu operations.
47/// Handles payment creation, processing, and event management using the Lightning Development Kit.
48#[derive(Clone)]
49pub struct CdkLdkNode {
50    inner: Arc<Node>,
51    fee_reserve: FeeReserve,
52    wait_invoice_cancel_token: CancellationToken,
53    wait_invoice_is_active: Arc<AtomicBool>,
54    sender: tokio::sync::broadcast::Sender<WaitPaymentResponse>,
55    receiver: Arc<tokio::sync::broadcast::Receiver<WaitPaymentResponse>>,
56    outgoing_payment_sender: tokio::sync::broadcast::Sender<PaymentId>,
57    events_cancel_token: CancellationToken,
58    web_addr: Option<SocketAddr>,
59}
60
61impl std::fmt::Debug for CdkLdkNode {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct("CdkLdkNode")
64            .field("fee_reserve", &self.fee_reserve)
65            .field("web_addr", &self.web_addr)
66            .finish_non_exhaustive()
67    }
68}
69
70/// Configuration for connecting to Bitcoin RPC
71///
72/// Contains the necessary connection parameters for Bitcoin Core RPC interface.
73#[derive(Debug, Clone)]
74pub struct BitcoinRpcConfig {
75    /// Bitcoin RPC server hostname or IP address
76    pub host: String,
77    /// Bitcoin RPC server port number
78    pub port: u16,
79    /// Username for Bitcoin RPC authentication
80    pub user: String,
81    /// Password for Bitcoin RPC authentication
82    pub password: String,
83}
84
85/// Source of blockchain data for the Lightning node
86///
87/// Specifies how the node should connect to the Bitcoin network to retrieve
88/// blockchain information and broadcast transactions.
89#[derive(Debug, Clone)]
90pub enum ChainSource {
91    /// Use an Esplora server for blockchain data
92    ///
93    /// Contains the URL of the Esplora server endpoint
94    Esplora(String),
95    /// Use Bitcoin Core RPC for blockchain data
96    ///
97    /// Contains the configuration for connecting to Bitcoin Core
98    BitcoinRpc(BitcoinRpcConfig),
99}
100
101/// Source of Lightning network gossip data
102///
103/// Specifies how the node should learn about the Lightning Network topology
104/// and routing information.
105#[derive(Debug, Clone)]
106pub enum GossipSource {
107    /// Learn gossip through peer-to-peer connections
108    ///
109    /// The node will connect to other Lightning nodes and exchange gossip data directly
110    P2P,
111    /// Use Rapid Gossip Sync for efficient gossip updates
112    ///
113    /// Contains the URL of the RGS server for compressed gossip data
114    RapidGossipSync(String),
115}
116/// A builder for an [`CdkLdkNode`] instance.
117#[derive(Debug)]
118pub struct CdkLdkNodeBuilder {
119    network: Network,
120    chain_source: ChainSource,
121    gossip_source: GossipSource,
122    log_dir_path: Option<String>,
123    storage_dir_path: String,
124    fee_reserve: FeeReserve,
125    listening_addresses: Vec<SocketAddress>,
126    seed: Option<Mnemonic>,
127    announcement_addresses: Option<Vec<SocketAddress>>,
128}
129
130impl CdkLdkNodeBuilder {
131    /// Creates a new builder instance.
132    pub fn new(
133        network: Network,
134        chain_source: ChainSource,
135        gossip_source: GossipSource,
136        storage_dir_path: String,
137        fee_reserve: FeeReserve,
138        listening_addresses: Vec<SocketAddress>,
139    ) -> Self {
140        Self {
141            network,
142            chain_source,
143            gossip_source,
144            storage_dir_path,
145            fee_reserve,
146            listening_addresses,
147            seed: None,
148            announcement_addresses: None,
149            log_dir_path: None,
150        }
151    }
152
153    /// Configures the [`CdkLdkNode`] to use the Mnemonic for entropy source configuration
154    pub fn with_seed(mut self, seed: Mnemonic) -> Self {
155        self.seed = Some(seed);
156        self
157    }
158    /// Configures the [`CdkLdkNode`] to use announce this address to the lightning network
159    pub fn with_announcement_address(mut self, announcement_addresses: Vec<SocketAddress>) -> Self {
160        self.announcement_addresses = Some(announcement_addresses);
161        self
162    }
163    /// Configures the [`CdkLdkNode`] to use announce this address to the lightning network
164    pub fn with_log_dir_path(mut self, log_dir_path: String) -> Self {
165        self.log_dir_path = Some(log_dir_path);
166        self
167    }
168
169    /// Builds the [`CdkLdkNode`] instance
170    ///
171    /// # Errors
172    /// Returns an error if the LDK node builder fails to create the node
173    pub fn build(self) -> Result<CdkLdkNode, Error> {
174        let mut ldk = Builder::new();
175        ldk.set_network(self.network);
176        tracing::info!("Storage dir of node is {}", self.storage_dir_path);
177        ldk.set_storage_dir_path(self.storage_dir_path);
178
179        match self.chain_source {
180            ChainSource::Esplora(esplora_url) => {
181                ldk.set_chain_source_esplora(esplora_url, None);
182            }
183            ChainSource::BitcoinRpc(BitcoinRpcConfig {
184                host,
185                port,
186                user,
187                password,
188            }) => {
189                ldk.set_chain_source_bitcoind_rpc(host, port, user, password);
190            }
191        }
192
193        match self.gossip_source {
194            GossipSource::P2P => {
195                ldk.set_gossip_source_p2p();
196            }
197            GossipSource::RapidGossipSync(rgs_url) => {
198                ldk.set_gossip_source_rgs(rgs_url);
199            }
200        }
201
202        ldk.set_listening_addresses(self.listening_addresses)?;
203        if self.log_dir_path.is_some() {
204            ldk.set_filesystem_logger(self.log_dir_path, Some(LogLevel::Info));
205        } else {
206            ldk.set_custom_logger(Arc::new(StdoutLogWriter));
207        }
208
209        ldk.set_node_alias("cdk-ldk-node".to_string())?;
210        // set the seed as bip39 entropy mnemonic
211        if let Some(seed) = self.seed {
212            ldk.set_entropy_bip39_mnemonic(seed, None);
213        }
214        // set the announcement addresses
215        if let Some(announcement_addresses) = self.announcement_addresses {
216            ldk.set_announcement_addresses(announcement_addresses)?;
217        }
218
219        let node = ldk.build()?;
220
221        tracing::info!("Creating tokio channel for payment notifications");
222        let (sender, receiver) = tokio::sync::broadcast::channel(8);
223        let (outgoing_payment_sender, _) =
224            tokio::sync::broadcast::channel(PAYMENT_EVENT_CHANNEL_CAPACITY);
225
226        let id = node.node_id();
227
228        let adr = node.announcement_addresses();
229
230        tracing::info!(
231            "Created node {} with address {:?} on network {}",
232            id,
233            adr,
234            self.network
235        );
236
237        Ok(CdkLdkNode {
238            inner: node.into(),
239            fee_reserve: self.fee_reserve,
240            wait_invoice_cancel_token: CancellationToken::new(),
241            wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
242            sender,
243            receiver: Arc::new(receiver),
244            outgoing_payment_sender,
245            events_cancel_token: CancellationToken::new(),
246            web_addr: None,
247        })
248    }
249}
250
251impl CdkLdkNode {
252    /// Set the web server address for the LDK node management interface
253    ///
254    /// # Arguments
255    /// * `addr` - Socket address for the web server. If None, no web server will be started.
256    pub fn set_web_addr(&mut self, addr: Option<SocketAddr>) {
257        self.web_addr = addr;
258    }
259
260    /// Get a default web server address using an unused port
261    ///
262    /// Returns a SocketAddr with localhost and port 0, which will cause
263    /// the system to automatically assign an available port
264    pub fn default_web_addr() -> SocketAddr {
265        SocketAddr::from(([127, 0, 0, 1], 8091))
266    }
267
268    fn make_payment_response_from_details(
269        unit: &CurrencyUnit,
270        payment_lookup_id: PaymentIdentifier,
271        payment_details: &PaymentDetails,
272    ) -> Result<MakePaymentResponse, payment::Error> {
273        let status = match payment_details.status {
274            PaymentStatus::Pending => MeltQuoteState::Pending,
275            PaymentStatus::Succeeded => MeltQuoteState::Paid,
276            PaymentStatus::Failed => MeltQuoteState::Failed,
277        };
278
279        let payment_proof = match &payment_details.kind {
280            PaymentKind::Bolt11 { preimage, .. } => preimage.map(|p| p.to_string()),
281            PaymentKind::Bolt12Offer { preimage, .. } => preimage.map(|p| p.to_string()),
282            _ => return Err(Error::UnexpectedPaymentKind.into()),
283        };
284
285        let total_spent = if status == MeltQuoteState::Paid {
286            let total_spent = payment_details
287                .amount_msat
288                .ok_or(Error::CouldNotGetAmountSpent)?
289                + payment_details.fee_paid_msat.unwrap_or_default();
290            Amount::new(total_spent, CurrencyUnit::Msat).convert_to(unit)?
291        } else {
292            Amount::new(0, unit.clone())
293        };
294
295        Ok(MakePaymentResponse {
296            payment_lookup_id,
297            payment_proof,
298            status,
299            total_spent,
300        })
301    }
302
303    fn select_bolt11_payment_details(
304        payment_details: impl IntoIterator<Item = PaymentDetails>,
305    ) -> Option<PaymentDetails> {
306        payment_details.into_iter().min_by_key(|details| {
307            let status_order = match details.status {
308                PaymentStatus::Succeeded => 0_u8,
309                PaymentStatus::Pending => 1,
310                PaymentStatus::Failed => 2,
311            };
312
313            (
314                status_order,
315                std::cmp::Reverse(details.latest_update_timestamp),
316            )
317        })
318    }
319
320    async fn wait_for_terminal_payment_event(
321        receiver: &mut tokio::sync::broadcast::Receiver<PaymentId>,
322        payment_id: PaymentId,
323    ) -> Result<(), tokio::sync::broadcast::error::RecvError> {
324        loop {
325            match receiver.recv().await {
326                Ok(completed_payment_id) if completed_payment_id == payment_id => return Ok(()),
327                Ok(_) => continue,
328                Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
329                    tracing::warn!(
330                        payment_id = %payment_id,
331                        skipped,
332                        "Terminal payment event receiver lagged; continuing to wait"
333                    );
334                }
335                Err(err) => return Err(err),
336            }
337        }
338    }
339
340    async fn wait_for_payment_terminal_status(
341        &self,
342        payment_id: PaymentId,
343        mut receiver: tokio::sync::broadcast::Receiver<PaymentId>,
344    ) -> Result<PaymentDetails, payment::Error> {
345        let payment_details = self
346            .inner
347            .payment(&payment_id)
348            .ok_or(Error::PaymentNotFound)?;
349
350        if payment_details.status != PaymentStatus::Pending {
351            return Ok(payment_details);
352        }
353
354        match tokio::time::timeout(
355            PAYMENT_WAIT_TIMEOUT,
356            Self::wait_for_terminal_payment_event(&mut receiver, payment_id),
357        )
358        .await
359        {
360            Ok(Ok(())) => {}
361            Ok(Err(err)) => {
362                tracing::warn!(
363                    payment_id = %payment_id,
364                    "Could not wait for terminal LDK payment event: {err}"
365                );
366            }
367            Err(_) => {
368                tracing::warn!(
369                    payment_id = %payment_id,
370                    "Payment did not reach a terminal state within {} seconds",
371                    PAYMENT_WAIT_TIMEOUT.as_secs()
372                );
373            }
374        }
375
376        let payment_details = self
377            .inner
378            .payment(&payment_id)
379            .ok_or(Error::PaymentNotFound)?;
380
381        if payment_details.status == PaymentStatus::Pending {
382            tracing::debug!(
383                payment_id = %payment_id,
384                "Payment remains pending after waiting for a terminal event"
385            );
386        }
387
388        Ok(payment_details)
389    }
390
391    /// Start the CDK LDK Node
392    ///
393    /// Starts the underlying LDK node and begins event processing.
394    /// Sets up event handlers to listen for Lightning events like payment received.
395    ///
396    /// # Returns
397    /// Returns `Ok(())` on successful start, error otherwise
398    ///
399    /// # Errors
400    /// Returns an error if the LDK node fails to start or event handling setup fails
401    pub fn start_ldk_node(&self) -> Result<(), Error> {
402        tracing::info!("Starting cdk-ldk node");
403        self.inner.start()?;
404        let node_config = self.inner.config();
405
406        tracing::info!("Starting node with network {}", node_config.network);
407
408        tracing::info!("Node status: {:?}", self.inner.status());
409
410        self.handle_events()?;
411
412        Ok(())
413    }
414
415    /// Start the web server for the LDK node management interface
416    ///
417    /// Starts a web server that provides a user interface for managing the LDK node.
418    /// The web interface allows users to view balances, manage channels, create invoices,
419    /// and send payments.
420    ///
421    /// # Arguments
422    /// * `web_addr` - The socket address to bind the web server to
423    ///
424    /// # Returns
425    /// Returns `Ok(())` on successful start, error otherwise
426    ///
427    /// # Errors
428    /// Returns an error if the web server fails to start
429    pub fn start_web_server(&self, web_addr: SocketAddr) -> Result<(), Error> {
430        let web_server = crate::web::WebServer::new(Arc::new(self.clone()));
431
432        tokio::spawn(async move {
433            if let Err(e) = web_server.serve(web_addr).await {
434                tracing::error!("Web server error: {}", e);
435            }
436        });
437
438        Ok(())
439    }
440
441    /// Stop the CDK LDK Node
442    ///
443    /// Gracefully stops the node by cancelling all active tasks and event handlers.
444    /// This includes:
445    /// - Cancelling the event handler task
446    /// - Cancelling any active wait_invoice streams
447    /// - Stopping the underlying LDK node
448    ///
449    /// # Returns
450    /// Returns `Ok(())` on successful shutdown, error otherwise
451    ///
452    /// # Errors
453    /// Returns an error if the underlying LDK node fails to stop
454    pub fn stop_ldk_node(&self) -> Result<(), Error> {
455        tracing::info!("Stopping CdkLdkNode");
456        // Cancel all tokio tasks
457        tracing::info!("Cancelling event handler");
458        self.events_cancel_token.cancel();
459
460        // Cancel any payment event streams
461        if self.is_payment_event_stream_active() {
462            tracing::info!("Cancelling payment event stream");
463            self.wait_invoice_cancel_token.cancel();
464        }
465
466        // Stop the LDK node
467        tracing::info!("Stopping LDK node");
468        self.inner.stop()?;
469        tracing::info!("CdkLdkNode stopped successfully");
470        Ok(())
471    }
472
473    /// Handle payment received event
474    async fn handle_payment_received(
475        node: &Arc<Node>,
476        sender: &tokio::sync::broadcast::Sender<WaitPaymentResponse>,
477        payment_id: Option<PaymentId>,
478        payment_hash: PaymentHash,
479        amount_msat: u64,
480    ) {
481        tracing::info!(
482            "Received payment for hash={} of amount={} msat",
483            payment_hash,
484            amount_msat
485        );
486
487        let payment_id = match payment_id {
488            Some(id) => id,
489            None => {
490                tracing::warn!("Received payment without payment_id");
491                return;
492            }
493        };
494
495        let payment_id_hex = hex::encode(payment_id.0);
496
497        if amount_msat == 0 {
498            tracing::warn!("Payment of no amount");
499            return;
500        }
501
502        tracing::info!(
503            "Processing payment notification: id={}, amount={} msats",
504            payment_id_hex,
505            amount_msat
506        );
507
508        let payment_details = match node.payment(&payment_id) {
509            Some(details) => details,
510            None => {
511                tracing::error!("Could not find payment details for id={}", payment_id_hex);
512                return;
513            }
514        };
515
516        let (payment_identifier, payment_id) = match payment_details.kind {
517            PaymentKind::Bolt11 { hash, .. } => {
518                (PaymentIdentifier::PaymentHash(hash.0), hash.to_string())
519            }
520            PaymentKind::Bolt12Offer { hash, offer_id, .. } => match hash {
521                Some(h) => (
522                    PaymentIdentifier::OfferId(offer_id.to_string()),
523                    h.to_string(),
524                ),
525                None => {
526                    tracing::error!("Bolt12 payment missing hash");
527                    return;
528                }
529            },
530            k => {
531                tracing::warn!("Received payment of kind {:?} which is not supported", k);
532                return;
533            }
534        };
535
536        let wait_payment_response = WaitPaymentResponse {
537            payment_identifier,
538            payment_amount: Amount::new(amount_msat, CurrencyUnit::Msat),
539            payment_id,
540        };
541
542        match sender.send(wait_payment_response) {
543            Ok(_) => tracing::info!("Successfully sent payment notification to stream"),
544            Err(err) => tracing::error!(
545                "Could not send payment received notification on channel: {}",
546                err
547            ),
548        }
549    }
550
551    /// Set up event handling for the node
552    pub fn handle_events(&self) -> Result<(), Error> {
553        let node = self.inner.clone();
554        let sender = self.sender.clone();
555        let outgoing_payment_sender = self.outgoing_payment_sender.clone();
556        let cancel_token = self.events_cancel_token.clone();
557
558        tracing::info!("Starting event handler task");
559
560        tokio::spawn(async move {
561            tracing::info!("Event handler loop started");
562            loop {
563                tokio::select! {
564                    _ = cancel_token.cancelled() => {
565                        tracing::info!("Event handler cancelled");
566                        break;
567                    }
568                    event = node.next_event_async() => {
569                        match event {
570                            Event::PaymentReceived {
571                                payment_id,
572                                payment_hash,
573                                amount_msat,
574                                custom_records: _
575                            } => {
576                                Self::handle_payment_received(
577                                    &node,
578                                    &sender,
579                                    payment_id,
580                                    payment_hash,
581                                    amount_msat
582                                ).await;
583                            }
584                            Event::PaymentSuccessful {
585                                payment_id,
586                                payment_hash,
587                                payment_preimage: _,
588                                fee_paid_msat: _,
589                            } => {
590                                tracing::info!(
591                                    payment_id = ?payment_id,
592                                    payment_hash = %payment_hash,
593                                    "LDK node payment succeeded"
594                                );
595                                if let Some(payment_id) = payment_id {
596                                    let _ = outgoing_payment_sender.send(payment_id);
597                                }
598                            }
599                            Event::PaymentFailed {
600                                payment_id,
601                                payment_hash,
602                                reason,
603                            } => {
604                                tracing::error!(
605                                    payment_id = ?payment_id,
606                                    payment_hash = ?payment_hash,
607                                    reason = ?reason,
608                                    "LDK node payment failed"
609                                );
610                                if let Some(payment_id) = payment_id {
611                                    let _ = outgoing_payment_sender.send(payment_id);
612                                }
613                            }
614                            event => {
615                                tracing::debug!("Received other ldk node event: {:?}", event);
616                            }
617                        }
618
619                        if let Err(err) = node.event_handled() {
620                            tracing::error!("Error handling node event: {}", err);
621                        } else {
622                            tracing::debug!("Successfully handled node event");
623                        }
624                    }
625                }
626            }
627            tracing::info!("Event handler loop terminated");
628        });
629
630        tracing::info!("Event handler task spawned");
631        Ok(())
632    }
633
634    /// Get Node used
635    pub fn node(&self) -> Arc<Node> {
636        Arc::clone(&self.inner)
637    }
638}
639
640/// Mint payment trait
641#[async_trait]
642impl MintPayment for CdkLdkNode {
643    type Err = payment::Error;
644
645    /// Start the payment processor
646    /// Starts the LDK node and begins event processing
647    async fn start(&self) -> Result<(), Self::Err> {
648        self.start_ldk_node().map_err(|e| {
649            tracing::error!("Failed to start CdkLdkNode: {}", e);
650            e
651        })?;
652
653        tracing::info!("CdkLdkNode payment processor started successfully");
654
655        // Start web server if configured
656        if let Some(web_addr) = self.web_addr {
657            tracing::info!("Starting LDK Node web interface on {}", web_addr);
658            self.start_web_server(web_addr).map_err(|e| {
659                tracing::error!("Failed to start web server: {}", e);
660                e
661            })?;
662        } else {
663            tracing::info!("No web server address configured, skipping web interface");
664        }
665
666        Ok(())
667    }
668
669    /// Stop the payment processor
670    /// Gracefully stops the LDK node and cancels all background tasks
671    async fn stop(&self) -> Result<(), Self::Err> {
672        self.stop_ldk_node().map_err(|e| {
673            tracing::error!("Failed to stop CdkLdkNode: {}", e);
674            e.into()
675        })
676    }
677
678    /// Base Settings
679    async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
680        let settings = SettingsResponse {
681            unit: CurrencyUnit::Msat.to_string(),
682            bolt11: Some(payment::Bolt11Settings {
683                mpp: false,
684                amountless: true,
685                invoice_description: true,
686            }),
687            bolt12: Some(payment::Bolt12Settings { amountless: true }),
688            onchain: None,
689            custom: std::collections::HashMap::new(),
690        };
691        Ok(settings)
692    }
693
694    /// Create a new invoice
695    #[instrument(skip(self))]
696    async fn create_incoming_payment_request(
697        &self,
698        options: IncomingPaymentOptions,
699    ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
700        match options {
701            IncomingPaymentOptions::Bolt11(bolt11_options) => {
702                let amount_msat: Amount = bolt11_options
703                    .amount
704                    .convert_to(&CurrencyUnit::Msat)?
705                    .into();
706                let description = bolt11_options.description.unwrap_or_default();
707                let time = match bolt11_options.unix_expiry {
708                    Some(t) => t
709                        .checked_sub(unix_time())
710                        .ok_or(payment::Error::InvalidExpiry)?,
711                    None => 36000,
712                };
713
714                let description = Bolt11InvoiceDescription::Direct(
715                    Description::new(description).map_err(|_| Error::InvalidDescription)?,
716                );
717
718                let payment = self
719                    .inner
720                    .bolt11_payment()
721                    .receive(amount_msat.into(), &description, time as u32)
722                    .map_err(Error::LdkNode)?;
723
724                let payment_hash = payment.payment_hash().to_string();
725                let payment_identifier = PaymentIdentifier::PaymentHash(
726                    hex::decode(&payment_hash)?
727                        .try_into()
728                        .map_err(|_| Error::InvalidPaymentHashLength)?,
729                );
730
731                Ok(CreateIncomingPaymentResponse {
732                    request_lookup_id: payment_identifier,
733                    request: payment.to_string(),
734                    expiry: Some(unix_time() + time),
735                    extra_json: None,
736                })
737            }
738            IncomingPaymentOptions::Bolt12(bolt12_options) => {
739                let Bolt12IncomingPaymentOptions {
740                    description,
741                    amount,
742                    unix_expiry,
743                } = *bolt12_options;
744
745                let time = unix_expiry
746                    .map(|t| {
747                        t.checked_sub(unix_time())
748                            .ok_or(payment::Error::InvalidExpiry)
749                            .map(|t| t as u32)
750                    })
751                    .transpose()?;
752
753                let offer = match amount {
754                    Some(amount) => {
755                        let amount_msat: Amount = amount.convert_to(&CurrencyUnit::Msat)?.into();
756
757                        self.inner
758                            .bolt12_payment()
759                            .receive(
760                                amount_msat.into(),
761                                &description.unwrap_or("".to_string()),
762                                time,
763                                None,
764                            )
765                            .map_err(Error::LdkNode)?
766                    }
767                    None => self
768                        .inner
769                        .bolt12_payment()
770                        .receive_variable_amount(&description.unwrap_or("".to_string()), time)
771                        .map_err(Error::LdkNode)?,
772                };
773                let payment_identifier = PaymentIdentifier::OfferId(offer.id().to_string());
774
775                Ok(CreateIncomingPaymentResponse {
776                    request_lookup_id: payment_identifier,
777                    request: offer.to_string(),
778                    expiry: unix_expiry,
779                    extra_json: None,
780                })
781            }
782            IncomingPaymentOptions::Custom(_) | IncomingPaymentOptions::Onchain(_) => {
783                Err(cdk_common::payment::Error::UnsupportedPaymentOption)
784            }
785        }
786    }
787
788    /// Get payment quote
789    /// Used to get fee and amount required for a payment request
790    #[instrument(skip_all)]
791    async fn get_payment_quote(
792        &self,
793        unit: &CurrencyUnit,
794        options: OutgoingPaymentOptions,
795    ) -> Result<PaymentQuoteResponse, Self::Err> {
796        match options {
797            cdk_common::payment::OutgoingPaymentOptions::Custom(_) => {
798                Err(cdk_common::payment::Error::UnsupportedPaymentOption)
799            }
800            OutgoingPaymentOptions::Bolt11(bolt11_options) => {
801                let bolt11 = bolt11_options.bolt11;
802
803                let amount_msat = match bolt11_options.melt_options {
804                    Some(MeltOptions::Amountless { amountless }) => {
805                        let amount_msat = amountless.amount_msat;
806
807                        if let Some(invoice_amount) = bolt11.amount_milli_satoshis() {
808                            if invoice_amount != u64::from(amount_msat) {
809                                return Err(payment::Error::AmountMismatch);
810                            }
811                        }
812
813                        amount_msat
814                    }
815                    Some(MeltOptions::Mpp { mpp }) => mpp.amount,
816                    None => bolt11
817                        .amount_milli_satoshis()
818                        .ok_or(Error::UnknownInvoiceAmount)?
819                        .into(),
820                };
821
822                let amount =
823                    Amount::new(amount_msat.into(), CurrencyUnit::Msat).convert_to(unit)?;
824
825                let relative_fee_reserve =
826                    (self.fee_reserve.percent_fee_reserve * amount.value() as f32) as u64;
827
828                let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
829
830                let fee = match relative_fee_reserve > absolute_fee_reserve {
831                    true => relative_fee_reserve,
832                    false => absolute_fee_reserve,
833                };
834
835                let payment_hash = bolt11.payment_hash().to_string();
836                let payment_hash_bytes = hex::decode(&payment_hash)?
837                    .try_into()
838                    .map_err(|_| Error::InvalidPaymentHashLength)?;
839
840                Ok(PaymentQuoteResponse {
841                    request_lookup_id: Some(PaymentIdentifier::PaymentHash(payment_hash_bytes)),
842                    amount,
843                    fee: Amount::new(fee, unit.clone()),
844                    state: MeltQuoteState::Unpaid,
845                    extra_json: None,
846                    estimated_blocks: None,
847                    fee_options: None,
848                })
849            }
850            OutgoingPaymentOptions::Bolt12(bolt12_options) => {
851                let offer = bolt12_options.offer;
852
853                let amount_msat = match bolt12_options.melt_options {
854                    Some(melt_options) => melt_options.amount_msat(),
855                    None => {
856                        let amount = offer.amount().ok_or(payment::Error::AmountMismatch)?;
857
858                        match amount {
859                            ldk_node::lightning::offers::offer::Amount::Bitcoin {
860                                amount_msats,
861                            } => amount_msats.into(),
862                            _ => return Err(payment::Error::AmountMismatch),
863                        }
864                    }
865                };
866                let amount =
867                    Amount::new(amount_msat.into(), CurrencyUnit::Msat).convert_to(unit)?;
868
869                let relative_fee_reserve =
870                    (self.fee_reserve.percent_fee_reserve * amount.value() as f32) as u64;
871
872                let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
873
874                let fee = match relative_fee_reserve > absolute_fee_reserve {
875                    true => relative_fee_reserve,
876                    false => absolute_fee_reserve,
877                };
878
879                Ok(PaymentQuoteResponse {
880                    request_lookup_id: None,
881                    amount,
882                    fee: Amount::new(fee, unit.clone()),
883                    state: MeltQuoteState::Unpaid,
884                    extra_json: None,
885                    estimated_blocks: None,
886                    fee_options: None,
887                })
888            }
889            OutgoingPaymentOptions::Onchain(_) => {
890                Err(cdk_common::payment::Error::UnsupportedPaymentOption)
891            }
892        }
893    }
894
895    /// Pay request
896    #[instrument(skip(self, options))]
897    async fn make_payment(
898        &self,
899        unit: &CurrencyUnit,
900        options: OutgoingPaymentOptions,
901    ) -> Result<MakePaymentResponse, Self::Err> {
902        match options {
903            cdk_common::payment::OutgoingPaymentOptions::Custom(_) => {
904                Err(cdk_common::payment::Error::UnsupportedPaymentOption)
905            }
906            OutgoingPaymentOptions::Bolt11(bolt11_options) => {
907                let bolt11 = bolt11_options.bolt11;
908
909                let send_params = match bolt11_options
910                    .max_fee_amount
911                    .map(|f| {
912                        f.convert_to(&CurrencyUnit::Msat)
913                            .map(|amount_msat| RouteParametersConfig {
914                                max_total_routing_fee_msat: Some(amount_msat.value()),
915                                ..Default::default()
916                            })
917                    })
918                    .transpose()
919                {
920                    Ok(params) => params,
921                    Err(err) => {
922                        tracing::error!("Failed to convert fee amount: {}", err);
923                        return Err(payment::Error::Custom(format!("Invalid fee amount: {err}")));
924                    }
925                };
926
927                // Subscribe before dispatch so an immediately completed
928                // payment cannot race ahead of the waiter.
929                let payment_event_receiver = self.outgoing_payment_sender.subscribe();
930
931                let payment_id = match bolt11_options.melt_options {
932                    Some(MeltOptions::Amountless { amountless }) => {
933                        if let Some(invoice_amount) = bolt11.amount_milli_satoshis() {
934                            if invoice_amount != u64::from(amountless.amount_msat) {
935                                return Err(payment::Error::AmountMismatch);
936                            }
937                        }
938
939                        self.inner
940                            .bolt11_payment()
941                            .send_using_amount(&bolt11, amountless.amount_msat.into(), send_params)
942                            .map_err(|err| {
943                                tracing::error!("Could not send send amountless bolt11: {}", err);
944                                Error::CouldNotSendBolt11WithoutAmount
945                            })?
946                    }
947                    None => self
948                        .inner
949                        .bolt11_payment()
950                        .send(&bolt11, send_params)
951                        .map_err(|err| {
952                            tracing::error!("Could not send bolt11 {}", err);
953                            Error::CouldNotSendBolt11
954                        })?,
955                    _ => return Err(payment::Error::UnsupportedPaymentOption),
956                };
957
958                let payment_details = self
959                    .wait_for_payment_terminal_status(payment_id, payment_event_receiver)
960                    .await?;
961
962                if payment_details.status == PaymentStatus::Failed {
963                    tracing::error!(payment_id = %payment_id, "Bolt11 payment failed");
964                }
965
966                Self::make_payment_response_from_details(
967                    unit,
968                    PaymentIdentifier::PaymentHash(bolt11.payment_hash().to_byte_array()),
969                    &payment_details,
970                )
971            }
972            OutgoingPaymentOptions::Bolt12(bolt12_options) => {
973                let offer = bolt12_options.offer;
974
975                let send_params = match bolt12_options
976                    .max_fee_amount
977                    .map(|f| {
978                        f.convert_to(&CurrencyUnit::Msat)
979                            .map(|amount_msat| RouteParametersConfig {
980                                max_total_routing_fee_msat: Some(amount_msat.value()),
981                                ..Default::default()
982                            })
983                    })
984                    .transpose()
985                {
986                    Ok(params) => params,
987                    Err(err) => {
988                        tracing::error!("Failed to convert fee amount: {}", err);
989                        return Err(payment::Error::Custom(format!("Invalid fee amount: {err}")));
990                    }
991                };
992
993                // BOLT12 payment ids are assigned by `send`, so subscribe
994                // first and filter the queued terminal events once it returns.
995                let payment_event_receiver = self.outgoing_payment_sender.subscribe();
996
997                let payment_id = match bolt12_options.melt_options {
998                    Some(MeltOptions::Amountless { amountless }) => self
999                        .inner
1000                        .bolt12_payment()
1001                        .send_using_amount(
1002                            &offer,
1003                            amountless.amount_msat.into(),
1004                            None,
1005                            None,
1006                            send_params,
1007                        )
1008                        .map_err(Error::LdkNode)?,
1009                    None => self
1010                        .inner
1011                        .bolt12_payment()
1012                        .send(&offer, None, None, send_params)
1013                        .map_err(Error::LdkNode)?,
1014                    _ => return Err(payment::Error::UnsupportedPaymentOption),
1015                };
1016
1017                let payment_details = self
1018                    .wait_for_payment_terminal_status(payment_id, payment_event_receiver)
1019                    .await?;
1020
1021                if payment_details.status == PaymentStatus::Failed {
1022                    tracing::error!(
1023                        payment_id = %payment_id,
1024                        amount_msat = ?payment_details.amount_msat,
1025                        fee_paid_msat = ?payment_details.fee_paid_msat,
1026                        payment_kind = ?payment_details.kind,
1027                        "Bolt12 payment failed"
1028                    );
1029                }
1030
1031                Self::make_payment_response_from_details(
1032                    unit,
1033                    PaymentIdentifier::PaymentId(payment_id.0),
1034                    &payment_details,
1035                )
1036            }
1037            OutgoingPaymentOptions::Onchain(_) => {
1038                Err(cdk_common::payment::Error::UnsupportedPaymentOption)
1039            }
1040        }
1041    }
1042
1043    /// Listen for invoices to be paid to the mint
1044    /// Returns a stream of request_lookup_id once invoices are paid
1045    #[instrument(skip(self))]
1046    async fn wait_payment_event(
1047        &self,
1048    ) -> Result<Pin<Box<dyn Stream<Item = cdk_common::payment::Event> + Send>>, Self::Err> {
1049        tracing::info!("Starting stream for invoices - wait_any_incoming_payment called");
1050
1051        // Set active flag to indicate stream is active
1052        self.wait_invoice_is_active.store(true, Ordering::SeqCst);
1053        tracing::debug!("wait_invoice_is_active set to true");
1054
1055        let receiver = self.receiver.clone();
1056
1057        tracing::info!("Receiver obtained successfully, creating response stream");
1058
1059        // Transform the String stream into a WaitPaymentResponse stream
1060        let response_stream = BroadcastStream::new(receiver.resubscribe());
1061
1062        // Map the stream to handle BroadcastStreamRecvError and wrap in Event
1063        let response_stream = response_stream.filter_map(|result| async move {
1064            match result {
1065                Ok(payment) => Some(cdk_common::payment::Event::PaymentReceived(payment)),
1066                Err(err) => {
1067                    tracing::warn!("Error in broadcast stream: {}", err);
1068                    None
1069                }
1070            }
1071        });
1072
1073        // Create a combined stream that also handles cancellation
1074        let cancel_token = self.wait_invoice_cancel_token.clone();
1075        let is_active = self.wait_invoice_is_active.clone();
1076
1077        let stream = Box::pin(response_stream);
1078
1079        // Set up a task to clean up when the stream is dropped
1080        tokio::spawn(async move {
1081            cancel_token.cancelled().await;
1082            tracing::info!("wait_invoice stream cancelled");
1083            is_active.store(false, Ordering::SeqCst);
1084        });
1085
1086        tracing::info!("wait_any_incoming_payment returning stream");
1087        Ok(stream)
1088    }
1089
1090    /// Is payment event stream active
1091    fn is_payment_event_stream_active(&self) -> bool {
1092        self.wait_invoice_is_active.load(Ordering::SeqCst)
1093    }
1094
1095    /// Cancel payment event stream
1096    fn cancel_payment_event_stream(&self) {
1097        self.wait_invoice_cancel_token.cancel()
1098    }
1099
1100    /// Check the status of an incoming payment
1101    async fn check_incoming_payment_status(
1102        &self,
1103        payment_identifier: &PaymentIdentifier,
1104    ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
1105        // Bolt12 offers are identified by offer id and can be paid more than
1106        // once, so collect every settled inbound payment for the offer.
1107        if let PaymentIdentifier::OfferId(offer_id) = payment_identifier {
1108            let payments = self.inner.list_payments_with_filter(|p| {
1109                p.direction == PaymentDirection::Inbound
1110                    && p.status == PaymentStatus::Succeeded
1111                    && matches!(
1112                        &p.kind,
1113                        PaymentKind::Bolt12Offer { offer_id: oid, .. } if oid.to_string() == *offer_id
1114                    )
1115            });
1116
1117            return Ok(payments
1118                .into_iter()
1119                .filter_map(|p| {
1120                    let payment_id = match &p.kind {
1121                        PaymentKind::Bolt12Offer {
1122                            hash: Some(hash), ..
1123                        } => hash.to_string(),
1124                        _ => {
1125                            tracing::warn!("Bolt12 payment for offer {} missing hash", offer_id);
1126                            return None;
1127                        }
1128                    };
1129
1130                    Some(WaitPaymentResponse {
1131                        payment_identifier: payment_identifier.clone(),
1132                        payment_amount: Amount::new(p.amount_msat?, CurrencyUnit::Msat),
1133                        payment_id,
1134                    })
1135                })
1136                .collect());
1137        }
1138
1139        let payment_id_str = match payment_identifier {
1140            PaymentIdentifier::PaymentHash(hash) => hex::encode(hash),
1141            PaymentIdentifier::CustomId(id) => id.clone(),
1142            _ => return Err(Error::UnsupportedPaymentIdentifierType.into()),
1143        };
1144
1145        let payment_id = PaymentId(
1146            hex::decode(&payment_id_str)?
1147                .try_into()
1148                .map_err(|_| Error::InvalidPaymentIdLength)?,
1149        );
1150
1151        let payment_details = self
1152            .inner
1153            .payment(&payment_id)
1154            .ok_or(Error::PaymentNotFound)?;
1155
1156        if payment_details.direction == PaymentDirection::Outbound {
1157            return Err(Error::InvalidPaymentDirection.into());
1158        }
1159
1160        let amount = if payment_details.status == PaymentStatus::Succeeded {
1161            payment_details
1162                .amount_msat
1163                .ok_or(Error::CouldNotGetPaymentAmount)?
1164        } else {
1165            return Ok(vec![]);
1166        };
1167
1168        let response = WaitPaymentResponse {
1169            payment_identifier: payment_identifier.clone(),
1170            payment_amount: Amount::new(amount, CurrencyUnit::Msat),
1171            payment_id: payment_id_str,
1172        };
1173
1174        Ok(vec![response])
1175    }
1176
1177    /// Check the status of an outgoing payment
1178    async fn check_outgoing_payment(
1179        &self,
1180        request_lookup_id: &PaymentIdentifier,
1181    ) -> Result<MakePaymentResponse, Self::Err> {
1182        let payment_details = match request_lookup_id {
1183            PaymentIdentifier::PaymentHash(id_hash) => {
1184                Self::select_bolt11_payment_details(self.inner.list_payments_with_filter(|p| {
1185                    p.direction == PaymentDirection::Outbound
1186                        && matches!(&p.kind, PaymentKind::Bolt11 { hash, .. } if &hash.0 == id_hash)
1187                }))
1188            }
1189            PaymentIdentifier::PaymentId(id) => self.inner.payment(&PaymentId(*id)),
1190            _ => {
1191                return Ok(MakePaymentResponse {
1192                    payment_lookup_id: request_lookup_id.clone(),
1193                    payment_proof: None,
1194                    status: MeltQuoteState::Unknown,
1195                    total_spent: Amount::new(0, CurrencyUnit::Msat),
1196                });
1197            }
1198        }
1199        .ok_or(Error::PaymentNotFound)?;
1200
1201        if payment_details.direction != PaymentDirection::Outbound {
1202            return Err(Error::InvalidPaymentDirection.into());
1203        }
1204
1205        Self::make_payment_response_from_details(
1206            &CurrencyUnit::Msat,
1207            request_lookup_id.clone(),
1208            &payment_details,
1209        )
1210    }
1211}
1212
1213impl Drop for CdkLdkNode {
1214    fn drop(&mut self) {
1215        tracing::info!("Drop called on CdkLdkNode");
1216        self.wait_invoice_cancel_token.cancel();
1217        tracing::debug!("Cancelled wait_invoice token in drop");
1218    }
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223    use super::*;
1224
1225    fn test_payment_details(status: PaymentStatus, amount_msat: Option<u64>) -> PaymentDetails {
1226        PaymentDetails {
1227            id: PaymentId([2; 32]),
1228            kind: PaymentKind::Bolt11 {
1229                hash: PaymentHash([1; 32]),
1230                preimage: None,
1231                secret: None,
1232            },
1233            amount_msat,
1234            fee_paid_msat: None,
1235            direction: PaymentDirection::Outbound,
1236            status,
1237            latest_update_timestamp: 0,
1238        }
1239    }
1240
1241    fn test_payment_details_with_id(
1242        id: [u8; 32],
1243        status: PaymentStatus,
1244        latest_update_timestamp: u64,
1245    ) -> PaymentDetails {
1246        PaymentDetails {
1247            id: PaymentId(id),
1248            latest_update_timestamp,
1249            ..test_payment_details(status, None)
1250        }
1251    }
1252
1253    #[test]
1254    fn failed_payment_response_does_not_require_amount() {
1255        let details = test_payment_details(PaymentStatus::Failed, None);
1256
1257        let response = CdkLdkNode::make_payment_response_from_details(
1258            &CurrencyUnit::Msat,
1259            PaymentIdentifier::PaymentId([2; 32]),
1260            &details,
1261        )
1262        .expect("failed payment details should map without amount");
1263
1264        assert_eq!(response.status, MeltQuoteState::Failed);
1265        assert_eq!(response.total_spent, Amount::new(0, CurrencyUnit::Msat));
1266    }
1267
1268    #[test]
1269    fn pending_payment_response_does_not_require_amount() {
1270        let details = test_payment_details(PaymentStatus::Pending, None);
1271
1272        let response = CdkLdkNode::make_payment_response_from_details(
1273            &CurrencyUnit::Msat,
1274            PaymentIdentifier::PaymentId([2; 32]),
1275            &details,
1276        )
1277        .expect("pending payment details should map without amount");
1278
1279        assert_eq!(response.status, MeltQuoteState::Pending);
1280        assert_eq!(response.total_spent, Amount::new(0, CurrencyUnit::Msat));
1281    }
1282
1283    #[test]
1284    fn paid_payment_response_requires_amount() {
1285        let details = test_payment_details(PaymentStatus::Succeeded, None);
1286
1287        let err = CdkLdkNode::make_payment_response_from_details(
1288            &CurrencyUnit::Msat,
1289            PaymentIdentifier::PaymentId([2; 32]),
1290            &details,
1291        )
1292        .expect_err("paid payment details without amount should fail");
1293
1294        assert!(matches!(err, payment::Error::Lightning(_)));
1295    }
1296
1297    #[test]
1298    fn bolt11_payment_selection_prefers_pending_over_failed() {
1299        let failed = test_payment_details_with_id([1; 32], PaymentStatus::Failed, 2);
1300        let pending = test_payment_details_with_id([2; 32], PaymentStatus::Pending, 1);
1301
1302        let selected = CdkLdkNode::select_bolt11_payment_details([failed, pending])
1303            .expect("payment details should be selected");
1304
1305        assert_eq!(selected.id, PaymentId([2; 32]));
1306        assert_eq!(selected.status, PaymentStatus::Pending);
1307    }
1308
1309    #[test]
1310    fn bolt11_payment_selection_prefers_succeeded_over_pending() {
1311        let pending = test_payment_details_with_id([1; 32], PaymentStatus::Pending, 2);
1312        let succeeded = PaymentDetails {
1313            amount_msat: Some(1000),
1314            ..test_payment_details_with_id([2; 32], PaymentStatus::Succeeded, 1)
1315        };
1316
1317        let selected = CdkLdkNode::select_bolt11_payment_details([pending, succeeded])
1318            .expect("payment details should be selected");
1319
1320        assert_eq!(selected.id, PaymentId([2; 32]));
1321        assert_eq!(selected.status, PaymentStatus::Succeeded);
1322    }
1323
1324    #[test]
1325    fn bolt11_payment_selection_uses_latest_failed_when_all_failed() {
1326        let older_failed = test_payment_details_with_id([1; 32], PaymentStatus::Failed, 1);
1327        let newer_failed = test_payment_details_with_id([2; 32], PaymentStatus::Failed, 2);
1328
1329        let selected = CdkLdkNode::select_bolt11_payment_details([older_failed, newer_failed])
1330            .expect("payment details should be selected");
1331
1332        assert_eq!(selected.id, PaymentId([2; 32]));
1333        assert_eq!(selected.status, PaymentStatus::Failed);
1334    }
1335
1336    #[tokio::test]
1337    async fn terminal_payment_event_wait_ignores_other_payments() {
1338        let (sender, mut receiver) = tokio::sync::broadcast::channel(4);
1339        let payment_id = PaymentId([2; 32]);
1340
1341        // Queue both events before entering the wait to exercise the race where
1342        // LDK completes immediately after dispatch returns.
1343        sender
1344            .send(PaymentId([1; 32]))
1345            .expect("receiver should be subscribed");
1346        sender
1347            .send(payment_id)
1348            .expect("receiver should be subscribed");
1349
1350        CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, payment_id)
1351            .await
1352            .expect("matching terminal event should wake the waiter");
1353    }
1354
1355    #[tokio::test]
1356    async fn terminal_payment_event_wait_recovers_from_lagged_channel() {
1357        let (sender, mut receiver) = tokio::sync::broadcast::channel(2);
1358        let payment_id = PaymentId([3; 32]);
1359
1360        sender
1361            .send(PaymentId([1; 32]))
1362            .expect("receiver should be subscribed");
1363        sender
1364            .send(PaymentId([2; 32]))
1365            .expect("receiver should be subscribed");
1366        sender
1367            .send(PaymentId([4; 32]))
1368            .expect("receiver should be subscribed");
1369        sender
1370            .send(payment_id)
1371            .expect("receiver should be subscribed");
1372
1373        CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, payment_id)
1374            .await
1375            .expect("receiver lag should not prevent a matching event from waking the waiter");
1376    }
1377
1378    #[tokio::test]
1379    async fn terminal_payment_event_wait_reports_closed_channel() {
1380        let (sender, mut receiver) = tokio::sync::broadcast::channel(1);
1381        drop(sender);
1382
1383        let err = CdkLdkNode::wait_for_terminal_payment_event(&mut receiver, PaymentId([2; 32]))
1384            .await
1385            .expect_err("a closed event channel should stop the wait");
1386
1387        assert!(matches!(
1388            err,
1389            tokio::sync::broadcast::error::RecvError::Closed
1390        ));
1391    }
1392}