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