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