lnd.go raw
1 package lnd
2
3 import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "math"
11 "slices"
12 "sort"
13 "strconv"
14 "strings"
15 "time"
16
17 "github.com/btcsuite/btcd/chaincfg/chainhash"
18 decodepay "github.com/nbd-wtf/ln-decodepay"
19 "google.golang.org/grpc/status"
20
21 "github.com/getAlby/hub/config"
22 "github.com/getAlby/hub/events"
23 "github.com/getAlby/hub/lnclient"
24 "github.com/getAlby/hub/lnclient/lnd/wrapper"
25 "github.com/getAlby/hub/logger"
26 "github.com/getAlby/hub/nip47/models"
27 "github.com/getAlby/hub/nip47/notifications"
28 "github.com/getAlby/hub/transactions"
29
30 "github.com/sirupsen/logrus"
31 // "gorm.io/gorm"
32
33 "github.com/lightningnetwork/lnd/lnrpc"
34 "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
35 "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
36 )
37
38 const SEND_PAYMENT_TIMEOUT = 50
39
40 type LNDService struct {
41 client *wrapper.LNDWrapper
42 nodeInfo *lnclient.NodeInfo
43 cancel context.CancelFunc
44 ctx context.Context
45 eventPublisher events.EventPublisher
46 }
47
48 func NewLNDService(ctx context.Context, eventPublisher events.EventPublisher, lndAddress, lndCertHex, lndMacaroonHex string) (result lnclient.LNClient, err error) {
49 if lndAddress == "" || lndMacaroonHex == "" {
50 return nil, errors.New("one or more required LND configuration are missing")
51 }
52
53 lndClient, err := wrapper.NewLNDclient(wrapper.LNDoptions{
54 Address: lndAddress,
55 CertHex: lndCertHex,
56 MacaroonHex: lndMacaroonHex,
57 })
58 if err != nil {
59 logger.Logger.WithError(err).Error("Failed to create new LND client")
60 return nil, err
61 }
62
63 var nodeInfo *lnclient.NodeInfo
64 maxRetries := 60
65 for i := range maxRetries {
66 nodeInfo, err = fetchNodeInfo(ctx, lndClient)
67 if err == nil {
68 break
69 }
70 logger.Logger.WithFields(logrus.Fields{
71 "iteration": i,
72 }).WithError(err).Error("Failed to connect to LND, retrying in 10s")
73
74 select {
75 case <-time.After(10 * time.Second):
76 case <-ctx.Done():
77 logger.Logger.WithError(ctx.Err()).Error("Context cancelled during LND connection retries")
78 return nil, ctx.Err()
79 }
80 }
81
82 if err != nil {
83 logger.Logger.WithError(err).Error("Failed to connect to LND on final attempt, not attempting further retries")
84 return nil, err
85 }
86
87 lndCtx, cancel := context.WithCancel(ctx)
88
89 lndService := &LNDService{
90 client: lndClient,
91 nodeInfo: nodeInfo,
92 cancel: cancel,
93 ctx: lndCtx,
94 eventPublisher: eventPublisher,
95 }
96
97 go lndService.subscribePayments(lndCtx)
98 go lndService.subscribeInvoices(lndCtx)
99 go lndService.subscribeChannelEvents(lndCtx)
100 go lndService.subscribeOpenHoldInvoices(lndCtx)
101 go lndService.trackForwardedPayments(lndCtx)
102
103 logger.Logger.WithField("alias", nodeInfo.Alias).Info("Connected to LND")
104
105 return lndService, nil
106 }
107
108 func (svc *LNDService) trackForwardedPayments(ctx context.Context) {
109 // NOTE: this only tracks payments when hub is online and attached
110 lastTime := time.Now()
111 for {
112 select {
113 case <-ctx.Done():
114 return
115 default:
116 time.Sleep(1 * time.Minute)
117 nextTime := time.Now()
118 forwardedPayments, err := svc.client.ForwardingHistory(ctx, &lnrpc.ForwardingHistoryRequest{
119 StartTime: uint64(lastTime.Unix()),
120 EndTime: uint64(nextTime.Unix()),
121 })
122 if err != nil {
123 logger.Logger.WithError(err).Error("failed to read forwarding history")
124 continue
125 }
126 for _, forwardingEvent := range forwardedPayments.ForwardingEvents {
127 svc.eventPublisher.Publish(&events.Event{
128 Event: "nwc_payment_forwarded",
129 Properties: &lnclient.PaymentForwardedEventProperties{
130 TotalFeeEarnedMsat: forwardingEvent.FeeMsat,
131 OutboundAmountForwardedMsat: forwardingEvent.AmtOutMsat,
132 },
133 })
134 }
135 lastTime = nextTime
136 }
137 }
138 }
139
140 func (svc *LNDService) subscribePayments(ctx context.Context) {
141 for {
142 select {
143 case <-ctx.Done():
144 return
145 default:
146 paymentStream, err := svc.client.SubscribePayments(ctx, &routerrpc.TrackPaymentsRequest{
147 NoInflightUpdates: true,
148 })
149 if err != nil {
150 logger.Logger.WithError(err).Error("Error subscribing to payments")
151 select {
152 case <-ctx.Done():
153 return
154 case <-time.After(10 * time.Second):
155 continue
156 }
157 }
158 paymentsLoop:
159 for {
160 payment, err := paymentStream.Recv()
161 if err != nil {
162 logger.Logger.WithError(err).Error("Failed to receive payment")
163 select {
164 case <-ctx.Done():
165 return
166 case <-time.After(2 * time.Second):
167 break paymentsLoop
168 }
169 }
170
171 switch payment.Status {
172 case lnrpc.Payment_FAILED:
173 logger.Logger.WithFields(logrus.Fields{
174 "payment": payment,
175 }).Info("Received payment failed notification")
176
177 transaction, err := lndPaymentToTransaction(payment)
178 if err != nil {
179 continue
180 }
181 svc.eventPublisher.Publish(&events.Event{
182 Event: "nwc_lnclient_payment_failed",
183 Properties: &lnclient.PaymentFailedEventProperties{
184 Transaction: transaction,
185 Reason: payment.FailureReason.String(),
186 },
187 })
188 case lnrpc.Payment_SUCCEEDED:
189 logger.Logger.WithFields(logrus.Fields{
190 "payment": payment,
191 }).Info("Received payment sent notification")
192
193 transaction, err := lndPaymentToTransaction(payment)
194 if err != nil {
195 continue
196 }
197 svc.eventPublisher.Publish(&events.Event{
198 Event: "nwc_lnclient_payment_sent",
199 Properties: transaction,
200 })
201 default:
202 continue
203 }
204 }
205 }
206 }
207 }
208
209 func (svc *LNDService) subscribeInvoices(ctx context.Context) {
210 for {
211 select {
212 case <-ctx.Done():
213 return
214 default:
215 invoiceStream, err := svc.client.SubscribeInvoices(ctx, &lnrpc.InvoiceSubscription{})
216 if err != nil {
217 logger.Logger.WithError(err).Error("Error subscribing to invoices")
218 select {
219 case <-ctx.Done():
220 return
221 case <-time.After(10 * time.Second):
222 continue
223 }
224 }
225 invoicesLoop:
226 for {
227 invoice, err := invoiceStream.Recv()
228 if err != nil {
229 logger.Logger.WithError(err).Error("Failed to receive invoice")
230 select {
231 case <-ctx.Done():
232 return
233 case <-time.After(2 * time.Second):
234 break invoicesLoop
235 }
236 }
237
238 if invoice.State != lnrpc.Invoice_SETTLED {
239 continue
240 }
241
242 logger.Logger.WithFields(logrus.Fields{
243 "invoice": invoice,
244 }).Info("Received new invoice")
245
246 svc.eventPublisher.Publish(&events.Event{
247 Event: "nwc_lnclient_payment_received",
248 Properties: lndInvoiceToTransaction(invoice),
249 })
250 }
251 }
252 }
253 }
254
255 func (svc *LNDService) subscribeChannelEvents(ctx context.Context) {
256 for {
257 select {
258 case <-ctx.Done():
259 return
260 default:
261 channelEvents, err := svc.client.SubscribeChannelEvents(ctx, &lnrpc.ChannelEventSubscription{})
262 if err != nil {
263 logger.Logger.WithError(err).Error("Error subscribing to channel events")
264 select {
265 case <-ctx.Done():
266 return
267 case <-time.After(10 * time.Second):
268 continue
269 }
270 }
271 channelEventsLoop:
272 for {
273 event, err := channelEvents.Recv()
274 if err != nil {
275 logger.Logger.WithError(err).Error("Failed to receive channel event")
276 select {
277 case <-ctx.Done():
278 return
279 case <-time.After(2 * time.Second):
280 break channelEventsLoop
281 }
282 }
283
284 switch update := event.Channel.(type) {
285 case *lnrpc.ChannelEventUpdate_OpenChannel:
286 channel := update.OpenChannel
287 logger.Logger.WithFields(logrus.Fields{
288 "counterparty_node_id": channel.RemotePubkey,
289 "public": !channel.Private,
290 "capacity": channel.Capacity,
291 "is_outbound": channel.Initiator,
292 }).Info("Channel opened")
293
294 svc.eventPublisher.Publish(&events.Event{
295 Event: "nwc_channel_ready",
296 Properties: map[string]interface{}{
297 "counterparty_node_id": channel.RemotePubkey,
298 "node_type": config.LNDBackendType,
299 "public": !channel.Private,
300 "capacity": channel.Capacity,
301 "is_outbound": channel.Initiator,
302 },
303 })
304 case *lnrpc.ChannelEventUpdate_ClosedChannel:
305 closureReason := update.ClosedChannel.CloseType.String()
306 counterpartyNodeId := update.ClosedChannel.RemotePubkey
307
308 logger.Logger.WithFields(logrus.Fields{
309 "counterparty_node_id": counterpartyNodeId,
310 "reason": closureReason,
311 }).Info("Channel closed")
312
313 svc.eventPublisher.Publish(&events.Event{
314 Event: "nwc_channel_closed",
315 Properties: map[string]interface{}{
316 "counterparty_node_id": counterpartyNodeId,
317 "counterparty_node_url": "https://amboss.space/node/" + counterpartyNodeId,
318 "reason": closureReason,
319 "node_type": config.LNDBackendType,
320 },
321 })
322 }
323 }
324 }
325 }
326 }
327
328 func (svc *LNDService) subscribeOpenHoldInvoices(ctx context.Context) {
329 oneWeekAgo := time.Now().AddDate(0, 0, -7).Unix()
330
331 listInvoicesResponse, err := svc.client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{
332 PendingOnly: true,
333 CreationDateStart: uint64(oneWeekAgo),
334 })
335 if err != nil {
336 logger.Logger.WithError(err).Error("Failed to list invoices for open hold invoices subscription")
337 return
338 }
339
340 for _, invoice := range listInvoicesResponse.Invoices {
341 if invoice.State == lnrpc.Invoice_OPEN {
342 paymentHashHex := hex.EncodeToString(invoice.RHash)
343 logger.Logger.WithFields(logrus.Fields{
344 "paymentHash": paymentHashHex,
345 "addIndex": invoice.AddIndex,
346 }).Info("Resubscribing to pending hold invoice")
347 go svc.subscribeSingleInvoice(invoice.RHash)
348 }
349 }
350 }
351
352 func (svc *LNDService) subscribeSingleInvoice(paymentHashBytes []byte) {
353 // Use the global context for the lifetime of this subscription, but create a cancellable one for this specific task
354 // This allows the goroutine to be potentially cancelled externally if needed, though it primarily exits on invoice state change.
355 // We use a background context derived from the global one to avoid cancelling if the original request context finishes.
356 ctx, cancel := context.WithCancel(svc.ctx)
357 defer cancel() // Ensure cancellation happens on exit
358
359 paymentHashHex := hex.EncodeToString(paymentHashBytes)
360 log := logger.Logger.WithField("paymentHash", paymentHashHex)
361
362 log.Info("Starting subscribeSingleInvoice goroutine")
363
364 subReq := &invoicesrpc.SubscribeSingleInvoiceRequest{
365 RHash: paymentHashBytes,
366 }
367
368 invoiceStream, err := svc.client.SubscribeSingleInvoice(ctx, subReq)
369 if err != nil {
370 log.WithError(err).Error("SubscribeSingleInvoice call failed")
371 // Goroutine will exit
372 return
373 }
374
375 log.Info("Successfully subscribed to single invoice stream")
376
377 defer func() {
378 log.Info("Exiting subscribeSingleInvoice goroutine")
379 if r := recover(); r != nil {
380 log.WithField("panic", r).Errorf("PANIC recovered in single invoice stream processing")
381 }
382 }()
383
384 for {
385 invoice, err := invoiceStream.Recv()
386
387 if err != nil {
388 log.WithError(err).Error("Failed to receive single invoice update from stream")
389 return
390 }
391 if ctx.Err() != nil {
392 log.Info("Context cancelled, exiting single invoice subscription loop")
393 return
394 }
395
396 log.WithFields(logrus.Fields{
397 "rawState": invoice.State.String(),
398 "addIndex": invoice.AddIndex,
399 "settleIndex": invoice.SettleIndex,
400 "amtPaidMsat": invoice.AmtPaidMsat,
401 }).Info("Raw update received from single invoice stream")
402
403 switch invoice.State {
404 case lnrpc.Invoice_ACCEPTED:
405 log.Info("Hold invoice accepted, publishing internal event")
406 transaction := lndInvoiceToTransaction(invoice)
407 var minExpiry uint32
408 for _, htlc := range invoice.Htlcs {
409 if htlc.ExpiryHeight < int32(minExpiry) || minExpiry == 0 {
410 minExpiry = uint32(htlc.ExpiryHeight)
411 }
412 }
413 transaction.SettleDeadline = &minExpiry
414 svc.eventPublisher.Publish(&events.Event{
415 Event: "nwc_lnclient_hold_invoice_accepted",
416 Properties: transaction,
417 })
418 case lnrpc.Invoice_CANCELED:
419 log.Info("Hold invoice canceled, ending subscription")
420 return // Invoice reached final state, exit goroutine
421 case lnrpc.Invoice_SETTLED:
422 return // Invoice reached final state, exit goroutine
423 case lnrpc.Invoice_OPEN:
424 // Continue loop
425 }
426 }
427 }
428
429 func (svc *LNDService) Shutdown() error {
430 logger.Logger.Info("cancelling LND context")
431 svc.cancel()
432 return nil
433 }
434
435 func (svc *LNDService) SendPaymentSync(payReq string, amountMsat *uint64) (*lnclient.PayInvoiceResponse, error) {
436 const MAX_PARTIAL_PAYMENTS = 16
437
438 paymentRequest, err := decodepay.Decodepay(payReq)
439 if err != nil {
440 logger.Logger.WithFields(logrus.Fields{
441 "bolt11": payReq,
442 }).WithError(err).Error("Failed to decode bolt11 invoice")
443 return nil, err
444 }
445
446 paymentAmountMsat := uint64(paymentRequest.MSatoshi)
447 if amountMsat != nil {
448 paymentAmountMsat = *amountMsat
449 }
450 sendRequest := &routerrpc.SendPaymentRequest{
451 PaymentRequest: payReq,
452 MaxParts: MAX_PARTIAL_PAYMENTS,
453 FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(paymentAmountMsat)),
454 TimeoutSeconds: SEND_PAYMENT_TIMEOUT,
455 }
456
457 if amountMsat != nil {
458 sendRequest.AmtMsat = int64(*amountMsat)
459 }
460
461 payStream, err := svc.client.SendPayment(svc.ctx, sendRequest)
462 if err != nil {
463 logger.Logger.WithField("bolt11", payReq).WithError(err).Error("SendPayment failed")
464 return nil, err
465 }
466
467 resp, err := svc.getPaymentResult(payStream)
468 if err != nil {
469 logger.Logger.WithField("bolt11", payReq).WithError(err).Error("Couldn't get response from paystream")
470 return nil, err
471 }
472
473 if resp.Status != lnrpc.Payment_SUCCEEDED {
474 // In LND, timeout error only happens when there are more routes to try
475 // but we ran out of time in contrast to LDK where the payment is initiated
476 // and might still succeed after receiving timeout error
477 // See https://github.com/lightningnetwork/lnd/issues/4269#issuecomment-626279140
478 failureReasonMessage := resp.FailureReason.String()
479 logger.Logger.WithFields(logrus.Fields{
480 "bolt11": payReq,
481 "reason": failureReasonMessage,
482 }).Error("Payment not successful")
483 return nil, errors.New(failureReasonMessage)
484 }
485
486 if resp.PaymentPreimage == "" {
487 logger.Logger.WithField("bolt11", payReq).Error("No payment preimage in response")
488 return nil, errors.New("no preimage in response")
489 }
490
491 return &lnclient.PayInvoiceResponse{
492 Preimage: resp.PaymentPreimage,
493 FeeMsat: uint64(resp.FeeMsat),
494 }, nil
495 }
496
497 func (svc *LNDService) SendKeysend(amountMsat uint64, destination string, custom_records []lnclient.TLVRecord, preimage string) (*lnclient.PayKeysendResponse, error) {
498 destBytes, err := hex.DecodeString(destination)
499 if err != nil {
500 logger.Logger.WithFields(logrus.Fields{
501 "payee_pubkey": destination,
502 "preimage": preimage,
503 }).WithError(err).Error("Failed to decode payee pubkey")
504 return nil, err
505 }
506 preImageBytes, err := hex.DecodeString(preimage)
507 if err != nil || len(preImageBytes) != 32 {
508 logger.Logger.WithFields(logrus.Fields{
509 "payee_pubkey": destination,
510 "preimage": preimage,
511 }).WithError(err).Error("Invalid preimage")
512 return nil, err
513 }
514
515 paymentHash256 := sha256.New()
516 paymentHash256.Write(preImageBytes)
517 paymentHashBytes := paymentHash256.Sum(nil)
518 paymentHash := hex.EncodeToString(paymentHashBytes)
519
520 destCustomRecords := map[uint64][]byte{}
521 for _, record := range custom_records {
522 decodedValue, err := hex.DecodeString(record.Value)
523 if err != nil {
524 logger.Logger.WithFields(logrus.Fields{
525 "payment_hash": paymentHash,
526 "preimage": preimage,
527 }).WithError(err).Error("Failed to decode custom records")
528 return nil, err
529 }
530 destCustomRecords[record.Type] = decodedValue
531 }
532 const MAX_PARTIAL_PAYMENTS = 16
533 const KEYSEND_CUSTOM_RECORD = 5482373484
534 destCustomRecords[KEYSEND_CUSTOM_RECORD] = preImageBytes
535 sendPaymentRequest := &routerrpc.SendPaymentRequest{
536 Dest: destBytes,
537 AmtMsat: int64(amountMsat),
538 PaymentHash: paymentHashBytes,
539 DestFeatures: []lnrpc.FeatureBit{lnrpc.FeatureBit_TLV_ONION_REQ},
540 DestCustomRecords: destCustomRecords,
541 MaxParts: MAX_PARTIAL_PAYMENTS,
542 TimeoutSeconds: SEND_PAYMENT_TIMEOUT,
543 FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(amountMsat)),
544 }
545
546 payStream, err := svc.client.SendPayment(svc.ctx, sendPaymentRequest)
547 if err != nil {
548 logger.Logger.WithFields(logrus.Fields{
549 "payment_hash": paymentHash,
550 "preimage": preimage,
551 }).WithError(err).Error("Failed to make keysend payment")
552 return nil, err
553 }
554
555 resp, err := svc.getPaymentResult(payStream)
556 if err != nil {
557 logger.Logger.WithFields(logrus.Fields{
558 "payment_hash": paymentHash,
559 "preimage": preimage,
560 }).WithError(err).Error("Couldn't get response from paystream")
561 return nil, err
562 }
563
564 if resp.Status != lnrpc.Payment_SUCCEEDED {
565 failureReasonMessage := resp.FailureReason.String()
566 logger.Logger.WithFields(logrus.Fields{
567 "payment_hash": paymentHash,
568 "preimage": preimage,
569 "reason": failureReasonMessage,
570 }).Error("Keysend not successful")
571 return nil, errors.New(failureReasonMessage)
572 }
573
574 if resp.PaymentPreimage != preimage {
575 logger.Logger.WithFields(logrus.Fields{
576 "payment_hash": paymentHash,
577 "preimage": preimage,
578 }).Error("Preimage in keysend response does not match")
579 return nil, errors.New("preimage in keysend response does not match")
580 }
581 logger.Logger.WithFields(logrus.Fields{
582 "payment_hash": paymentHash,
583 "preimage": preimage,
584 }).Info("Keysend payment successful")
585
586 return &lnclient.PayKeysendResponse{
587 FeeMsat: uint64(resp.FeeMsat),
588 }, nil
589 }
590
591 func (svc *LNDService) getPaymentResult(stream routerrpc.Router_SendPaymentV2Client) (*lnrpc.Payment, error) {
592 for {
593 payment, err := stream.Recv()
594 if err != nil {
595 return nil, err
596 }
597
598 if payment.Status != lnrpc.Payment_IN_FLIGHT {
599 return payment, nil
600 }
601 }
602 }
603
604 func (svc *LNDService) MakeInvoice(ctx context.Context, amountMsat int64, description string, descriptionHash string, expiry int64, throughNodePubkey *string) (transaction *lnclient.Transaction, err error) {
605 var descriptionHashBytes []byte
606
607 if descriptionHash != "" {
608 descriptionHashBytes, err = hex.DecodeString(descriptionHash)
609 if err != nil || len(descriptionHashBytes) != 32 {
610 if err == nil {
611 err = errors.New("description hash must be 32 bytes hex")
612 }
613 logger.Logger.WithFields(logrus.Fields{
614 "descriptionHash": descriptionHash,
615 }).WithError(err).Error("Invalid description hash")
616 return nil, err
617 }
618 }
619
620 if expiry == 0 {
621 expiry = lnclient.DEFAULT_INVOICE_EXPIRY
622 }
623
624 channels, err := svc.ListChannels(ctx)
625 if err != nil {
626 return nil, err
627 }
628
629 hasPublicChannels := false
630 for _, channel := range channels {
631 if channel.Active && channel.Public {
632 hasPublicChannels = true
633 break
634 }
635 }
636
637 var hints []*lnrpc.RouteHint
638 if !hasPublicChannels && throughNodePubkey != nil {
639 channelsRes, err := svc.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{
640 PrivateOnly: true,
641 })
642 if err != nil {
643 return nil, err
644 }
645
646 for _, channel := range channelsRes.Channels {
647 if channel.RemotePubkey != *throughNodePubkey {
648 continue
649 }
650
651 chanInfo, err := svc.client.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
652 ChanId: channel.ChanId,
653 })
654 if err != nil {
655 logger.Logger.WithFields(logrus.Fields{
656 "channel_id": channel.ChanId,
657 }).WithError(err).Error("Unable to get channel info")
658 continue
659 }
660
661 var remotePolicy *lnrpc.RoutingPolicy
662 if chanInfo.Node1Pub == channel.RemotePubkey {
663 remotePolicy = chanInfo.Node1Policy
664 } else {
665 remotePolicy = chanInfo.Node2Policy
666 }
667
668 if remotePolicy == nil {
669 logger.Logger.WithFields(logrus.Fields{
670 "channel_id": channel.ChanId,
671 }).WithError(err).Error("Remote channel policy does not exist")
672 continue
673 }
674
675 channelId := chanInfo.ChannelId
676 if channel.PeerScidAlias != 0 {
677 channelId = channel.PeerScidAlias
678 }
679
680 hint := &lnrpc.RouteHint{
681 HopHints: []*lnrpc.HopHint{
682 {
683 NodeId: channel.RemotePubkey,
684 ChanId: channelId,
685 FeeBaseMsat: uint32(remotePolicy.FeeBaseMsat),
686 FeeProportionalMillionths: uint32(remotePolicy.FeeRateMilliMsat),
687 CltvExpiryDelta: remotePolicy.TimeLockDelta,
688 },
689 },
690 }
691
692 hints = append(hints, hint)
693 if len(hints) == 3 {
694 // limit to 3 channels
695 // NOTE: there is no check that the channels are online or have enough receiving capacity.
696 break
697 }
698 }
699
700 if len(hints) == 0 {
701 return nil, errors.New("no channel found for given throughNodePubkey")
702 }
703 }
704
705 addInvoiceRequest := &lnrpc.Invoice{
706 ValueMsat: amountMsat,
707 Memo: description,
708 DescriptionHash: descriptionHashBytes,
709 Expiry: expiry,
710 RouteHints: hints,
711 Private: !hasPublicChannels, // use private channel hints in the invoice
712 }
713
714 resp, err := svc.client.AddInvoice(ctx, addInvoiceRequest)
715 if err != nil {
716 logger.Logger.WithError(err).Error("Failed to create invoice")
717 return nil, err
718 }
719
720 inv, err := svc.client.LookupInvoice(ctx, &lnrpc.PaymentHash{RHash: resp.RHash})
721 if err != nil {
722 logger.Logger.WithError(err).Error("Failed to lookup invoice")
723 return nil, err
724 }
725
726 transaction = lndInvoiceToTransaction(inv)
727 return transaction, nil
728 }
729
730 func (svc *LNDService) MakeHoldInvoice(ctx context.Context, amountMsat int64, description string, descriptionHash string, expiry int64, paymentHash string, minCltvExpiryDelta *uint64) (transaction *lnclient.Transaction, err error) {
731 var descriptionHashBytes []byte
732 var paymentHashBytes []byte
733
734 if descriptionHash != "" {
735 descriptionHashBytes, err = hex.DecodeString(descriptionHash)
736 if err != nil || len(descriptionHashBytes) != 32 {
737 if err == nil {
738 err = errors.New("description hash must be 32 bytes hex")
739 }
740 logger.Logger.WithFields(logrus.Fields{
741 "descriptionHash": descriptionHash,
742 }).WithError(err).Error("Invalid description hash")
743 return nil, err
744 }
745 }
746
747 paymentHashBytes, err = hex.DecodeString(paymentHash)
748 if err != nil || len(paymentHashBytes) != 32 {
749 if err == nil {
750 err = errors.New("payment hash must be 32 bytes hex")
751 }
752 logger.Logger.WithFields(logrus.Fields{
753 "paymentHash": paymentHash,
754 }).WithError(err).Error("Invalid payment hash")
755 return nil, err
756 }
757
758 if expiry == 0 {
759 expiry = lnclient.DEFAULT_INVOICE_EXPIRY
760 }
761
762 channels, err := svc.ListChannels(ctx)
763 if err != nil {
764 return nil, err
765 }
766
767 hasPublicChannels := false
768 for _, channel := range channels {
769 if channel.Active && channel.Public {
770 hasPublicChannels = true
771 }
772 }
773
774 addInvoiceRequest := &invoicesrpc.AddHoldInvoiceRequest{
775 ValueMsat: amountMsat,
776 Memo: description,
777 DescriptionHash: descriptionHashBytes,
778 Expiry: expiry,
779 Private: !hasPublicChannels,
780 Hash: paymentHashBytes,
781 }
782 if minCltvExpiryDelta != nil {
783 addInvoiceRequest.CltvExpiry = *minCltvExpiryDelta
784 }
785
786 _, err = svc.client.AddHoldInvoice(ctx, addInvoiceRequest)
787 if err != nil {
788 logger.Logger.WithError(err).Error("Failed to create hold invoice")
789 return nil, err
790 }
791
792 // Start subscribing to updates for this specific hold invoice in a separate goroutine
793 go svc.subscribeSingleInvoice(paymentHashBytes)
794 logger.Logger.WithField("paymentHash", paymentHash).Info("Launched single invoice subscription goroutine")
795
796 inv, err := svc.client.LookupInvoice(ctx, &lnrpc.PaymentHash{RHash: paymentHashBytes})
797 if err != nil {
798 logger.Logger.WithField("paymentHash", paymentHash).WithError(err).Error("Failed to lookup hold invoice after creation")
799 return nil, err
800 }
801
802 transaction = lndInvoiceToTransaction(inv)
803 return transaction, nil
804 }
805
806 func (svc *LNDService) SettleHoldInvoice(ctx context.Context, preimage string) (err error) {
807 preimageBytes, err := hex.DecodeString(preimage)
808 if err != nil || len(preimageBytes) != 32 {
809 if err == nil {
810 err = errors.New("preimage must be 32 bytes hex")
811 }
812 logger.Logger.WithFields(logrus.Fields{
813 "preimage": preimage,
814 }).WithError(err).Error("Invalid preimage")
815 return err
816 }
817
818 _, err = svc.client.SettleInvoice(ctx, &invoicesrpc.SettleInvoiceMsg{
819 Preimage: preimageBytes,
820 })
821 if err != nil {
822 logger.Logger.WithFields(logrus.Fields{
823 "preimage": preimage,
824 }).WithError(err).Error("Failed to settle hold invoice")
825 return err
826 }
827 return nil
828 }
829
830 func (svc *LNDService) CancelHoldInvoice(ctx context.Context, paymentHash string) (err error) {
831 paymentHashBytes, err := hex.DecodeString(paymentHash)
832 if err != nil || len(paymentHashBytes) != 32 {
833 if err == nil {
834 err = errors.New("payment hash must be 32 bytes hex")
835 }
836 logger.Logger.WithFields(logrus.Fields{
837 "paymentHash": paymentHash,
838 }).WithError(err).Error("Invalid payment hash")
839 return err
840 }
841
842 _, err = svc.client.CancelInvoice(ctx, &invoicesrpc.CancelInvoiceMsg{
843 PaymentHash: paymentHashBytes,
844 })
845 if err != nil {
846 logger.Logger.WithFields(logrus.Fields{
847 "paymentHash": paymentHash,
848 }).WithError(err).Error("Failed to cancel hold invoice")
849 return err
850 }
851 return nil
852 }
853
854 func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
855 paymentHashBytes, err := hex.DecodeString(paymentHash)
856 if err != nil || len(paymentHashBytes) != 32 {
857 if err == nil {
858 err = errors.New("payment hash must be 32 bytes hex")
859 }
860 logger.Logger.WithFields(logrus.Fields{
861 "payment_hash": paymentHash,
862 }).WithError(err).Error("Invalid payment hash")
863 return nil, err
864 }
865
866 lndInvoice, err := svc.client.LookupInvoice(ctx, &lnrpc.PaymentHash{RHash: paymentHashBytes})
867 if err != nil {
868 logger.Logger.WithFields(logrus.Fields{
869 "payment_hash": paymentHash,
870 }).WithError(err).Error("Failed to lookup invoice")
871 return nil, err
872 }
873
874 transaction = lndInvoiceToTransaction(lndInvoice)
875 return transaction, nil
876 }
877
878 func (svc *LNDService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
879 return svc.nodeInfo, nil
880 }
881
882 func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
883 activeResp, err := svc.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{})
884 if err != nil {
885 logger.Logger.WithError(err).Error("Failed to fetch channels")
886 return nil, err
887 }
888 pendingResp, err := svc.client.PendingChannels(ctx, &lnrpc.PendingChannelsRequest{})
889 if err != nil {
890 logger.Logger.WithError(err).Error("Failed to fetch pending channels")
891 return nil, err
892 }
893
894 nodeInfo, err := svc.client.GetInfo(ctx, &lnrpc.GetInfoRequest{})
895 if err != nil {
896 logger.Logger.WithError(err).Error("Failed to fetch node info")
897 return nil, err
898 }
899
900 // hardcoding required confirmations as there seems to be no way to get the number of required confirmations in LND
901 var confirmationsRequired uint32 = 6
902 // get recent transactions to check how many confirmations pending channel(s) have
903 recentOnchainTransactions, err := svc.client.GetTransactions(ctx, &lnrpc.GetTransactionsRequest{
904 StartHeight: int32(nodeInfo.BlockHeight - confirmationsRequired),
905 })
906 if err != nil {
907 logger.Logger.WithError(err).Error("Failed to fetch onchain transactions")
908 return nil, err
909 }
910
911 channels := make([]lnclient.Channel, len(activeResp.Channels)+len(pendingResp.PendingOpenChannels))
912
913 for i, lndChannel := range activeResp.Channels {
914 channelPoint, err := svc.parseChannelPoint(lndChannel.ChannelPoint)
915 if err != nil {
916 return nil, err
917 }
918
919 // first 3 bytes of the channel ID are the block height
920 channelOpeningBlockHeight := lndChannel.ChanId >> 40
921 confirmations := nodeInfo.BlockHeight - uint32(channelOpeningBlockHeight) + 1
922
923 var forwardingFeeBaseMsat uint32
924 var forwardingFeeProportionalMillionths uint32
925 if !lndChannel.Private {
926 channelEdge, err := svc.client.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
927 ChanId: lndChannel.ChanId,
928 })
929 if err != nil {
930 return nil, err
931 }
932
933 var policy *lnrpc.RoutingPolicy
934 if channelEdge.Node1Pub == nodeInfo.IdentityPubkey {
935 policy = channelEdge.Node1Policy
936 } else {
937 policy = channelEdge.Node2Policy
938 }
939 if policy != nil {
940 forwardingFeeBaseMsat = uint32(policy.FeeBaseMsat)
941 forwardingFeeProportionalMillionths = uint32(policy.FeeRateMilliMsat)
942 }
943 }
944
945 channels[i] = lnclient.Channel{
946 InternalChannel: lndChannel,
947 LocalBalanceMsat: lndChannel.LocalBalance * 1000,
948 LocalSpendableBalanceMsat: int64(math.Max(float64((lndChannel.LocalBalance-int64(lndChannel.LocalConstraints.ChanReserveSat))*1000), float64(0))),
949 RemoteBalanceMsat: lndChannel.RemoteBalance * 1000,
950 RemotePubkey: lndChannel.RemotePubkey,
951 Id: strconv.FormatUint(lndChannel.ChanId, 10),
952 Active: lndChannel.Active,
953 Public: !lndChannel.Private,
954 FundingTxId: channelPoint.GetFundingTxidStr(),
955 FundingTxVout: channelPoint.GetOutputIndex(),
956 Confirmations: &confirmations,
957 ConfirmationsRequired: &confirmationsRequired,
958 UnspendablePunishmentReserveSat: lndChannel.LocalConstraints.ChanReserveSat,
959 CounterpartyUnspendablePunishmentReserveSat: lndChannel.RemoteConstraints.ChanReserveSat,
960 IsOutbound: lndChannel.Initiator,
961 ForwardingFeeBaseMsat: forwardingFeeBaseMsat,
962 ForwardingFeeProportionalMillionths: forwardingFeeProportionalMillionths,
963 }
964 }
965
966 for j, lndChannel := range pendingResp.PendingOpenChannels {
967 channelPoint, err := svc.parseChannelPoint(lndChannel.Channel.ChannelPoint)
968 if err != nil {
969 return nil, err
970 }
971 fundingTxId := channelPoint.GetFundingTxidStr()
972
973 var confirmations *uint32
974 for _, t := range recentOnchainTransactions.Transactions {
975 if t.TxHash == fundingTxId {
976 confirmations32 := uint32(t.NumConfirmations)
977 confirmations = &confirmations32
978 }
979 }
980
981 channels[j+len(activeResp.Channels)] = lnclient.Channel{
982 InternalChannel: lndChannel,
983 LocalBalanceMsat: lndChannel.Channel.LocalBalance * 1000,
984 RemoteBalanceMsat: lndChannel.Channel.RemoteBalance * 1000,
985 RemotePubkey: lndChannel.Channel.RemoteNodePub,
986 Public: !lndChannel.Channel.Private,
987 FundingTxId: fundingTxId,
988 Active: false,
989 Confirmations: confirmations,
990 ConfirmationsRequired: &confirmationsRequired,
991 }
992 }
993
994 return channels, nil
995 }
996
997 func (svc *LNDService) parseChannelPoint(channelPointStr string) (*lnrpc.ChannelPoint, error) {
998 channelPointParts := strings.Split(channelPointStr, ":")
999
1000 if len(channelPointParts) != 2 {
1001 logger.Logger.WithField("channel_point", channelPointStr).Error("Invalid channel point")
1002 return nil, errors.New("invalid channel point")
1003 }
1004
1005 channelPoint := &lnrpc.ChannelPoint{}
1006 channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
1007 FundingTxidStr: channelPointParts[0],
1008 }
1009
1010 outputIndex, err := strconv.ParseUint(channelPointParts[1], 10, 32)
1011 if err != nil {
1012 logger.Logger.WithField("channel_point", channelPointStr).WithError(err).Error("Failed to parse channel point")
1013 return nil, err
1014 }
1015 channelPoint.OutputIndex = uint32(outputIndex)
1016
1017 return channelPoint, nil
1018 }
1019
1020 func (svc *LNDService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
1021 pubkey := svc.GetPubkey()
1022 nodeConnectionInfo = &lnclient.NodeConnectionInfo{
1023 Pubkey: pubkey,
1024 }
1025
1026 nodeInfo, err := svc.client.GetNodeInfo(ctx, &lnrpc.NodeInfoRequest{
1027 PubKey: pubkey,
1028 })
1029 if err != nil {
1030 logger.Logger.WithError(err).Error("Failed to fetch node info")
1031 return nodeConnectionInfo, nil
1032 }
1033
1034 addresses := nodeInfo.Node.Addresses
1035 if len(addresses) < 1 {
1036 logger.Logger.Error("No available listening addresses")
1037 return nodeConnectionInfo, nil
1038 }
1039
1040 firstAddress := addresses[0]
1041 parts := strings.Split(firstAddress.Addr, ":")
1042 if len(parts) != 2 {
1043 logger.Logger.Error("Failed to fetch node address")
1044 return nodeConnectionInfo, nil
1045 }
1046 port, err := strconv.Atoi(parts[1])
1047 if err != nil {
1048 logger.Logger.WithError(err).Error("Failed to fetch node port")
1049 return nodeConnectionInfo, nil
1050 }
1051
1052 nodeConnectionInfo.Address = parts[0]
1053 nodeConnectionInfo.Port = port
1054
1055 return nodeConnectionInfo, nil
1056 }
1057
1058 func (svc *LNDService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
1059 _, err := svc.client.ConnectPeer(ctx, &lnrpc.ConnectPeerRequest{
1060 Addr: &lnrpc.LightningAddress{
1061 Pubkey: connectPeerRequest.Pubkey,
1062 Host: connectPeerRequest.Address + ":" + strconv.Itoa(int(connectPeerRequest.Port)),
1063 },
1064 })
1065
1066 if grpcErr, ok := status.FromError(err); ok {
1067 if strings.HasPrefix(grpcErr.Message(), "already connected to peer") {
1068 return nil
1069 }
1070 }
1071 return err
1072 }
1073
1074 func (svc *LNDService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
1075 peers, err := svc.ListPeers(ctx)
1076 if err != nil {
1077 return nil, errors.New("failed to list peers")
1078 }
1079
1080 var foundPeer *lnclient.PeerDetails
1081 for _, peer := range peers {
1082 if peer.NodeId == openChannelRequest.Pubkey {
1083
1084 foundPeer = &peer
1085 break
1086 }
1087 }
1088
1089 if foundPeer == nil {
1090 return nil, errors.New("node is not peered yet")
1091 }
1092
1093 logger.Logger.WithField("peer_id", foundPeer.NodeId).Info("Opening channel")
1094
1095 nodePub, err := hex.DecodeString(openChannelRequest.Pubkey)
1096 if err != nil {
1097 return nil, errors.New("failed to decode pubkey")
1098 }
1099
1100 channel, err := svc.client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{
1101 NodePubkey: nodePub,
1102 Private: !openChannelRequest.Public,
1103 LocalFundingAmount: openChannelRequest.AmountSats,
1104 // set a super-high forwarding fee of 100K sats by default to disable unwanted routing
1105 BaseFee: 100_000_000,
1106 })
1107 if err != nil {
1108 logger.Logger.WithError(err).Error("Failed to open channel")
1109 return nil, fmt.Errorf("failed to open channel with %s: %s", foundPeer.NodeId, err)
1110 }
1111
1112 fundingTxidBytes := channel.GetFundingTxidBytes()
1113
1114 // we get the funding transaction id bytes in reverse
1115 for i, j := 0, len(fundingTxidBytes)-1; i < j; i, j = i+1, j-1 {
1116 fundingTxidBytes[i], fundingTxidBytes[j] = fundingTxidBytes[j], fundingTxidBytes[i]
1117 }
1118
1119 return &lnclient.OpenChannelResponse{
1120 FundingTxId: hex.EncodeToString(fundingTxidBytes),
1121 }, err
1122 }
1123
1124 func (svc *LNDService) UpdateChannel(ctx context.Context, updateChannelRequest *lnclient.UpdateChannelRequest) error {
1125 logger.Logger.WithFields(logrus.Fields{
1126 "request": updateChannelRequest,
1127 }).Info("Updating Channel")
1128
1129 chanId64, err := strconv.ParseUint(updateChannelRequest.ChannelId, 10, 64)
1130 if err != nil {
1131 logger.Logger.WithField("request", updateChannelRequest).Error("Failed to parse channel id")
1132 return err
1133 }
1134
1135 channelEdge, err := svc.client.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
1136 ChanId: chanId64,
1137 })
1138 if err != nil {
1139 logger.Logger.WithField("request", updateChannelRequest).Error("Failed to fetch channel info")
1140 return err
1141 }
1142
1143 channelPoint, err := svc.parseChannelPoint(channelEdge.ChanPoint)
1144 if err != nil {
1145 return err
1146 }
1147
1148 var nodePolicy *lnrpc.RoutingPolicy
1149 if channelEdge.Node1Pub == svc.client.IdentityPubkey {
1150 nodePolicy = channelEdge.Node1Policy
1151 } else {
1152 nodePolicy = channelEdge.Node2Policy
1153 }
1154
1155 _, err = svc.client.UpdateChannel(ctx, &lnrpc.PolicyUpdateRequest{
1156 Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
1157 ChanPoint: channelPoint,
1158 },
1159 BaseFeeMsat: int64(updateChannelRequest.ForwardingFeeBaseMsat),
1160 FeeRatePpm: updateChannelRequest.ForwardingFeeProportionalMillionths,
1161 TimeLockDelta: nodePolicy.TimeLockDelta,
1162 MaxHtlcMsat: nodePolicy.MaxHtlcMsat,
1163 })
1164 if err != nil {
1165 logger.Logger.WithField("request", updateChannelRequest).WithError(err).Error("Failed to update channel")
1166 return err
1167 }
1168
1169 return nil
1170 }
1171
1172 func (svc *LNDService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) error {
1173 logger.Logger.WithFields(logrus.Fields{
1174 "request": closeChannelRequest,
1175 }).Info("Closing Channel")
1176
1177 resp, err := svc.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{})
1178 if err != nil {
1179 logger.Logger.WithError(err).Error("Failed to fetch channels")
1180 return err
1181 }
1182
1183 var foundChannel *lnrpc.Channel
1184 for _, channel := range resp.Channels {
1185 if strconv.FormatUint(channel.ChanId, 10) == closeChannelRequest.ChannelId {
1186
1187 foundChannel = channel
1188 break
1189 }
1190 }
1191
1192 if foundChannel == nil {
1193 logger.Logger.WithField("request", closeChannelRequest).Error("Failed to find channel to close")
1194 return errors.New("no channel exists with the given id")
1195 }
1196
1197 channelPoint, err := svc.parseChannelPoint(foundChannel.ChannelPoint)
1198 if err != nil {
1199 return err
1200 }
1201
1202 stream, err := svc.client.CloseChannel(ctx, &lnrpc.CloseChannelRequest{
1203 ChannelPoint: channelPoint,
1204 Force: closeChannelRequest.Force,
1205 })
1206 if err != nil {
1207 logger.Logger.WithField("request", closeChannelRequest).WithError(err).Error("Failed to close channel")
1208 return err
1209 }
1210
1211 for {
1212 resp, err := stream.Recv()
1213 if err != nil {
1214 return err
1215 }
1216
1217 switch update := resp.Update.(type) {
1218 case *lnrpc.CloseStatusUpdate_ClosePending:
1219 closingHash := update.ClosePending.Txid
1220 txid, err := chainhash.NewHash(closingHash)
1221 if err != nil {
1222 return err
1223 }
1224 logger.Logger.WithFields(logrus.Fields{
1225 "closingTxid": txid.String(),
1226 }).Info("Channel close pending")
1227 // TODO: return the closing tx id or fire an event
1228 return nil
1229 }
1230 }
1231 }
1232
1233 func (svc *LNDService) GetNewOnchainAddress(ctx context.Context) (string, error) {
1234 resp, err := svc.client.NewAddress(ctx, &lnrpc.NewAddressRequest{
1235 Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
1236 })
1237 if err != nil {
1238 logger.Logger.WithError(err).Error("Failed to generate onchain address")
1239 return "", err
1240 }
1241 return resp.Address, nil
1242 }
1243
1244 func (svc *LNDService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
1245 balances, err := svc.client.WalletBalance(ctx, &lnrpc.WalletBalanceRequest{})
1246 if err != nil {
1247 logger.Logger.WithError(err).Error("Failed to fetch wallet balance")
1248 return nil, err
1249 }
1250 pendingChannels, err := svc.client.PendingChannels(ctx, &lnrpc.PendingChannelsRequest{})
1251 if err != nil {
1252 logger.Logger.WithError(err).Error("Failed to list pending channels")
1253 return nil, err
1254 }
1255 pendingBalancesFromChannelClosures := uint64(0)
1256 pendingBalancesDetails := []lnclient.PendingBalanceDetails{}
1257 for _, closingChannel := range pendingChannels.WaitingCloseChannels {
1258 pendingBalancesFromChannelClosures += uint64(closingChannel.LimboBalance)
1259 if closingChannel.Channel != nil {
1260 channelPoint, err := svc.parseChannelPoint(closingChannel.Channel.ChannelPoint)
1261 if err != nil {
1262 return nil, err
1263 }
1264 pendingBalancesDetails = append(pendingBalancesDetails, lnclient.PendingBalanceDetails{
1265 NodeId: closingChannel.Channel.RemoteNodePub,
1266 AmountSat: uint64(closingChannel.LimboBalance),
1267 FundingTxId: channelPoint.GetFundingTxidStr(),
1268 FundingTxVout: channelPoint.GetOutputIndex(),
1269 })
1270 }
1271 }
1272 logger.Logger.WithFields(logrus.Fields{
1273 "balances": balances,
1274 }).Debug("Listed Balances")
1275 return &lnclient.OnchainBalanceResponse{
1276 SpendableSat: int64(balances.ConfirmedBalance),
1277 TotalSat: int64(balances.TotalBalance),
1278 ReservedSat: int64(balances.ReservedBalanceAnchorChan),
1279 PendingBalancesFromChannelClosuresSat: pendingBalancesFromChannelClosures,
1280 PendingBalancesDetails: pendingBalancesDetails,
1281 PendingSweepBalancesDetails: []lnclient.PendingBalanceDetails{},
1282 InternalBalances: map[string]interface{}{
1283 "balances": balances,
1284 "pending_channels": pendingChannels,
1285 },
1286 }, nil
1287 }
1288
1289 func (svc *LNDService) RedeemOnchainFunds(ctx context.Context, toAddress string, amountSat uint64, feeRate *uint64, sendAll bool) (txId string, err error) {
1290 sendCoinsRequest := &lnrpc.SendCoinsRequest{
1291 Addr: toAddress,
1292 SendAll: sendAll,
1293 Amount: int64(amountSat),
1294 }
1295
1296 if feeRate != nil {
1297 sendCoinsRequest.SatPerVbyte = *feeRate
1298 } else {
1299 sendCoinsRequest.TargetConf = 1
1300 }
1301
1302 resp, err := svc.client.SendCoins(ctx, sendCoinsRequest)
1303 if err != nil {
1304 logger.Logger.WithError(err).Error("Failed to send onchain funds")
1305 return "", err
1306 }
1307 return resp.Txid, nil
1308 }
1309
1310 func (svc *LNDService) ResetRouter(key string) error {
1311 return nil
1312 }
1313
1314 func (svc *LNDService) SignMessage(ctx context.Context, message string) (string, error) {
1315 resp, err := svc.client.SignMessage(ctx, &lnrpc.SignMessageRequest{Msg: []byte(message)})
1316 if err != nil {
1317 logger.Logger.WithError(err).Error("Failed to sign message")
1318 return "", err
1319 }
1320
1321 return resp.Signature, nil
1322 }
1323
1324 func (svc *LNDService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
1325 resp, err := svc.client.ListPeers(ctx, &lnrpc.ListPeersRequest{})
1326 if err != nil {
1327 logger.Logger.WithError(err).Error("Failed to list peers")
1328 return nil, err
1329 }
1330 ret := make([]lnclient.PeerDetails, 0, len(resp.Peers))
1331 for _, peer := range resp.Peers {
1332 ret = append(ret, lnclient.PeerDetails{
1333 NodeId: peer.PubKey,
1334 Address: peer.Address,
1335 IsPersisted: true,
1336 IsConnected: true,
1337 })
1338 }
1339 return ret, nil
1340 }
1341
1342 func (svc *LNDService) GetNetworkGraph(ctx context.Context, nodeIds []string) (lnclient.NetworkGraphResponse, error) {
1343 graph, err := svc.client.DescribeGraph(ctx, &lnrpc.ChannelGraphRequest{})
1344 if err != nil {
1345 logger.Logger.WithError(err).Error("Failed to fetch network graph")
1346 return "", err
1347 }
1348
1349 type NodeInfoWithId struct {
1350 Node *lnrpc.LightningNode `json:"node"`
1351 NodeId string `json:"nodeId"`
1352 }
1353
1354 nodes := []NodeInfoWithId{}
1355 channels := []*lnrpc.ChannelEdge{}
1356
1357 for _, node := range graph.Nodes {
1358 if slices.Contains(nodeIds, node.PubKey) {
1359 nodes = append(nodes, NodeInfoWithId{
1360 Node: node,
1361 NodeId: node.PubKey,
1362 })
1363 }
1364 }
1365
1366 for _, edge := range graph.Edges {
1367 if slices.Contains(nodeIds, edge.Node1Pub) || slices.Contains(nodeIds, edge.Node2Pub) {
1368 channels = append(channels, edge)
1369 }
1370 }
1371
1372 networkGraph := map[string]interface{}{
1373 "nodes": nodes,
1374 "channels": channels,
1375 }
1376 return networkGraph, nil
1377 }
1378
1379 func (svc *LNDService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
1380 resp, err := svc.client.GetDebugInfo(ctx, &lnrpc.GetDebugInfoRequest{})
1381 if err != nil {
1382 logger.Logger.WithError(err).Error("Failed to fetch debug info")
1383 return nil, err
1384 }
1385 jsonBytes, err := json.MarshalIndent(resp.Log, "", "")
1386 if err != nil {
1387 return nil, err
1388 }
1389
1390 jsonLength := len(jsonBytes)
1391 start := jsonLength - maxLen
1392 if maxLen == 0 || start < 0 {
1393 start = 0
1394 }
1395 slicedBytes := jsonBytes[start:]
1396
1397 return slicedBytes, nil
1398 }
1399
1400 func (svc *LNDService) GetBalances(ctx context.Context, includeInactiveChannels bool) (*lnclient.BalancesResponse, error) {
1401 onchainBalance, err := svc.GetOnchainBalance(ctx)
1402 if err != nil {
1403 return nil, err
1404 }
1405
1406 var totalReceivable int64 = 0
1407 var totalSpendable int64 = 0
1408 var nextMaxReceivable int64 = 0
1409 var nextMaxSpendable int64 = 0
1410 var nextMaxReceivableMPP int64 = 0
1411 var nextMaxSpendableMPP int64 = 0
1412
1413 resp, err := svc.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{})
1414 if err != nil {
1415 logger.Logger.WithError(err).Error("Failed to fetch channels")
1416 return nil, err
1417 }
1418
1419 for _, channel := range resp.Channels {
1420 // Unnecessary since ListChannels only returns active channels
1421 if channel.Active || includeInactiveChannels {
1422 channelSpendable := max(channel.LocalBalance*1000-int64(channel.LocalConstraints.ChanReserveSat*1000), 0)
1423 channelReceivable := max(channel.RemoteBalance*1000-int64(channel.RemoteConstraints.ChanReserveSat*1000), 0)
1424
1425 // spending or receiving amount may be constrained by channel configuration (e.g. ACINQ does this)
1426 channelConstrainedSpendable := min(channelSpendable, int64(channel.RemoteConstraints.MaxPendingAmtMsat))
1427 channelConstrainedReceivable := min(channelReceivable, int64(channel.LocalConstraints.MaxPendingAmtMsat))
1428
1429 nextMaxSpendable = max(nextMaxSpendable, channelConstrainedSpendable)
1430 nextMaxReceivable = max(nextMaxReceivable, channelConstrainedReceivable)
1431
1432 nextMaxSpendableMPP += channelConstrainedSpendable
1433 nextMaxReceivableMPP += channelConstrainedReceivable
1434
1435 // these are what the wallet can send and receive, but not necessarily in one go
1436 totalSpendable += channelSpendable
1437 totalReceivable += channelReceivable
1438 }
1439 }
1440
1441 return &lnclient.BalancesResponse{
1442 Onchain: *onchainBalance,
1443 Lightning: lnclient.LightningBalanceResponse{
1444 TotalSpendableMsat: totalSpendable,
1445 TotalReceivableMsat: totalReceivable,
1446 NextMaxSpendableMsat: nextMaxSpendable,
1447 NextMaxReceivableMsat: nextMaxReceivable,
1448 NextMaxSpendableMPPMsat: nextMaxSpendableMPP,
1449 NextMaxReceivableMPPMsat: nextMaxReceivableMPP,
1450 },
1451 }, nil
1452 }
1453
1454 func (svc *LNDService) GetStorageDir() (string, error) {
1455 return "", nil
1456 }
1457
1458 func (svc *LNDService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.NodeStatus, err error) {
1459 info, err := svc.GetInfo(ctx)
1460 if err != nil {
1461 return nil, err
1462 }
1463 nodeInfo, err := svc.client.GetNodeInfo(ctx, &lnrpc.NodeInfoRequest{
1464 PubKey: svc.GetPubkey(),
1465 })
1466 if err != nil {
1467 logger.Logger.WithError(err).Error("Failed to fetch node info")
1468 return nil, err
1469 }
1470 state, err := svc.client.GetState(ctx, &lnrpc.GetStateRequest{})
1471 if err != nil {
1472 logger.Logger.WithError(err).Error("Failed to fetch wallet state")
1473 return nil, err
1474 }
1475 return &lnclient.NodeStatus{
1476 IsReady: true, // Assuming that, if GetNodeInfo() succeeds, the node is online and accessible.
1477 InternalNodeStatus: map[string]interface{}{
1478 "info": info,
1479 "node_info": nodeInfo,
1480 "wallet_state": state.GetState().String(),
1481 },
1482 }, nil
1483 }
1484
1485 func (svc *LNDService) DisconnectPeer(ctx context.Context, peerId string) error {
1486 _, err := svc.client.DisconnectPeer(ctx, &lnrpc.DisconnectPeerRequest{PubKey: peerId})
1487 if err != nil {
1488 logger.Logger.WithError(err).Error("Failed to disconnect peer")
1489 return err
1490 }
1491
1492 return nil
1493 }
1494
1495 func (svc *LNDService) UpdateLastWalletSyncRequest() {}
1496
1497 func (svc *LNDService) GetSupportedNIP47Methods() []string {
1498 return []string{
1499 models.PAY_INVOICE_METHOD,
1500 models.PAY_KEYSEND_METHOD,
1501 models.GET_BALANCE_METHOD,
1502 models.GET_BUDGET_METHOD,
1503 models.GET_INFO_METHOD,
1504 models.MAKE_INVOICE_METHOD,
1505 models.LOOKUP_INVOICE_METHOD,
1506 models.LIST_TRANSACTIONS_METHOD,
1507 models.MULTI_PAY_INVOICE_METHOD,
1508 models.MULTI_PAY_KEYSEND_METHOD,
1509 models.SIGN_MESSAGE_METHOD,
1510 models.MAKE_HOLD_INVOICE_METHOD,
1511 models.SETTLE_HOLD_INVOICE_METHOD,
1512 models.CANCEL_HOLD_INVOICE_METHOD,
1513 }
1514 }
1515
1516 func (svc *LNDService) GetSupportedNIP47NotificationTypes() []string {
1517 return []string{notifications.PAYMENT_RECEIVED_NOTIFICATION, notifications.PAYMENT_SENT_NOTIFICATION, notifications.HOLD_INVOICE_ACCEPTED_NOTIFICATION}
1518 }
1519
1520 func (svc *LNDService) GetPubkey() string {
1521 return svc.nodeInfo.Pubkey
1522 }
1523
1524 func fetchNodeInfo(ctx context.Context, client *wrapper.LNDWrapper) (*lnclient.NodeInfo, error) {
1525 resp, err := client.GetInfo(ctx, &lnrpc.GetInfoRequest{})
1526 if err != nil {
1527 logger.Logger.WithError(err).Error("Failed to fetch node info")
1528 return nil, err
1529 }
1530 network := resp.Chains[0].Network
1531 if network == "mainnet" {
1532 network = "bitcoin"
1533 }
1534 return &lnclient.NodeInfo{
1535 Alias: resp.Alias,
1536 Color: resp.Color,
1537 Pubkey: resp.IdentityPubkey,
1538 Network: network,
1539 BlockHeight: resp.BlockHeight,
1540 BlockHash: resp.BlockHash,
1541 }, nil
1542 }
1543
1544 func lndPaymentToTransaction(payment *lnrpc.Payment) (*lnclient.Transaction, error) {
1545 var expiresAt *int64
1546 var description string
1547 var descriptionHash string
1548 if payment.PaymentRequest != "" {
1549 paymentRequest, err := decodepay.Decodepay(strings.ToLower(payment.PaymentRequest))
1550 if err != nil {
1551 logger.Logger.WithFields(logrus.Fields{
1552 "bolt11": payment.PaymentRequest,
1553 }).WithError(err).Error("Failed to decode bolt11 invoice")
1554 return nil, err
1555 }
1556 expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
1557 expiresAt = &expiresAtUnix
1558 description = paymentRequest.Description
1559 descriptionHash = paymentRequest.DescriptionHash
1560 }
1561
1562 var settledAt *int64
1563 if payment.Status == lnrpc.Payment_SUCCEEDED {
1564 // FIXME: how to get the actual settled at time?
1565 settledAtUnix := time.Unix(0, payment.CreationTimeNs).Unix()
1566 settledAt = &settledAtUnix
1567 }
1568
1569 return &lnclient.Transaction{
1570 Type: "outgoing",
1571 Invoice: payment.PaymentRequest,
1572 Preimage: payment.PaymentPreimage,
1573 PaymentHash: payment.PaymentHash,
1574 AmountMsat: payment.ValueMsat,
1575 FeesPaidMsat: payment.FeeMsat,
1576 CreatedAt: time.Unix(0, payment.CreationTimeNs).Unix(),
1577 Description: description,
1578 DescriptionHash: descriptionHash,
1579 ExpiresAt: expiresAt,
1580 SettledAt: settledAt,
1581 // TODO: Metadata: (e.g. keysend),
1582 }, nil
1583 }
1584
1585 func lndInvoiceToTransaction(invoice *lnrpc.Invoice) *lnclient.Transaction {
1586 var settledAt *int64
1587 preimage := hex.EncodeToString(invoice.RPreimage)
1588 metadata := map[string]interface{}{}
1589
1590 if invoice.State == lnrpc.Invoice_SETTLED {
1591 settledAt = &invoice.SettleDate
1592 }
1593 var expiresAt *int64
1594 if invoice.Expiry > 0 {
1595 expiresAtUnix := invoice.CreationDate + invoice.Expiry
1596 expiresAt = &expiresAtUnix
1597 }
1598
1599 if invoice.IsKeysend {
1600 tlvRecords := []lnclient.TLVRecord{}
1601 for _, htlc := range invoice.Htlcs {
1602 for key, value := range htlc.CustomRecords {
1603 tlvRecords = append(tlvRecords, lnclient.TLVRecord{
1604 Type: key,
1605 Value: hex.EncodeToString(value),
1606 })
1607 }
1608 }
1609 metadata["tlv_records"] = tlvRecords
1610 }
1611
1612 return &lnclient.Transaction{
1613 Type: "incoming",
1614 Invoice: invoice.PaymentRequest,
1615 Description: invoice.Memo,
1616 DescriptionHash: hex.EncodeToString(invoice.DescriptionHash),
1617 Preimage: preimage,
1618 PaymentHash: hex.EncodeToString(invoice.RHash),
1619 AmountMsat: invoice.ValueMsat,
1620 CreatedAt: invoice.CreationDate,
1621 SettledAt: settledAt,
1622 ExpiresAt: expiresAt,
1623 Metadata: metadata,
1624 }
1625 }
1626
1627 func (svc *LNDService) GetCustomNodeCommandDefinitions() []lnclient.CustomNodeCommandDef {
1628 return nil
1629 }
1630
1631 func (svc *LNDService) ExecuteCustomNodeCommand(ctx context.Context, command *lnclient.CustomNodeCommandRequest) (*lnclient.CustomNodeCommandResponse, error) {
1632 return nil, nil
1633 }
1634
1635 func (svc *LNDService) MakeOffer(ctx context.Context, description string) (string, error) {
1636 return "", errors.New("not supported")
1637 }
1638
1639 func (svc *LNDService) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
1640 resp, err := svc.client.GetTransactions(ctx, &lnrpc.GetTransactionsRequest{})
1641 if err != nil {
1642 logger.Logger.WithError(err).Error("Failed to get onchain transactions")
1643 return nil, err
1644 }
1645
1646 transactions := []lnclient.OnchainTransaction{}
1647 for _, tx := range resp.Transactions {
1648 state := "unconfirmed"
1649 if tx.NumConfirmations > 0 {
1650 state = "confirmed"
1651 }
1652
1653 amountSat := tx.Amount
1654 txType := "incoming"
1655 if tx.Amount < 0 {
1656 amountSat = -amountSat
1657 txType = "outgoing"
1658 }
1659
1660 transactions = append(transactions, lnclient.OnchainTransaction{
1661 AmountSat: uint64(amountSat),
1662 CreatedAt: uint64(tx.TimeStamp),
1663 State: state,
1664 Type: txType,
1665 NumConfirmations: uint32(tx.NumConfirmations),
1666 TxId: tx.TxHash,
1667 })
1668 }
1669 sort.SliceStable(transactions, func(i, j int) bool {
1670 return transactions[i].CreatedAt > transactions[j].CreatedAt
1671 })
1672 return transactions, nil
1673 }
1674