1use std::fmt::{self, Display};
2use std::str::FromStr;
3use std::sync::Arc;
4use std::time::{Duration, UNIX_EPOCH};
5
6use anyhow::ensure;
7use async_trait::async_trait;
8use bitcoin::hashes::{Hash, sha256};
9use fedimint_core::encoding::Encodable;
10use fedimint_core::task::{TaskGroup, sleep};
11use fedimint_core::util::FmtCompact;
12use fedimint_core::{Amount, BitcoinAmountOrAll, crit, secp256k1};
13use fedimint_gateway_common::{
14 ListTransactionsResponse, PaymentDetails, PaymentDirection, PaymentKind,
15};
16use fedimint_ln_common::PrunedInvoice;
17use fedimint_ln_common::contracts::Preimage;
18use fedimint_ln_common::route_hints::{RouteHint, RouteHintHop};
19use fedimint_logging::LOG_LIGHTNING;
20use hex::ToHex;
21use secp256k1::PublicKey;
22use tokio::sync::mpsc;
23use tokio_stream::wrappers::ReceiverStream;
24use tonic_lnd::invoicesrpc::lookup_invoice_msg::InvoiceRef;
25use tonic_lnd::invoicesrpc::{
26 AddHoldInvoiceRequest, CancelInvoiceMsg, LookupInvoiceMsg, SettleInvoiceMsg,
27 SubscribeSingleInvoiceRequest,
28};
29use tonic_lnd::lnrpc::channel_point::FundingTxid;
30use tonic_lnd::lnrpc::failure::FailureCode;
31use tonic_lnd::lnrpc::invoice::InvoiceState;
32use tonic_lnd::lnrpc::payment::PaymentStatus;
33use tonic_lnd::lnrpc::{
34 ChanInfoRequest, ChannelBalanceRequest, ChannelPoint, CloseChannelRequest, ConnectPeerRequest,
35 GetInfoRequest, Invoice, InvoiceSubscription, LightningAddress, ListChannelsRequest,
36 ListInvoiceRequest, ListPaymentsRequest, ListPeersRequest, OpenChannelRequest,
37 SendCoinsRequest, WalletBalanceRequest,
38};
39use tonic_lnd::routerrpc::{
40 CircuitKey, ForwardHtlcInterceptResponse, ResolveHoldForwardAction, SendPaymentRequest,
41 TrackPaymentRequest,
42};
43use tonic_lnd::tonic::Code;
44use tonic_lnd::walletrpc::AddrRequest;
45use tonic_lnd::{Client as LndClient, connect};
46use tracing::{debug, info, trace, warn};
47
48use super::{
49 ChannelInfo, ILnRpcClient, LightningRpcError, ListActiveChannelsResponse,
50 MAX_LIGHTNING_RETRIES, RouteHtlcStream,
51};
52use crate::{
53 CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, CreateInvoiceRequest,
54 CreateInvoiceResponse, GetBalancesResponse, GetInvoiceRequest, GetInvoiceResponse,
55 GetLnOnchainAddressResponse, GetNodeInfoResponse, GetRouteHintsResponse,
56 InterceptPaymentRequest, InterceptPaymentResponse, InvoiceDescription, OpenChannelResponse,
57 PayInvoiceResponse, PaymentAction, SendOnchainRequest, SendOnchainResponse,
58};
59
60type HtlcSubscriptionSender = mpsc::Sender<InterceptPaymentRequest>;
61
62const LND_PAYMENT_TIMEOUT_SECONDS: i32 = 180;
63
64#[derive(Clone)]
65pub struct GatewayLndClient {
66 address: String,
68 tls_cert: String,
69 macaroon: String,
70 lnd_sender: Option<mpsc::Sender<ForwardHtlcInterceptResponse>>,
71}
72
73impl GatewayLndClient {
74 pub fn new(
75 address: String,
76 tls_cert: String,
77 macaroon: String,
78 lnd_sender: Option<mpsc::Sender<ForwardHtlcInterceptResponse>>,
79 ) -> Self {
80 info!(
81 target: LOG_LIGHTNING,
82 address = %address,
83 tls_cert_path = %tls_cert,
84 macaroon = %macaroon,
85 "Gateway configured to connect to LND LnRpcClient",
86 );
87 GatewayLndClient {
88 address,
89 tls_cert,
90 macaroon,
91 lnd_sender,
92 }
93 }
94
95 async fn connect(&self) -> Result<LndClient, LightningRpcError> {
96 let mut retries = 0;
97 let client = loop {
98 if retries >= MAX_LIGHTNING_RETRIES {
99 return Err(LightningRpcError::FailedToConnect);
100 }
101
102 retries += 1;
103
104 match connect(
105 self.address.clone(),
106 self.tls_cert.clone(),
107 self.macaroon.clone(),
108 )
109 .await
110 {
111 Ok(client) => break client,
112 Err(err) => {
113 debug!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Couldn't connect to LND, retrying in 1 second...");
114 sleep(Duration::from_secs(1)).await;
115 }
116 }
117 };
118
119 Ok(client)
120 }
121
122 async fn spawn_lnv2_hold_invoice_subscription(
127 &self,
128 task_group: &TaskGroup,
129 gateway_sender: HtlcSubscriptionSender,
130 payment_hash: Vec<u8>,
131 ) -> Result<(), LightningRpcError> {
132 let mut client = self.connect().await?;
133
134 let self_copy = self.clone();
135 let r_hash = payment_hash.clone();
136 task_group.spawn("LND HOLD Invoice Subscription", |handle| async move {
137 let future_stream =
138 client
139 .invoices()
140 .subscribe_single_invoice(SubscribeSingleInvoiceRequest {
141 r_hash: r_hash.clone(),
142 });
143
144 let mut hold_stream = tokio::select! {
145 stream = future_stream => {
146 match stream {
147 Ok(stream) => stream.into_inner(),
148 Err(err) => {
149 crit!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to subscribe to hold invoice updates");
150 return;
151 }
152 }
153 },
154 () = handle.make_shutdown_rx() => {
155 info!(target: LOG_LIGHTNING, "LND HOLD Invoice Subscription received shutdown signal");
156 return;
157 }
158 };
159
160 while let Some(hold) = tokio::select! {
161 () = handle.make_shutdown_rx() => {
162 None
163 }
164 hold_update = hold_stream.message() => {
165 match hold_update {
166 Ok(hold) => hold,
167 Err(err) => {
168 crit!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Error received over hold invoice update stream");
169 None
170 }
171 }
172 }
173 } {
174 debug!(
175 target: LOG_LIGHTNING,
176 payment_hash = %PrettyPaymentHash(&r_hash),
177 state = %hold.state,
178 "LND HOLD Invoice Update",
179 );
180
181 if hold.state() == InvoiceState::Accepted {
182 let intercept = InterceptPaymentRequest {
183 payment_hash: Hash::from_slice(&hold.r_hash.clone())
184 .expect("Failed to convert to Hash"),
185 amount_msat: hold.amt_paid_msat as u64,
186 expiry: hold.expiry as u32,
189 short_channel_id: Some(0),
190 incoming_chan_id: 0,
191 htlc_id: 0,
192 };
193
194 match gateway_sender.send(intercept).await {
195 Ok(()) => {}
196 Err(err) => {
197 warn!(
198 target: LOG_LIGHTNING,
199 err = %err.fmt_compact(),
200 "Hold Invoice Subscription failed to send Intercept to gateway"
201 );
202 let _ = self_copy.cancel_hold_invoice(hold.r_hash).await;
203 }
204 }
205 }
206 }
207 });
208
209 Ok(())
210 }
211
212 async fn spawn_lnv2_invoice_subscription(
218 &self,
219 task_group: &TaskGroup,
220 gateway_sender: HtlcSubscriptionSender,
221 ) -> Result<(), LightningRpcError> {
222 let mut client = self.connect().await?;
223
224 let add_index = client
226 .lightning()
227 .list_invoices(ListInvoiceRequest {
228 pending_only: true,
229 index_offset: 0,
230 num_max_invoices: u64::MAX,
231 reversed: false,
232 })
233 .await
234 .map_err(|status| {
235 warn!(target: LOG_LIGHTNING, status = %status, "Failed to list all invoices");
236 LightningRpcError::FailedToRouteHtlcs {
237 failure_reason: "Failed to list all invoices".to_string(),
238 }
239 })?
240 .into_inner()
241 .first_index_offset;
242
243 let self_copy = self.clone();
244 let hold_group = task_group.make_subgroup();
245 task_group.spawn("LND Invoice Subscription", move |handle| async move {
246 let future_stream = client.lightning().subscribe_invoices(InvoiceSubscription {
247 add_index,
248 settle_index: u64::MAX, });
250 let mut invoice_stream = tokio::select! {
251 stream = future_stream => {
252 match stream {
253 Ok(stream) => stream.into_inner(),
254 Err(err) => {
255 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to subscribe to all invoice updates");
256 return;
257 }
258 }
259 },
260 () = handle.make_shutdown_rx() => {
261 info!(target: LOG_LIGHTNING, "LND Invoice Subscription received shutdown signal");
262 return;
263 }
264 };
265
266 info!(target: LOG_LIGHTNING, "LND Invoice Subscription: starting to process invoice updates");
267 while let Some(invoice) = tokio::select! {
268 () = handle.make_shutdown_rx() => {
269 info!(target: LOG_LIGHTNING, "LND Invoice Subscription task received shutdown signal");
270 None
271 }
272 invoice_update = invoice_stream.message() => {
273 match invoice_update {
274 Ok(invoice) => invoice,
275 Err(err) => {
276 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Error received over invoice update stream");
277 None
278 }
279 }
280 }
281 } {
282 let payment_hash = invoice.r_hash.clone();
287
288 debug!(
289 target: LOG_LIGHTNING,
290 payment_hash = %PrettyPaymentHash(&payment_hash),
291 state = %invoice.state,
292 "LND HOLD Invoice Update",
293 );
294
295 if invoice.r_preimage.is_empty() && invoice.state() == InvoiceState::Open {
296 info!(
297 target: LOG_LIGHTNING,
298 payment_hash = %PrettyPaymentHash(&payment_hash),
299 "Monitoring new LNv2 invoice",
300 );
301 if let Err(err) = self_copy
302 .spawn_lnv2_hold_invoice_subscription(
303 &hold_group,
304 gateway_sender.clone(),
305 payment_hash.clone(),
306 )
307 .await
308 {
309 warn!(
310 target: LOG_LIGHTNING,
311 err = %err.fmt_compact(),
312 payment_hash = %PrettyPaymentHash(&payment_hash),
313 "Failed to spawn HOLD invoice subscription task",
314 );
315 }
316 }
317 }
318 });
319
320 Ok(())
321 }
322
323 async fn spawn_lnv1_htlc_interceptor(
327 &self,
328 task_group: &TaskGroup,
329 lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
330 lnd_rx: mpsc::Receiver<ForwardHtlcInterceptResponse>,
331 gateway_sender: HtlcSubscriptionSender,
332 ) -> Result<(), LightningRpcError> {
333 let mut client = self.connect().await?;
334
335 client
338 .lightning()
339 .get_info(GetInfoRequest {})
340 .await
341 .map_err(|status| LightningRpcError::FailedToGetNodeInfo {
342 failure_reason: format!("Failed to get node info {status:?}"),
343 })?;
344
345 task_group.spawn("LND HTLC Subscription", |handle| async move {
346 let future_stream = client
347 .router()
348 .htlc_interceptor(ReceiverStream::new(lnd_rx));
349 let mut htlc_stream = tokio::select! {
350 stream = future_stream => {
351 match stream {
352 Ok(stream) => stream.into_inner(),
353 Err(e) => {
354 crit!(target: LOG_LIGHTNING, err = ?e, "Failed to establish htlc stream");
355 return;
356 }
357 }
358 },
359 () = handle.make_shutdown_rx() => {
360 info!(target: LOG_LIGHTNING, "LND HTLC Subscription received shutdown signal while trying to intercept HTLC stream, exiting...");
361 return;
362 }
363 };
364
365 debug!(target: LOG_LIGHTNING, "LND HTLC Subscription: starting to process stream");
366 while let Some(htlc) = tokio::select! {
375 () = handle.make_shutdown_rx() => {
376 info!(target: LOG_LIGHTNING, "LND HTLC Subscription task received shutdown signal");
377 None
378 }
379 htlc_message = htlc_stream.message() => {
380 match htlc_message {
381 Ok(htlc) => htlc,
382 Err(err) => {
383 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Error received over HTLC stream");
384 None
385 }
386 }}
387 } {
388 trace!(target: LOG_LIGHTNING, ?htlc, "LND Handling HTLC");
389
390 if htlc.incoming_circuit_key.is_none() {
391 warn!(target: LOG_LIGHTNING, "Cannot route htlc with None incoming_circuit_key");
392 continue;
393 }
394
395 let incoming_circuit_key = htlc.incoming_circuit_key.unwrap();
396
397 let intercept = InterceptPaymentRequest {
399 payment_hash: Hash::from_slice(&htlc.payment_hash).expect("Failed to convert payment Hash"),
400 amount_msat: htlc.outgoing_amount_msat,
401 expiry: htlc.incoming_expiry,
402 short_channel_id: Some(htlc.outgoing_requested_chan_id),
403 incoming_chan_id: incoming_circuit_key.chan_id,
404 htlc_id: incoming_circuit_key.htlc_id,
405 };
406
407 match gateway_sender.send(intercept).await {
408 Ok(()) => {}
409 Err(err) => {
410 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to send HTLC to gatewayd for processing");
411 let _ = Self::cancel_htlc(incoming_circuit_key, lnd_sender.clone())
412 .await
413 .map_err(|err| {
414 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to cancel HTLC");
415 });
416 }
417 }
418 }
419 });
420
421 Ok(())
422 }
423
424 async fn spawn_interceptor(
426 &self,
427 task_group: &TaskGroup,
428 lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
429 lnd_rx: mpsc::Receiver<ForwardHtlcInterceptResponse>,
430 gateway_sender: HtlcSubscriptionSender,
431 ) -> Result<(), LightningRpcError> {
432 self.spawn_lnv1_htlc_interceptor(task_group, lnd_sender, lnd_rx, gateway_sender.clone())
433 .await?;
434
435 self.spawn_lnv2_invoice_subscription(task_group, gateway_sender)
436 .await?;
437
438 Ok(())
439 }
440
441 async fn cancel_htlc(
442 key: CircuitKey,
443 lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
444 ) -> Result<(), LightningRpcError> {
445 let response = ForwardHtlcInterceptResponse {
447 incoming_circuit_key: Some(key),
448 action: ResolveHoldForwardAction::Fail.into(),
449 preimage: vec![],
450 failure_message: vec![],
451 failure_code: FailureCode::TemporaryChannelFailure.into(),
452 };
453 Self::send_lnd_response(lnd_sender, response).await
454 }
455
456 async fn send_lnd_response(
457 lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
458 response: ForwardHtlcInterceptResponse,
459 ) -> Result<(), LightningRpcError> {
460 lnd_sender.send(response).await.map_err(|send_error| {
462 LightningRpcError::FailedToCompleteHtlc {
463 failure_reason: format!(
464 "Failed to send ForwardHtlcInterceptResponse to LND {send_error:?}"
465 ),
466 }
467 })
468 }
469
470 async fn lookup_payment(
471 &self,
472 payment_hash: Vec<u8>,
473 client: &mut LndClient,
474 ) -> Result<Option<String>, LightningRpcError> {
475 loop {
478 let payments = client
479 .router()
480 .track_payment_v2(TrackPaymentRequest {
481 payment_hash: payment_hash.clone(),
482 no_inflight_updates: true,
483 })
484 .await;
485
486 match payments {
487 Ok(payments) => {
488 if let Some(payment) =
490 payments.into_inner().message().await.map_err(|status| {
491 LightningRpcError::FailedPayment {
492 failure_reason: status.message().to_string(),
493 }
494 })?
495 {
496 if payment.status() == PaymentStatus::Succeeded {
497 return Ok(Some(payment.payment_preimage));
498 }
499
500 let failure_reason = payment.failure_reason();
501 return Err(LightningRpcError::FailedPayment {
502 failure_reason: format!("{failure_reason:?}"),
503 });
504 }
505 }
506 Err(err) => {
507 if err.code() == Code::NotFound {
510 return Ok(None);
511 }
512
513 warn!(
514 target: LOG_LIGHTNING,
515 payment_hash = %PrettyPaymentHash(&payment_hash),
516 err = %err.fmt_compact(),
517 "Could not get the status of payment. Trying again in 5 seconds"
518 );
519 sleep(Duration::from_secs(5)).await;
520 }
521 }
522 }
523 }
524
525 async fn settle_hold_invoice(
529 &self,
530 payment_hash: Vec<u8>,
531 preimage: Preimage,
532 ) -> Result<(), LightningRpcError> {
533 let mut client = self.connect().await?;
534 let invoice = client
535 .invoices()
536 .lookup_invoice_v2(LookupInvoiceMsg {
537 invoice_ref: Some(InvoiceRef::PaymentHash(payment_hash.clone())),
538 lookup_modifier: 0,
539 })
540 .await
541 .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
542 failure_reason: "Hold invoice does not exist".to_string(),
543 })?
544 .into_inner();
545
546 let state = invoice.state();
547 if state != InvoiceState::Accepted {
548 warn!(
549 target: LOG_LIGHTNING,
550 state = invoice.state,
551 payment_hash = %PrettyPaymentHash(&payment_hash),
552 "HOLD invoice state is not accepted",
553 );
554 return Err(LightningRpcError::FailedToCompleteHtlc {
555 failure_reason: "HOLD invoice state is not accepted".to_string(),
556 });
557 }
558
559 client
560 .invoices()
561 .settle_invoice(SettleInvoiceMsg {
562 preimage: preimage.0.to_vec(),
563 })
564 .await
565 .map_err(|err| {
566 warn!(
567 target: LOG_LIGHTNING,
568 err = %err.fmt_compact(),
569 payment_hash = %PrettyPaymentHash(&payment_hash),
570 "Failed to settle HOLD invoice",
571 );
572 LightningRpcError::FailedToCompleteHtlc {
573 failure_reason: "Failed to settle HOLD invoice".to_string(),
574 }
575 })?;
576
577 Ok(())
578 }
579
580 async fn cancel_hold_invoice(&self, payment_hash: Vec<u8>) -> Result<(), LightningRpcError> {
584 let mut client = self.connect().await?;
585 let invoice = client
586 .invoices()
587 .lookup_invoice_v2(LookupInvoiceMsg {
588 invoice_ref: Some(InvoiceRef::PaymentHash(payment_hash.clone())),
589 lookup_modifier: 0,
590 })
591 .await
592 .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
593 failure_reason: "Hold invoice does not exist".to_string(),
594 })?
595 .into_inner();
596
597 let state = invoice.state();
598 if state != InvoiceState::Open {
599 warn!(
600 target: LOG_LIGHTNING,
601 state = %invoice.state,
602 payment_hash = %PrettyPaymentHash(&payment_hash),
603 "Trying to cancel HOLD invoice that is not OPEN",
604 );
605 }
606
607 client
608 .invoices()
609 .cancel_invoice(CancelInvoiceMsg {
610 payment_hash: payment_hash.clone(),
611 })
612 .await
613 .map_err(|err| {
614 warn!(
615 target: LOG_LIGHTNING,
616 err = %err.fmt_compact(),
617 payment_hash = %PrettyPaymentHash(&payment_hash),
618 "Failed to cancel HOLD invoice",
619 );
620 LightningRpcError::FailedToCompleteHtlc {
621 failure_reason: "Failed to cancel HOLD invoice".to_string(),
622 }
623 })?;
624
625 Ok(())
626 }
627}
628
629impl fmt::Debug for GatewayLndClient {
630 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
631 write!(f, "LndClient")
632 }
633}
634
635#[async_trait]
636impl ILnRpcClient for GatewayLndClient {
637 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
638 let mut client = self.connect().await?;
639 let info = client
640 .lightning()
641 .get_info(GetInfoRequest {})
642 .await
643 .map_err(|status| LightningRpcError::FailedToGetNodeInfo {
644 failure_reason: format!("Failed to get node info {status:?}"),
645 })?
646 .into_inner();
647
648 let pub_key: PublicKey =
649 info.identity_pubkey
650 .parse()
651 .map_err(|e| LightningRpcError::FailedToGetNodeInfo {
652 failure_reason: format!("Failed to parse public key {e:?}"),
653 })?;
654
655 let network = match info
656 .chains
657 .first()
658 .ok_or_else(|| LightningRpcError::FailedToGetNodeInfo {
659 failure_reason: "Failed to parse node network".to_string(),
660 })?
661 .network
662 .as_str()
663 {
664 "mainnet" => "bitcoin",
667 other => other,
668 }
669 .to_string();
670
671 return Ok(GetNodeInfoResponse {
672 pub_key,
673 alias: info.alias,
674 network,
675 block_height: info.block_height,
676 synced_to_chain: info.synced_to_chain,
677 });
678 }
679
680 async fn routehints(
681 &self,
682 num_route_hints: usize,
683 ) -> Result<GetRouteHintsResponse, LightningRpcError> {
684 let mut client = self.connect().await?;
685 let mut channels = client
686 .lightning()
687 .list_channels(ListChannelsRequest {
688 active_only: true,
689 inactive_only: false,
690 public_only: false,
691 private_only: false,
692 peer: vec![],
693 })
694 .await
695 .map_err(|status| LightningRpcError::FailedToGetRouteHints {
696 failure_reason: format!("Failed to list channels {status:?}"),
697 })?
698 .into_inner()
699 .channels;
700
701 channels.sort_by(|a, b| b.remote_balance.cmp(&a.remote_balance));
703 channels.truncate(num_route_hints);
704
705 let mut route_hints: Vec<RouteHint> = vec![];
706 for chan in &channels {
707 let info = client
708 .lightning()
709 .get_chan_info(ChanInfoRequest {
710 chan_id: chan.chan_id,
711 })
712 .await
713 .map_err(|status| LightningRpcError::FailedToGetRouteHints {
714 failure_reason: format!("Failed to get channel info {status:?}"),
715 })?
716 .into_inner();
717
718 let Some(policy) = info.node1_policy.clone() else {
719 continue;
720 };
721 let src_node_id =
722 PublicKey::from_str(&chan.remote_pubkey).expect("Failed to parse pubkey");
723 let short_channel_id = chan.chan_id;
724 let base_msat = policy.fee_base_msat as u32;
725 let proportional_millionths = policy.fee_rate_milli_msat as u32;
726 let cltv_expiry_delta = policy.time_lock_delta;
727 let htlc_maximum_msat = Some(policy.max_htlc_msat);
728 let htlc_minimum_msat = Some(policy.min_htlc as u64);
729
730 let route_hint_hop = RouteHintHop {
731 src_node_id,
732 short_channel_id,
733 base_msat,
734 proportional_millionths,
735 cltv_expiry_delta: cltv_expiry_delta as u16,
736 htlc_minimum_msat,
737 htlc_maximum_msat,
738 };
739 route_hints.push(RouteHint(vec![route_hint_hop]));
740 }
741
742 Ok(GetRouteHintsResponse { route_hints })
743 }
744
745 async fn pay_private(
746 &self,
747 invoice: PrunedInvoice,
748 max_delay: u64,
749 max_fee: Amount,
750 ) -> Result<PayInvoiceResponse, LightningRpcError> {
751 let payment_hash = invoice.payment_hash.to_byte_array().to_vec();
752 info!(
753 target: LOG_LIGHTNING,
754 payment_hash = %PrettyPaymentHash(&payment_hash),
755 "LND Paying invoice",
756 );
757 let mut client = self.connect().await?;
758
759 debug!(
760 target: LOG_LIGHTNING,
761 payment_hash = %PrettyPaymentHash(&payment_hash),
762 "pay_private checking if payment for invoice exists"
763 );
764
765 let preimage: Vec<u8> = match self
767 .lookup_payment(invoice.payment_hash.to_byte_array().to_vec(), &mut client)
768 .await?
769 {
770 Some(preimage) => {
771 info!(
772 target: LOG_LIGHTNING,
773 payment_hash = %PrettyPaymentHash(&payment_hash),
774 "LND payment already exists for invoice",
775 );
776 hex::FromHex::from_hex(preimage.as_str()).map_err(|error| {
777 LightningRpcError::FailedPayment {
778 failure_reason: format!("Failed to convert preimage {error:?}"),
779 }
780 })?
781 }
782 _ => {
783 let fee_limit_msat: i64 =
787 max_fee
788 .msats
789 .try_into()
790 .map_err(|error| LightningRpcError::FailedPayment {
791 failure_reason: format!(
792 "max_fee_msat exceeds valid LND fee limit ranges {error:?}"
793 ),
794 })?;
795
796 let amt_msat = invoice.amount.msats.try_into().map_err(|error| {
797 LightningRpcError::FailedPayment {
798 failure_reason: format!("amount exceeds valid LND amount ranges {error:?}"),
799 }
800 })?;
801 let final_cltv_delta =
802 invoice.min_final_cltv_delta.try_into().map_err(|error| {
803 LightningRpcError::FailedPayment {
804 failure_reason: format!(
805 "final cltv delta exceeds valid LND range {error:?}"
806 ),
807 }
808 })?;
809 let cltv_limit =
810 max_delay
811 .try_into()
812 .map_err(|error| LightningRpcError::FailedPayment {
813 failure_reason: format!("max delay exceeds valid LND range {error:?}"),
814 })?;
815
816 let dest_features = wire_features_to_lnd_feature_vec(&invoice.destination_features)
817 .map_err(|e| LightningRpcError::FailedPayment {
818 failure_reason: e.to_string(),
819 })?;
820
821 debug!(
822 target: LOG_LIGHTNING,
823 payment_hash = %PrettyPaymentHash(&payment_hash),
824 "LND payment does not exist, will attempt to pay",
825 );
826 let payments = client
827 .router()
828 .send_payment_v2(SendPaymentRequest {
829 amt_msat,
830 dest: invoice.destination.serialize().to_vec(),
831 dest_features,
832 payment_hash: invoice.payment_hash.to_byte_array().to_vec(),
833 payment_addr: invoice.payment_secret.to_vec(),
834 route_hints: route_hints_to_lnd(&invoice.route_hints),
835 final_cltv_delta,
836 cltv_limit,
837 no_inflight_updates: false,
838 timeout_seconds: LND_PAYMENT_TIMEOUT_SECONDS,
839 fee_limit_msat,
840 ..Default::default()
841 })
842 .await
843 .map_err(|status| {
844 warn!(
845 target: LOG_LIGHTNING,
846 status = %status,
847 payment_hash = %PrettyPaymentHash(&payment_hash),
848 "LND payment request failed",
849 );
850 LightningRpcError::FailedPayment {
851 failure_reason: format!("Failed to make outgoing payment {status:?}"),
852 }
853 })?;
854
855 debug!(
856 target: LOG_LIGHTNING,
857 payment_hash = %PrettyPaymentHash(&payment_hash),
858 "LND payment request sent, waiting for payment status...",
859 );
860 let mut messages = payments.into_inner();
861 loop {
862 match messages.message().await.map_err(|error| {
863 LightningRpcError::FailedPayment {
864 failure_reason: format!("Failed to get payment status {error:?}"),
865 }
866 }) {
867 Ok(Some(payment)) if payment.status() == PaymentStatus::Succeeded => {
868 info!(
869 target: LOG_LIGHTNING,
870 payment_hash = %PrettyPaymentHash(&payment_hash),
871 "LND payment succeeded for invoice",
872 );
873 break hex::FromHex::from_hex(payment.payment_preimage.as_str())
874 .map_err(|error| LightningRpcError::FailedPayment {
875 failure_reason: format!("Failed to convert preimage {error:?}"),
876 })?;
877 }
878 Ok(Some(payment)) if payment.status() == PaymentStatus::InFlight => {
879 debug!(
880 target: LOG_LIGHTNING,
881 payment_hash = %PrettyPaymentHash(&payment_hash),
882 "LND payment is inflight",
883 );
884 continue;
885 }
886 Ok(Some(payment)) => {
887 warn!(
888 target: LOG_LIGHTNING,
889 payment_hash = %PrettyPaymentHash(&payment_hash),
890 status = %payment.status,
891 "LND payment failed",
892 );
893 let failure_reason = payment.failure_reason();
894 return Err(LightningRpcError::FailedPayment {
895 failure_reason: format!("{failure_reason:?}"),
896 });
897 }
898 Ok(None) => {
899 warn!(
900 target: LOG_LIGHTNING,
901 payment_hash = %PrettyPaymentHash(&payment_hash),
902 "LND payment failed with no payment status",
903 );
904 return Err(LightningRpcError::FailedPayment {
905 failure_reason: format!(
906 "Failed to get payment status for payment hash {:?}",
907 invoice.payment_hash
908 ),
909 });
910 }
911 Err(err) => {
912 warn!(
913 target: LOG_LIGHTNING,
914 payment_hash = %PrettyPaymentHash(&payment_hash),
915 err = %err.fmt_compact(),
916 "LND payment failed",
917 );
918 return Err(err);
919 }
920 }
921 }
922 }
923 };
924 Ok(PayInvoiceResponse {
925 preimage: Preimage(preimage.try_into().expect("Failed to create preimage")),
926 })
927 }
928
929 fn supports_private_payments(&self) -> bool {
932 true
933 }
934
935 async fn route_htlcs<'a>(
936 self: Box<Self>,
937 task_group: &TaskGroup,
938 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
939 const CHANNEL_SIZE: usize = 100;
940
941 let (gateway_sender, gateway_receiver) =
943 mpsc::channel::<InterceptPaymentRequest>(CHANNEL_SIZE);
944
945 let (lnd_sender, lnd_rx) = mpsc::channel::<ForwardHtlcInterceptResponse>(CHANNEL_SIZE);
946
947 self.spawn_interceptor(
948 task_group,
949 lnd_sender.clone(),
950 lnd_rx,
951 gateway_sender.clone(),
952 )
953 .await?;
954 let new_client = Arc::new(Self {
955 address: self.address.clone(),
956 tls_cert: self.tls_cert.clone(),
957 macaroon: self.macaroon.clone(),
958 lnd_sender: Some(lnd_sender.clone()),
959 });
960 Ok((Box::pin(ReceiverStream::new(gateway_receiver)), new_client))
961 }
962
963 async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
964 let InterceptPaymentResponse {
965 action,
966 payment_hash,
967 incoming_chan_id,
968 htlc_id,
969 } = htlc;
970
971 let (action, preimage) = match action {
972 PaymentAction::Settle(preimage) => (ResolveHoldForwardAction::Settle, preimage),
973 PaymentAction::Cancel => (ResolveHoldForwardAction::Fail, Preimage([0; 32])),
974 PaymentAction::Forward => (ResolveHoldForwardAction::Resume, Preimage([0; 32])),
975 };
976
977 match action {
979 ResolveHoldForwardAction::Settle => {
980 if let Ok(()) = self
981 .settle_hold_invoice(payment_hash.to_byte_array().to_vec(), preimage.clone())
982 .await
983 {
984 info!(target: LOG_LIGHTNING, payment_hash = %PrettyPaymentHash(&payment_hash.consensus_encode_to_vec()), "Successfully settled HOLD invoice");
985 return Ok(());
986 }
987 }
988 _ => {
989 if let Ok(()) = self
990 .cancel_hold_invoice(payment_hash.to_byte_array().to_vec())
991 .await
992 {
993 info!(target: LOG_LIGHTNING, payment_hash = %PrettyPaymentHash(&payment_hash.consensus_encode_to_vec()), "Successfully canceled HOLD invoice");
994 return Ok(());
995 }
996 }
997 }
998
999 if let Some(lnd_sender) = self.lnd_sender.clone() {
1001 let response = ForwardHtlcInterceptResponse {
1002 incoming_circuit_key: Some(CircuitKey {
1003 chan_id: incoming_chan_id,
1004 htlc_id,
1005 }),
1006 action: action.into(),
1007 preimage: preimage.0.to_vec(),
1008 failure_message: vec![],
1009 failure_code: FailureCode::TemporaryChannelFailure.into(),
1010 };
1011
1012 Self::send_lnd_response(lnd_sender, response).await?;
1013 return Ok(());
1014 }
1015
1016 crit!("Gatewayd has not started to route HTLCs");
1017 Err(LightningRpcError::FailedToCompleteHtlc {
1018 failure_reason: "Gatewayd has not started to route HTLCs".to_string(),
1019 })
1020 }
1021
1022 async fn create_invoice(
1023 &self,
1024 create_invoice_request: CreateInvoiceRequest,
1025 ) -> Result<CreateInvoiceResponse, LightningRpcError> {
1026 let mut client = self.connect().await?;
1027 let description = create_invoice_request
1028 .description
1029 .unwrap_or(InvoiceDescription::Direct(String::new()));
1030
1031 if create_invoice_request.payment_hash.is_none() {
1032 let invoice = match description {
1033 InvoiceDescription::Direct(description) => Invoice {
1034 memo: description,
1035 value_msat: create_invoice_request.amount_msat as i64,
1036 expiry: i64::from(create_invoice_request.expiry_secs),
1037 ..Default::default()
1038 },
1039 InvoiceDescription::Hash(desc_hash) => Invoice {
1040 description_hash: desc_hash.to_byte_array().to_vec(),
1041 value_msat: create_invoice_request.amount_msat as i64,
1042 expiry: i64::from(create_invoice_request.expiry_secs),
1043 ..Default::default()
1044 },
1045 };
1046
1047 let add_invoice_response =
1048 client.lightning().add_invoice(invoice).await.map_err(|e| {
1049 LightningRpcError::FailedToGetInvoice {
1050 failure_reason: e.to_string(),
1051 }
1052 })?;
1053
1054 let invoice = add_invoice_response.into_inner().payment_request;
1055 Ok(CreateInvoiceResponse { invoice })
1056 } else {
1057 let payment_hash = create_invoice_request
1058 .payment_hash
1059 .expect("Already checked payment hash")
1060 .to_byte_array()
1061 .to_vec();
1062 let hold_invoice_request = match description {
1063 InvoiceDescription::Direct(description) => AddHoldInvoiceRequest {
1064 memo: description,
1065 hash: payment_hash.clone(),
1066 value_msat: create_invoice_request.amount_msat as i64,
1067 expiry: i64::from(create_invoice_request.expiry_secs),
1068 ..Default::default()
1069 },
1070 InvoiceDescription::Hash(desc_hash) => AddHoldInvoiceRequest {
1071 description_hash: desc_hash.to_byte_array().to_vec(),
1072 hash: payment_hash.clone(),
1073 value_msat: create_invoice_request.amount_msat as i64,
1074 expiry: i64::from(create_invoice_request.expiry_secs),
1075 ..Default::default()
1076 },
1077 };
1078
1079 let hold_invoice_response = client
1080 .invoices()
1081 .add_hold_invoice(hold_invoice_request)
1082 .await
1083 .map_err(|e| LightningRpcError::FailedToGetInvoice {
1084 failure_reason: e.to_string(),
1085 })?;
1086
1087 let invoice = hold_invoice_response.into_inner().payment_request;
1088 Ok(CreateInvoiceResponse { invoice })
1089 }
1090 }
1091
1092 async fn get_ln_onchain_address(
1093 &self,
1094 ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
1095 let mut client = self.connect().await?;
1096
1097 match client
1098 .wallet()
1099 .next_addr(AddrRequest {
1100 account: String::new(), r#type: 4, change: false,
1103 })
1104 .await
1105 {
1106 Ok(response) => Ok(GetLnOnchainAddressResponse {
1107 address: response.into_inner().addr,
1108 }),
1109 Err(e) => Err(LightningRpcError::FailedToGetLnOnchainAddress {
1110 failure_reason: format!("Failed to get funding address {e:?}"),
1111 }),
1112 }
1113 }
1114
1115 async fn send_onchain(
1116 &self,
1117 SendOnchainRequest {
1118 address,
1119 amount,
1120 fee_rate_sats_per_vbyte,
1121 }: SendOnchainRequest,
1122 ) -> Result<SendOnchainResponse, LightningRpcError> {
1123 #[allow(deprecated)]
1124 let request = match amount {
1125 BitcoinAmountOrAll::All => SendCoinsRequest {
1126 addr: address.assume_checked().to_string(),
1127 amount: 0,
1128 target_conf: 0,
1129 sat_per_vbyte: fee_rate_sats_per_vbyte,
1130 sat_per_byte: 0,
1131 send_all: true,
1132 label: String::new(),
1133 min_confs: 0,
1134 spend_unconfirmed: true,
1135 },
1136 BitcoinAmountOrAll::Amount(amount) => SendCoinsRequest {
1137 addr: address.assume_checked().to_string(),
1138 amount: amount.to_sat() as i64,
1139 target_conf: 0,
1140 sat_per_vbyte: fee_rate_sats_per_vbyte,
1141 sat_per_byte: 0,
1142 send_all: false,
1143 label: String::new(),
1144 min_confs: 0,
1145 spend_unconfirmed: true,
1146 },
1147 };
1148
1149 match self.connect().await?.lightning().send_coins(request).await {
1150 Ok(res) => Ok(SendOnchainResponse {
1151 txid: res.into_inner().txid,
1152 }),
1153 Err(e) => Err(LightningRpcError::FailedToWithdrawOnchain {
1154 failure_reason: format!("Failed to withdraw funds on-chain {e:?}"),
1155 }),
1156 }
1157 }
1158
1159 async fn open_channel(
1160 &self,
1161 crate::OpenChannelRequest {
1162 pubkey,
1163 host,
1164 channel_size_sats,
1165 push_amount_sats,
1166 }: crate::OpenChannelRequest,
1167 ) -> Result<OpenChannelResponse, LightningRpcError> {
1168 let mut client = self.connect().await?;
1169
1170 let peers = client
1171 .lightning()
1172 .list_peers(ListPeersRequest { latest_error: true })
1173 .await
1174 .map_err(|e| LightningRpcError::FailedToConnectToPeer {
1175 failure_reason: format!("Could not list peers: {e:?}"),
1176 })?
1177 .into_inner();
1178
1179 if !peers.peers.into_iter().any(|peer| {
1181 PublicKey::from_str(&peer.pub_key).expect("could not parse public key") == pubkey
1182 }) {
1183 client
1184 .lightning()
1185 .connect_peer(ConnectPeerRequest {
1186 addr: Some(LightningAddress {
1187 pubkey: pubkey.to_string(),
1188 host,
1189 }),
1190 perm: false,
1191 timeout: 10,
1192 })
1193 .await
1194 .map_err(|e| LightningRpcError::FailedToConnectToPeer {
1195 failure_reason: format!("Failed to connect to peer {e:?}"),
1196 })?;
1197 }
1198
1199 match client
1201 .lightning()
1202 .open_channel_sync(OpenChannelRequest {
1203 node_pubkey: pubkey.serialize().to_vec(),
1204 local_funding_amount: channel_size_sats.try_into().expect("u64 -> i64"),
1205 push_sat: push_amount_sats.try_into().expect("u64 -> i64"),
1206 ..Default::default()
1207 })
1208 .await
1209 {
1210 Ok(res) => Ok(OpenChannelResponse {
1211 funding_txid: match res.into_inner().funding_txid {
1212 Some(txid) => match txid {
1213 FundingTxid::FundingTxidBytes(mut bytes) => {
1214 bytes.reverse();
1215 hex::encode(bytes)
1216 }
1217 FundingTxid::FundingTxidStr(str) => str,
1218 },
1219 None => String::new(),
1220 },
1221 }),
1222 Err(e) => Err(LightningRpcError::FailedToOpenChannel {
1223 failure_reason: format!("Failed to open channel {e:?}"),
1224 }),
1225 }
1226 }
1227
1228 async fn close_channels_with_peer(
1229 &self,
1230 CloseChannelsWithPeerRequest { pubkey }: CloseChannelsWithPeerRequest,
1231 ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
1232 let mut client = self.connect().await?;
1233
1234 let channels_with_peer = client
1235 .lightning()
1236 .list_channels(ListChannelsRequest {
1237 active_only: false,
1238 inactive_only: false,
1239 public_only: false,
1240 private_only: false,
1241 peer: pubkey.serialize().to_vec(),
1242 })
1243 .await
1244 .map_err(|e| LightningRpcError::FailedToCloseChannelsWithPeer {
1245 failure_reason: format!("Failed to list channels {e:?}"),
1246 })?
1247 .into_inner()
1248 .channels;
1249
1250 for channel in &channels_with_peer {
1251 let channel_point =
1252 bitcoin::OutPoint::from_str(&channel.channel_point).map_err(|e| {
1253 LightningRpcError::FailedToCloseChannelsWithPeer {
1254 failure_reason: format!("Failed to parse channel point {e:?}"),
1255 }
1256 })?;
1257
1258 client
1259 .lightning()
1260 .close_channel(CloseChannelRequest {
1261 channel_point: Some(ChannelPoint {
1262 funding_txid: Some(
1263 tonic_lnd::lnrpc::channel_point::FundingTxid::FundingTxidBytes(
1264 <bitcoin::Txid as AsRef<[u8]>>::as_ref(&channel_point.txid)
1265 .to_vec(),
1266 ),
1267 ),
1268 output_index: channel_point.vout,
1269 }),
1270 ..Default::default()
1271 })
1272 .await
1273 .map_err(|e| LightningRpcError::FailedToCloseChannelsWithPeer {
1274 failure_reason: format!("Failed to close channel {e:?}"),
1275 })?;
1276 }
1277
1278 Ok(CloseChannelsWithPeerResponse {
1279 num_channels_closed: channels_with_peer.len() as u32,
1280 })
1281 }
1282
1283 async fn list_active_channels(&self) -> Result<ListActiveChannelsResponse, LightningRpcError> {
1284 let mut client = self.connect().await?;
1285
1286 match client
1287 .lightning()
1288 .list_channels(ListChannelsRequest {
1289 active_only: true,
1290 inactive_only: false,
1291 public_only: false,
1292 private_only: false,
1293 peer: vec![],
1294 })
1295 .await
1296 {
1297 Ok(response) => Ok(ListActiveChannelsResponse {
1298 channels: response
1299 .into_inner()
1300 .channels
1301 .into_iter()
1302 .map(|channel| {
1303 let channel_size_sats = channel.capacity.try_into().expect("i64 -> u64");
1304
1305 let local_balance_sats: u64 =
1306 channel.local_balance.try_into().expect("i64 -> u64");
1307 let local_channel_reserve_sats: u64 = match channel.local_constraints {
1308 Some(constraints) => constraints.chan_reserve_sat,
1309 None => 0,
1310 };
1311
1312 let outbound_liquidity_sats =
1313 local_balance_sats.saturating_sub(local_channel_reserve_sats);
1314
1315 let remote_balance_sats: u64 =
1316 channel.remote_balance.try_into().expect("i64 -> u64");
1317 let remote_channel_reserve_sats: u64 = match channel.remote_constraints {
1318 Some(constraints) => constraints.chan_reserve_sat,
1319 None => 0,
1320 };
1321
1322 let inbound_liquidity_sats =
1323 remote_balance_sats.saturating_sub(remote_channel_reserve_sats);
1324
1325 ChannelInfo {
1326 remote_pubkey: PublicKey::from_str(&channel.remote_pubkey)
1327 .expect("Lightning node returned invalid remote channel pubkey"),
1328 channel_size_sats,
1329 outbound_liquidity_sats,
1330 inbound_liquidity_sats,
1331 short_channel_id: channel.chan_id,
1332 }
1333 })
1334 .collect(),
1335 }),
1336 Err(e) => Err(LightningRpcError::FailedToListActiveChannels {
1337 failure_reason: format!("Failed to list active channels {e:?}"),
1338 }),
1339 }
1340 }
1341
1342 async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
1343 let mut client = self.connect().await?;
1344
1345 let wallet_balance_response = client
1346 .lightning()
1347 .wallet_balance(WalletBalanceRequest {})
1348 .await
1349 .map_err(|e| LightningRpcError::FailedToGetBalances {
1350 failure_reason: format!("Failed to get on-chain balance {e:?}"),
1351 })?
1352 .into_inner();
1353
1354 let channel_balance_response = client
1355 .lightning()
1356 .channel_balance(ChannelBalanceRequest {})
1357 .await
1358 .map_err(|e| LightningRpcError::FailedToGetBalances {
1359 failure_reason: format!("Failed to get lightning balance {e:?}"),
1360 })?
1361 .into_inner();
1362 let total_outbound = channel_balance_response.local_balance.unwrap_or_default();
1363 let unsettled_outbound = channel_balance_response
1364 .unsettled_local_balance
1365 .unwrap_or_default();
1366 let pending_outbound = channel_balance_response
1367 .pending_open_local_balance
1368 .unwrap_or_default();
1369 let lightning_balance_msats =
1370 total_outbound.msat - unsettled_outbound.msat - pending_outbound.msat;
1371
1372 let total_inbound = channel_balance_response.remote_balance.unwrap_or_default();
1373 let unsettled_inbound = channel_balance_response
1374 .unsettled_remote_balance
1375 .unwrap_or_default();
1376 let pending_inbound = channel_balance_response
1377 .pending_open_remote_balance
1378 .unwrap_or_default();
1379 let inbound_lightning_liquidity_msats =
1380 total_inbound.msat - unsettled_inbound.msat - pending_inbound.msat;
1381
1382 Ok(GetBalancesResponse {
1383 onchain_balance_sats: (wallet_balance_response.total_balance
1384 + wallet_balance_response.reserved_balance_anchor_chan)
1385 as u64,
1386 lightning_balance_msats,
1387 inbound_lightning_liquidity_msats,
1388 })
1389 }
1390
1391 async fn get_invoice(
1392 &self,
1393 get_invoice_request: GetInvoiceRequest,
1394 ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
1395 let mut client = self.connect().await?;
1396 let invoice = client
1397 .invoices()
1398 .lookup_invoice_v2(LookupInvoiceMsg {
1399 invoice_ref: Some(InvoiceRef::PaymentHash(
1400 get_invoice_request.payment_hash.consensus_encode_to_vec(),
1401 )),
1402 ..Default::default()
1403 })
1404 .await;
1405 let invoice = match invoice {
1406 Ok(invoice) => invoice.into_inner(),
1407 Err(_) => return Ok(None),
1408 };
1409 let preimage: [u8; 32] = invoice
1410 .clone()
1411 .r_preimage
1412 .try_into()
1413 .expect("Could not convert preimage");
1414 let status = match &invoice.state() {
1415 InvoiceState::Settled => fedimint_gateway_common::PaymentStatus::Succeeded,
1416 InvoiceState::Canceled => fedimint_gateway_common::PaymentStatus::Failed,
1417 _ => fedimint_gateway_common::PaymentStatus::Pending,
1418 };
1419
1420 Ok(Some(GetInvoiceResponse {
1421 preimage: Some(preimage.consensus_encode_to_hex()),
1422 payment_hash: Some(
1423 sha256::Hash::from_slice(&invoice.r_hash).expect("Could not convert payment hash"),
1424 ),
1425 amount: Amount::from_msats(invoice.value_msat as u64),
1426 created_at: UNIX_EPOCH + Duration::from_secs(invoice.creation_date as u64),
1427 status,
1428 }))
1429 }
1430
1431 async fn list_transactions(
1432 &self,
1433 start_secs: u64,
1434 end_secs: u64,
1435 ) -> Result<ListTransactionsResponse, LightningRpcError> {
1436 let mut client = self.connect().await?;
1437 let payments = client
1438 .lightning()
1439 .list_payments(ListPaymentsRequest {
1440 ..Default::default()
1442 })
1443 .await
1444 .map_err(|err| LightningRpcError::FailedToListTransactions {
1445 failure_reason: err.to_string(),
1446 })?
1447 .into_inner();
1448
1449 let mut payments = payments
1450 .payments
1451 .iter()
1452 .filter_map(|payment| {
1453 let timestamp_secs = (payment.creation_time_ns / 1_000_000_000) as u64;
1454 if timestamp_secs < start_secs || timestamp_secs >= end_secs {
1455 return None;
1456 }
1457 let payment_hash = sha256::Hash::from_str(&payment.payment_hash).ok();
1458 let preimage = (!payment.payment_preimage.is_empty())
1459 .then_some(payment.payment_preimage.clone());
1460 let status = match &payment.status() {
1461 PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
1462 PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
1463 _ => fedimint_gateway_common::PaymentStatus::Pending,
1464 };
1465 Some(PaymentDetails {
1466 payment_hash,
1467 preimage,
1468 payment_kind: PaymentKind::Bolt11,
1469 amount: Amount::from_msats(payment.value_msat as u64),
1470 direction: PaymentDirection::Outbound,
1471 status,
1472 timestamp_secs,
1473 })
1474 })
1475 .collect::<Vec<_>>();
1476
1477 let invoices = client
1478 .lightning()
1479 .list_invoices(ListInvoiceRequest {
1480 pending_only: false,
1481 ..Default::default()
1483 })
1484 .await
1485 .map_err(|err| LightningRpcError::FailedToListTransactions {
1486 failure_reason: err.to_string(),
1487 })?
1488 .into_inner();
1489
1490 let mut incoming_payments = invoices
1491 .invoices
1492 .iter()
1493 .filter_map(|invoice| {
1494 let timestamp_secs = invoice.settle_date as u64;
1495 if timestamp_secs < start_secs || timestamp_secs >= end_secs {
1496 return None;
1497 }
1498 let status = match &invoice.state() {
1499 InvoiceState::Settled => fedimint_gateway_common::PaymentStatus::Succeeded,
1500 InvoiceState::Canceled => fedimint_gateway_common::PaymentStatus::Failed,
1501 _ => return None,
1502 };
1503 let preimage = (!invoice.r_preimage.is_empty())
1504 .then_some(invoice.r_preimage.encode_hex::<String>());
1505 Some(PaymentDetails {
1506 payment_hash: Some(
1507 sha256::Hash::from_slice(&invoice.r_hash)
1508 .expect("Could not convert payment hash"),
1509 ),
1510 preimage,
1511 payment_kind: PaymentKind::Bolt11,
1512 amount: Amount::from_msats(invoice.value_msat as u64),
1513 direction: PaymentDirection::Inbound,
1514 status,
1515 timestamp_secs,
1516 })
1517 })
1518 .collect::<Vec<_>>();
1519
1520 payments.append(&mut incoming_payments);
1521 payments.sort_by_key(|p| p.timestamp_secs);
1522
1523 Ok(ListTransactionsResponse {
1524 transactions: payments,
1525 })
1526 }
1527
1528 fn create_offer(
1529 &self,
1530 _amount_msat: Option<Amount>,
1531 _description: Option<String>,
1532 _expiry_secs: Option<u32>,
1533 _quantity: Option<u64>,
1534 ) -> Result<String, LightningRpcError> {
1535 Err(LightningRpcError::Bolt12Error {
1536 failure_reason: "LND Does not support Bolt12".to_string(),
1537 })
1538 }
1539
1540 async fn pay_offer(
1541 &self,
1542 _offer: String,
1543 _quantity: Option<u64>,
1544 _amount: Option<Amount>,
1545 _payer_note: Option<String>,
1546 ) -> Result<Preimage, LightningRpcError> {
1547 Err(LightningRpcError::Bolt12Error {
1548 failure_reason: "LND Does not support Bolt12".to_string(),
1549 })
1550 }
1551}
1552
1553fn route_hints_to_lnd(
1554 route_hints: &[fedimint_ln_common::route_hints::RouteHint],
1555) -> Vec<tonic_lnd::lnrpc::RouteHint> {
1556 route_hints
1557 .iter()
1558 .map(|hint| tonic_lnd::lnrpc::RouteHint {
1559 hop_hints: hint
1560 .0
1561 .iter()
1562 .map(|hop| tonic_lnd::lnrpc::HopHint {
1563 node_id: hop.src_node_id.serialize().encode_hex(),
1564 chan_id: hop.short_channel_id,
1565 fee_base_msat: hop.base_msat,
1566 fee_proportional_millionths: hop.proportional_millionths,
1567 cltv_expiry_delta: u32::from(hop.cltv_expiry_delta),
1568 })
1569 .collect(),
1570 })
1571 .collect()
1572}
1573
1574fn wire_features_to_lnd_feature_vec(features_wire_encoded: &[u8]) -> anyhow::Result<Vec<i32>> {
1575 ensure!(
1576 features_wire_encoded.len() <= 1_000,
1577 "Will not process feature bit vectors larger than 1000 byte"
1578 );
1579
1580 let lnd_features = features_wire_encoded
1581 .iter()
1582 .rev()
1583 .enumerate()
1584 .flat_map(|(byte_idx, &feature_byte)| {
1585 (0..8).filter_map(move |bit_idx| {
1586 if (feature_byte & (1u8 << bit_idx)) != 0 {
1587 Some(
1588 i32::try_from(byte_idx * 8 + bit_idx)
1589 .expect("Index will never exceed i32::MAX for feature vectors <8MB"),
1590 )
1591 } else {
1592 None
1593 }
1594 })
1595 })
1596 .collect::<Vec<_>>();
1597
1598 Ok(lnd_features)
1599}
1600
1601struct PrettyPaymentHash<'a>(&'a Vec<u8>);
1603
1604impl Display for PrettyPaymentHash<'_> {
1605 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1606 write!(f, "payment_hash={}", self.0.encode_hex::<String>())
1607 }
1608}
1609
1610#[cfg(test)]
1611mod tests;