ldk.go raw

   1  package ldk
   2  
   3  import (
   4  	"context"
   5  	"crypto/sha256"
   6  	"encoding/hex"
   7  	"encoding/json"
   8  	"errors"
   9  	"fmt"
  10  	"math"
  11  	"net"
  12  	"net/http"
  13  	"net/url"
  14  	"os"
  15  	"path/filepath"
  16  	"slices"
  17  	"sort"
  18  	"strconv"
  19  	"strings"
  20  	"sync"
  21  	"time"
  22  
  23  	"github.com/getAlby/ldk-node-go/ldk_node"
  24  	"github.com/tyler-smith/go-bip32"
  25  
  26  	// "github.com/getAlby/hub/ldk_node"
  27  
  28  	decodepay "github.com/nbd-wtf/ln-decodepay"
  29  	"github.com/sirupsen/logrus"
  30  
  31  	"github.com/getAlby/hub/alby"
  32  	"github.com/getAlby/hub/config"
  33  	"github.com/getAlby/hub/events"
  34  	"github.com/getAlby/hub/lnclient"
  35  	"github.com/getAlby/hub/logger"
  36  	"github.com/getAlby/hub/lsp"
  37  	"github.com/getAlby/hub/nip47/models"
  38  	"github.com/getAlby/hub/nip47/notifications"
  39  	"github.com/getAlby/hub/service/keys"
  40  	"github.com/getAlby/hub/transactions"
  41  )
  42  
  43  type LDKService struct {
  44  	workdir                            string
  45  	node                               *ldk_node.Node
  46  	ldkEventBroadcaster                LDKEventBroadcaster
  47  	cancel                             context.CancelFunc
  48  	ctx                                context.Context
  49  	network                            string
  50  	eventPublisher                     events.EventPublisher
  51  	syncing                            bool
  52  	lastFullSync                       time.Time
  53  	lastFeeEstimatesSync               time.Time
  54  	cfg                                config.Config
  55  	lastWalletSyncRequest              time.Time
  56  	redeemedOnchainFundsWithinThisSync bool
  57  	pubkey                             string
  58  	lsps2Pubkey                        string
  59  	lsps2Address                       string
  60  	lsps2InfoMu                        sync.Mutex
  61  	lsps2InfoFetchedAt                 time.Time
  62  	lsps2MinPaymentSizeMsat            *uint64
  63  	lsps2MaxPaymentSizeMsat            *uint64
  64  	lsps2OpeningFeeParamsMenu          []ldk_node.Lsps2OpeningFeeParams
  65  	shuttingDown                       bool
  66  	eventHandlingMutex                 sync.Mutex
  67  }
  68  
  69  const resetRouterKey = "ResetRouter"
  70  const maxInvoiceExpiry = 24 * time.Hour
  71  const lsps2InfoCacheTTL = 60 * time.Minute
  72  
  73  // cached opening fee params must be at most this old when used to derive the
  74  // maximum LSP fee for a new JIT channel invoice
  75  const lsps2FeeCapCacheTTL = 1 * time.Minute
  76  
  77  // absolute ceiling on the LSPS2 opening fee accepted for a JIT channel,
  78  // regardless of the fee menu the LSP advertises: the greater of a base amount
  79  // and a percentage of the payment, so small payments can absorb the fixed
  80  // cost of a channel open while larger payments cannot be overcharged.
  81  const lsps2MaxOpeningFeeBaseMsat = 5_000_000
  82  const lsps2MaxOpeningFeePercent = 10
  83  
  84  func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, vssToken string, setStartupState func(startupState string), channelPeerSuggestions []alby.ChannelPeerSuggestion) (result lnclient.LNClient, err error) {
  85  	if mnemonic == "" || workDir == "" {
  86  		return nil, errors.New("one or more required LDK configuration are missing")
  87  	}
  88  
  89  	setStartupState("Configuring node")
  90  
  91  	// create dir if not exists
  92  	newpath := filepath.Join(workDir)
  93  	err = os.MkdirAll(newpath, os.ModePerm)
  94  	if err != nil {
  95  		logger.Logger.WithError(err).Error("Failed to create LDK working dir")
  96  		return nil, err
  97  	}
  98  
  99  	ldkConfig := ldk_node.DefaultConfig()
 100  
 101  	ldkConfig.TrustedPeers0conf = []string{
 102  		lsp.OlympusLSP().Pubkey,
 103  		lsp.MegalithLSP().Pubkey,
 104  		"02b4552a7a85274e4da01a7c71ca57407181752e8568b31d51f13c111a2941dce3", // LNServer_Wave
 105  		"038ba8f67ba8ff5c48764cdd3251c33598d55b203546d08a8f0ec9dcd9f27e3637", // Flashsats
 106  		"03a5c38d0dfd2dd1ebe679c308788c468a0186720bb33973b1c56710ffe6696c08", // BHODL
 107  
 108  		// Mutinynet
 109  		lsp.OlympusMutinynetLSP().Pubkey,
 110  		lsp.MegalithMutinynetLSP().Pubkey,
 111  		"03f726f240f0391448fb31c33e130ecc9708c9137e1f4e77b5d17d5dec74b0dd1e", // flashsats
 112  	}
 113  
 114  	// rather than fully trusting our LSPs, we set the channel reserve to 0.
 115  	// this allows us to receive incoming channels without any on-chain balance
 116  	// but if the user has 0 on-chain balance when the channel is closed,
 117  	// we rely on the counterparty to bump the transaction.
 118  	// It's also possible in rare situations the counterparty can take
 119  	// funds if the channel was closed due to a stuck HTLC.
 120  	// Therefore, the user SHOULD add some on-chain funds to prevent this.
 121  	ldkConfig.AnchorChannelsConfig.PerChannelReserveSats = 0
 122  	ldkConfig.AnchorChannelsConfig.TrustedPeersNoReserve = []string{
 123  		/*lsp.OlympusLSP().Pubkey,
 124  		lsp.AlbyPlebsLSP().Pubkey,
 125  		lsp.MegalithLSP().Pubkey,
 126  		"02b4552a7a85274e4da01a7c71ca57407181752e8568b31d51f13c111a2941dce3", // LNServer_Wave
 127  		"0296b2db342fcf87ea94d981757fdf4d3e545bd5cef4919f58b5d38dfdd73bf5c9", // blocktank
 128  		"038ba8f67ba8ff5c48764cdd3251c33598d55b203546d08a8f0ec9dcd9f27e3637", // flashsats
 129  		"0370a5392cd7c81ff5128fa656ee6db0c4d11c778fcd6cb98cb6ba3b48394f5705", // lqwd
 130  
 131  		// Mutinynet
 132  		lsp.AlbyMutinynetPlebsLSP().Pubkey,
 133  		lsp.OlympusMutinynetLSP().Pubkey,
 134  		lsp.MegalithMutinynetLSP().Pubkey,
 135  		"0296820bbba5bd33719962bafd69996ee89e03ce7164d8f368cbb85463f5f47876", // flashsats
 136  		"035e8a9034a8c68f219aacadae748c7a3cd719109309db39b09886e5ff17696b1b", // lqwd*/
 137  	}
 138  
 139  	listeningAddresses := strings.Split(cfg.GetEnv().LDKListeningAddresses, ",")
 140  	ldkConfig.ListeningAddresses = &listeningAddresses
 141  	if cfg.GetEnv().LDKAnnouncementAddresses != "" {
 142  		announcementAddresses := strings.Split(cfg.GetEnv().LDKAnnouncementAddresses, ",")
 143  		ldkConfig.AnnouncementAddresses = &announcementAddresses
 144  	}
 145  
 146  	logLevel, err := strconv.Atoi(cfg.GetEnv().LDKLogLevel)
 147  	if err != nil {
 148  		// If parsing log level fails we default to info log level
 149  		logLevel = int(logrus.InfoLevel)
 150  	}
 151  
 152  	ldkLogger, err := NewLDKLogger(logrus.Level(logLevel), cfg.GetEnv().LogToFile, workDir)
 153  	if err != nil {
 154  		return nil, err
 155  	}
 156  	ldkConfig.TransientNetworkGraph = cfg.GetEnv().LDKTransientNetworkGraph
 157  
 158  	alias, _ := cfg.Get("NodeAlias", "")
 159  	if alias == "" {
 160  		alias = "Alby Hub"
 161  	}
 162  
 163  	builder := ldk_node.BuilderFromConfig(ldkConfig)
 164  	builder.SetCustomLogger(ldkLogger)
 165  	builder.SetNodeAlias(alias)
 166  	builder.SetEntropyBip39Mnemonic(mnemonic, nil)
 167  
 168  	liquiditySourceLsps2 := cfg.GetEnv().LDKLiquiditySourceLsps2
 169  	network := cfg.GetNetwork()
 170  
 171  	// if no explicit override, try a matching LSPS2 suggestion for this network
 172  	if liquiditySourceLsps2 == "" {
 173  		for _, suggestion := range channelPeerSuggestions {
 174  			if suggestion.PaymentMethod == "lightning" &&
 175  				suggestion.Type == lsp.LSP_TYPE_LSPS2 &&
 176  				suggestion.Network == network &&
 177  				suggestion.NodeAddress != "" {
 178  				liquiditySourceLsps2 = suggestion.NodeAddress
 179  				break
 180  			}
 181  		}
 182  	}
 183  
 184  	// fall back to a hardcoded per-network default
 185  	if liquiditySourceLsps2 == "" {
 186  		switch network {
 187  		case "signet":
 188  			// Megalith LSP 2 (Mutinynet)
 189  			liquiditySourceLsps2 = "03e30fda71887a916ef5548a4d02b06fe04aaa1a8de9e24134ce7f139cf79d7579@64.23.192.68:9736"
 190  		case "bitcoin":
 191  			// Megalith LSP 2
 192  			liquiditySourceLsps2 = "034066e29e402d9cf55af1ae1026cc5adf92eed1e0e421785442f53717ad1453b0@64.23.159.177:9735"
 193  		}
 194  	}
 195  
 196  	lsps2Pubkey, lsps2Address := parseLiquiditySourceLsps2(liquiditySourceLsps2)
 197  
 198  	if lsps2Pubkey != "" {
 199  		builder.SetLiquiditySourceLsps2(lsps2Pubkey, lsps2Address, nil)
 200  	}
 201  
 202  	switch network {
 203  	case "signet":
 204  		builder.SetNetwork(ldk_node.NetworkSignet)
 205  	case "regtest":
 206  		builder.SetNetwork(ldk_node.NetworkRegtest)
 207  	case "testnet":
 208  		builder.SetNetwork(ldk_node.NetworkSignet)
 209  	default:
 210  		builder.SetNetwork(ldk_node.NetworkBitcoin)
 211  	}
 212  
 213  	var chainSource string
 214  	if cfg.GetEnv().LDKBitcoindRpcHost != "" {
 215  		logger.Logger.WithFields(logrus.Fields{
 216  			"rpc_host": cfg.GetEnv().LDKBitcoindRpcHost,
 217  			"rpc_port": cfg.GetEnv().LDKBitcoindRpcPort,
 218  		}).Info("Using LDK node bitcoin RPC chain source")
 219  		port, err := strconv.ParseUint(cfg.GetEnv().LDKBitcoindRpcPort, 10, 16)
 220  		if err != nil {
 221  			return nil, err
 222  		}
 223  		builder.SetChainSourceBitcoindRpc(cfg.GetEnv().LDKBitcoindRpcHost, uint16(port), cfg.GetEnv().LDKBitcoindRpcUser, cfg.GetEnv().LDKBitcoindRpcPassword)
 224  		chainSource = "bitcoind"
 225  	} else if cfg.GetEnv().LDKElectrumServer != "" {
 226  		builder.SetChainSourceElectrum(cfg.GetEnv().LDKElectrumServer, &ldk_node.ElectrumSyncConfig{
 227  			// turn off background sync - we manage syncs ourselves
 228  			BackgroundSyncConfig: nil,
 229  		})
 230  		chainSource = "electrum"
 231  	} else {
 232  		logger.Logger.WithFields(logrus.Fields{
 233  			"esplora_url": cfg.GetEnv().LDKEsploraServer,
 234  		}).Info("Using LDK node esplora chain source")
 235  		builder.SetChainSourceEsplora(cfg.GetEnv().LDKEsploraServer, &ldk_node.EsploraSyncConfig{
 236  			// turn off background sync - we manage syncs ourselves
 237  			BackgroundSyncConfig: nil,
 238  		})
 239  		chainSource = "esplora"
 240  	}
 241  
 242  	if cfg.GetEnv().LDKGossipSource != "" {
 243  		logger.Logger.WithField("gossipSource", cfg.GetEnv().LDKGossipSource).Warn("LDK RGS instance set")
 244  		builder.SetGossipSourceRgs(cfg.GetEnv().LDKGossipSource)
 245  	}
 246  	builder.SetStorageDirPath(filepath.Join(newpath, "./storage"))
 247  
 248  	migrateStorage, _ := cfg.Get("LdkMigrateStorage", "")
 249  	clearMigrateStorageConfigValue := false
 250  	if migrateStorage == "VSS" {
 251  		clearMigrateStorageConfigValue = true
 252  		if vssToken == "" {
 253  			return nil, errors.New("migration enabled but no vss token found")
 254  		}
 255  		builder.MigrateStorage(ldk_node.MigrateStorageVss)
 256  	}
 257  
 258  	resetStateRequest := getResetStateRequest(cfg)
 259  	if resetStateRequest != nil {
 260  		builder.ResetState(*resetStateRequest)
 261  	}
 262  
 263  	logger.Logger.WithFields(logrus.Fields{
 264  		"migrate_storage":     migrateStorage,
 265  		"vss_enabled":         vssToken != "",
 266  		"node_alias":          alias,
 267  		"listening_addresses": listeningAddresses,
 268  		"chain_source":        chainSource,
 269  	}).Info("Creating LDK node")
 270  	setStartupState("Loading node data...")
 271  	var node *ldk_node.Node
 272  	if vssToken != "" {
 273  		node, err = builder.BuildWithVssStoreAndFixedHeaders(cfg.GetEnv().LDKVssUrl, "albyhub", map[string]string{
 274  			"Authorization": fmt.Sprintf("Bearer %s", vssToken),
 275  		})
 276  	} else {
 277  		node, err = builder.Build()
 278  	}
 279  
 280  	if err != nil {
 281  		logger.Logger.WithError(err).Error("Failed to create LDK node")
 282  		return nil, err
 283  	}
 284  
 285  	logger.Logger.WithFields(logrus.Fields{}).Info("LDK node created")
 286  
 287  	if clearMigrateStorageConfigValue {
 288  		err = cfg.SetUpdate("LdkMigrateStorage", "", "")
 289  		if err != nil {
 290  			logger.Logger.WithError(err).Error("Failed to clear LDK migrate storage config value")
 291  			return nil, err
 292  		}
 293  	}
 294  
 295  	ldkEventConsumer := make(chan *ldk_node.Event)
 296  	ldkCtx, cancel := context.WithCancel(ctx)
 297  	ldkEventBroadcaster := NewLDKEventBroadcaster(ldkCtx, ldkEventConsumer)
 298  	nodeId := node.NodeId()
 299  
 300  	ls := LDKService{
 301  		workdir:             newpath,
 302  		node:                node,
 303  		cancel:              cancel,
 304  		ldkEventBroadcaster: ldkEventBroadcaster,
 305  		network:             network,
 306  		eventPublisher:      eventPublisher,
 307  		cfg:                 cfg,
 308  		pubkey:              nodeId,
 309  		lsps2Pubkey:         lsps2Pubkey,
 310  		lsps2Address:        lsps2Address,
 311  		ctx:                 ldkCtx,
 312  	}
 313  
 314  	eventPublisher.RegisterSubscriber(&ls)
 315  
 316  	// TODO: remove after 2026-01-01 - we now log to app logs rather than ldk log files
 317  	// this line is just left to cleanup old logs after the update
 318  	deleteOldLDKLogs(filepath.Join(newpath, "./logs"))
 319  
 320  	// check for and forward new LDK events to LDKEventBroadcaster (through ldkEventConsumer)
 321  	go func() {
 322  		for {
 323  			select {
 324  			case <-ldkCtx.Done():
 325  				return
 326  			default:
 327  			}
 328  
 329  			// NextEventAsync parks this goroutine on a Go channel until the next event
 330  			// arrives - unlike WaitNextEvent it does not block an OS thread in FFI.
 331  			// NOTE: the call cannot be cancelled; after shutdown it stays parked until
 332  			// the node emits a final event or the process exits.
 333  			event := node.NextEventAsync()
 334  
 335  			// eventHandlingMutex is held while handling so Shutdown() can wait
 336  			// for in-flight event handling to finish before stopping the node.
 337  			// Events dropped without EventHandled() are redelivered by LDK on
 338  			// the next startup.
 339  			ok := func() bool {
 340  				ls.eventHandlingMutex.Lock()
 341  				defer ls.eventHandlingMutex.Unlock()
 342  
 343  				if ldkCtx.Err() != nil {
 344  					return false
 345  				}
 346  
 347  				ls.handleLdkEvent(&event)
 348  
 349  				select {
 350  				case ldkEventConsumer <- &event:
 351  				case <-ldkCtx.Done():
 352  					return false
 353  				}
 354  
 355  				if err := node.EventHandled(); err != nil {
 356  					logger.Logger.WithError(err).Error("Failed to mark LDK event as handled")
 357  				}
 358  				return true
 359  			}()
 360  			if !ok {
 361  				return
 362  			}
 363  		}
 364  	}()
 365  
 366  	logger.Logger.WithFields(logrus.Fields{
 367  		"nodeId": nodeId,
 368  	}).Info("Starting LDK node...")
 369  
 370  	setStartupState("Starting node...")
 371  
 372  	err = node.Start()
 373  	if err != nil {
 374  		logger.Logger.WithError(err).Error("Failed to start LDK node")
 375  		return nil, err
 376  	}
 377  
 378  	logger.Logger.WithFields(logrus.Fields{
 379  		"nodeId": nodeId,
 380  		"status": node.Status(),
 381  	}).Info("Started LDK node. Syncing wallet...")
 382  
 383  	setStartupState("Syncing node...")
 384  	syncStartTime := time.Now()
 385  	err = node.SyncWallets()
 386  	if err != nil {
 387  		logger.Logger.WithError(err).Error("Failed to sync LDK wallets")
 388  		ls.eventPublisher.Publish(&events.Event{
 389  			Event: "nwc_node_sync_failed",
 390  			Properties: map[string]interface{}{
 391  				"error":        err.Error(),
 392  				"sync_type":    "full",
 393  				"initial_sync": true,
 394  				"node_type":    config.LDKBackendType,
 395  			},
 396  		})
 397  
 398  		shutdownErr := ls.Shutdown()
 399  		if shutdownErr != nil {
 400  			logger.Logger.WithError(shutdownErr).Error("Failed to shutdown LDK node")
 401  		}
 402  
 403  		return nil, err
 404  	}
 405  	ls.lastFullSync = time.Now()
 406  	ls.lastFeeEstimatesSync = time.Now()
 407  
 408  	logger.Logger.WithFields(logrus.Fields{
 409  		"nodeId":   nodeId,
 410  		"status":   node.Status(),
 411  		"duration": math.Ceil(time.Since(syncStartTime).Seconds()),
 412  	}).Info("LDK node synced successfully")
 413  
 414  	// setup background sync
 415  	go func() {
 416  		MIN_SYNC_INTERVAL := 1 * time.Minute
 417  		MIN_FEE_ESTIMATES_SYNC_INTERVAL := 5 * time.Minute
 418  		MAX_SYNC_INTERVAL := 1 * time.Hour // NOTE: this could be increased further (possibly to 6 hours)
 419  		for {
 420  			ls.syncing = false
 421  			select {
 422  			case <-ldkCtx.Done():
 423  				return
 424  			case <-time.After(MIN_SYNC_INTERVAL):
 425  				ls.syncing = true
 426  
 427  				channels := ls.node.ListChannels()
 428  				for _, channel := range channels {
 429  					if channel.Confirmations != nil && channel.ConfirmationsRequired != nil && *channel.Confirmations < *channel.ConfirmationsRequired {
 430  						logger.Logger.WithField("channel_id", channel.UserChannelId).Debug("Using short sync time while opening channel")
 431  						ls.lastWalletSyncRequest = time.Now()
 432  						break
 433  					}
 434  				}
 435  				balances := ls.node.ListBalances()
 436  				for _, balance := range balances.LightningBalances {
 437  					switch balanceType := (balance).(type) {
 438  					case ldk_node.LightningBalanceContentiousClaimable:
 439  						logger.Logger.WithField("channel_id", balanceType.ChannelId).Debug("Using short sync time while balances are contentious claimable after channel closure")
 440  						ls.lastWalletSyncRequest = time.Now()
 441  					}
 442  				}
 443  
 444  				if time.Since(ls.lastWalletSyncRequest) > MIN_SYNC_INTERVAL && time.Since(ls.lastFullSync) < MAX_SYNC_INTERVAL {
 445  
 446  					if time.Since(ls.lastFeeEstimatesSync) < MIN_FEE_ESTIMATES_SYNC_INTERVAL {
 447  						logger.Logger.Debug("Skipping updating fee estimates")
 448  						continue
 449  					}
 450  
 451  					// only update fee estimates
 452  					logger.Logger.Debug("Updating fee estimates")
 453  					err = node.UpdateFeeEstimates()
 454  					if err != nil {
 455  						logger.Logger.WithError(err).Error("Failed to update fee estimates")
 456  						ls.eventPublisher.Publish(&events.Event{
 457  							Event: "nwc_node_sync_failed",
 458  							Properties: map[string]interface{}{
 459  								"error":     err.Error(),
 460  								"sync_type": "fee_estimates",
 461  								"node_type": config.LDKBackendType,
 462  							},
 463  						})
 464  						continue
 465  					}
 466  					ls.lastFeeEstimatesSync = time.Now()
 467  					continue
 468  				}
 469  
 470  				logger.Logger.Debug("Starting full background wallet sync")
 471  				syncStartTime := time.Now()
 472  				err = node.SyncWallets()
 473  
 474  				if err != nil {
 475  					logger.Logger.WithError(err).Error("Failed to sync LDK wallets")
 476  					ls.eventPublisher.Publish(&events.Event{
 477  						Event: "nwc_node_sync_failed",
 478  						Properties: map[string]interface{}{
 479  							"error":     err.Error(),
 480  							"sync_type": "full",
 481  							"node_type": config.LDKBackendType,
 482  						},
 483  					})
 484  
 485  					// try again at next MIN_SYNC_INTERVAL
 486  					continue
 487  				}
 488  
 489  				ls.redeemedOnchainFundsWithinThisSync = false
 490  				ls.lastFullSync = time.Now()
 491  				// fee estimates happens as part of full sync
 492  				ls.lastFeeEstimatesSync = time.Now()
 493  
 494  				logger.Logger.WithFields(logrus.Fields{
 495  					"nodeId":   nodeId,
 496  					"status":   node.Status(),
 497  					"duration": math.Ceil(time.Since(syncStartTime).Seconds()),
 498  				}).Info("LDK node synced successfully")
 499  
 500  				// delete old payments while node is not syncing
 501  				ls.deleteOldLDKPayments()
 502  			}
 503  		}
 504  	}()
 505  
 506  	return &ls, nil
 507  }
 508  
 509  var shutdownMutex sync.Mutex
 510  
 511  func (ls *LDKService) Shutdown() error {
 512  	shutdownMutex.Lock()
 513  	defer shutdownMutex.Unlock()
 514  	if ls.shuttingDown {
 515  		logger.Logger.Debug("LDK client is already shutting down")
 516  		return nil
 517  	}
 518  	ls.shuttingDown = true
 519  	ls.eventPublisher.RemoveSubscriber(ls)
 520  
 521  	logger.Logger.Info("shutting down LDK client")
 522  	logger.Logger.Info("cancelling LDK context")
 523  	ls.cancel()
 524  
 525  	// wait for in-flight LDK event handling to finish - handleLdkEvent makes
 526  	// node calls which must not run once the node is stopped and destroyed.
 527  	// Held until the end of Shutdown; the event loop checks the cancelled
 528  	// context under this mutex before touching the node.
 529  	ls.eventHandlingMutex.Lock()
 530  	defer ls.eventHandlingMutex.Unlock()
 531  
 532  	maxAttempts := 40
 533  	for i := 0; ls.syncing; i++ {
 534  		logger.Logger.WithField("attempt", i).Warn("Waiting for background sync to finish before stopping LDK node...")
 535  		time.Sleep(1 * time.Second)
 536  		if i > maxAttempts {
 537  			logger.Logger.Error("Timed out waiting for background sync to finish before stopping LDK node")
 538  			break
 539  		}
 540  	}
 541  
 542  	logger.Logger.Info("stopping LDK node")
 543  	shutdownChannel := make(chan error)
 544  	go func() {
 545  		shutdownChannel <- ls.node.Stop()
 546  	}()
 547  
 548  	select {
 549  	case err := <-shutdownChannel:
 550  		if err != nil {
 551  			logger.Logger.WithError(err).Error("Failed to stop LDK node")
 552  			// do not return error - we still need to destroy the node
 553  		} else {
 554  			logger.Logger.Info("LDK stop node succeeded")
 555  		}
 556  	case <-time.After(5 * time.Minute):
 557  		logger.Logger.Error("Timeout shutting down LDK node after 5 minutes")
 558  	}
 559  
 560  	logger.Logger.Debug("Destroying LDK node object")
 561  	ls.node.Destroy()
 562  
 563  	logger.Logger.Info("LDK shutdown complete")
 564  
 565  	return nil
 566  }
 567  
 568  func getMaxTotalRoutingFeeLimit(amountMsat uint64) uint64 {
 569  	return transactions.CalculateFeeReserveMsat(amountMsat)
 570  }
 571  
 572  func (ls *LDKService) MakeOffer(ctx context.Context, description string) (string, error) {
 573  	offer, err := ls.node.Bolt12Payment().ReceiveVariableAmount(description, nil)
 574  	if err != nil {
 575  		logger.Logger.WithError(err).Error("Failed to generate BOLT12 offer")
 576  		return "", err
 577  	}
 578  
 579  	logger.Logger.WithField("offer", offer).Info("Generated BOLT12 offer")
 580  	return offer.String(), nil
 581  }
 582  
 583  func (ls *LDKService) SendPaymentSync(invoice string, amountMsat *uint64) (*lnclient.PayInvoiceResponse, error) {
 584  	paymentRequest, err := decodepay.Decodepay(invoice)
 585  	if err != nil {
 586  		logger.Logger.WithFields(logrus.Fields{
 587  			"bolt11": invoice,
 588  		}).WithError(err).Error("Failed to decode bolt11 invoice")
 589  
 590  		return nil, err
 591  	}
 592  
 593  	paymentAmountMsat := uint64(paymentRequest.MSatoshi)
 594  	if amountMsat != nil {
 595  		paymentAmountMsat = *amountMsat
 596  	}
 597  
 598  	maxSpendable := ls.getMaxSpendable()
 599  	if paymentAmountMsat > maxSpendable {
 600  		ls.eventPublisher.Publish(&events.Event{
 601  			Event: "nwc_outgoing_liquidity_required",
 602  			Properties: map[string]interface{}{
 603  				// "amount":         amount / 1000,
 604  				// "max_receivable": maxReceivable,
 605  				// "num_channels":   len(gs.node.ListChannels()),
 606  				"node_type": config.LDKBackendType,
 607  			},
 608  		})
 609  	}
 610  
 611  	paymentStart := time.Now()
 612  	ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
 613  	defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
 614  
 615  	saturationPower := ls.cfg.GetEnv().LDKMaxChannelSaturationPowerOfHalf
 616  	maxPathCount := ls.cfg.GetEnv().LDKMaxPathCount
 617  	maxTotalRoutingFeeMsat := getMaxTotalRoutingFeeLimit(paymentAmountMsat)
 618  
 619  	routeParameters := &ldk_node.RouteParametersConfig{
 620  		MaxTotalRoutingFeeMsat:          &maxTotalRoutingFeeMsat,
 621  		MaxChannelSaturationPowerOfHalf: saturationPower,
 622  		MaxPathCount:                    maxPathCount,
 623  		MaxTotalCltvExpiryDelta:         1008, // TODO: remove and use default
 624  	}
 625  
 626  	invoiceObj, err := ldk_node.Bolt11InvoiceFromStr(invoice)
 627  	if err != nil {
 628  		logger.Logger.WithError(err).Error("ldk failed to parse bolt 11 invoice from string")
 629  		return nil, err
 630  	}
 631  
 632  	var paymentHash string
 633  	if amountMsat == nil {
 634  		paymentHash, err = ls.node.Bolt11Payment().Send(invoiceObj, routeParameters)
 635  	} else {
 636  		paymentHash, err = ls.node.Bolt11Payment().SendUsingAmount(invoiceObj, *amountMsat, routeParameters)
 637  	}
 638  	if err != nil {
 639  		logger.Logger.WithError(err).Error("SendPayment failed")
 640  		return nil, err
 641  	}
 642  	feeMsat := uint64(0)
 643  	preimage := ""
 644  
 645  	for {
 646  		select {
 647  		case <-ls.ctx.Done():
 648  			return nil, ls.ctx.Err()
 649  
 650  		case ev := <-ldkEventSubscription:
 651  			switch event := (*ev).(type) {
 652  			case ldk_node.EventPaymentSuccessful:
 653  				if event.PaymentHash != paymentHash {
 654  					continue
 655  				}
 656  				logger.Logger.WithFields(logrus.Fields{
 657  					"event": event,
 658  				}).Info("Got payment success event")
 659  
 660  				if event.PaymentPreimage == nil {
 661  					logger.Logger.WithField("payment_hash", paymentHash).Error("No payment preimage in payment success event")
 662  					return nil, errors.New("payment preimage not found")
 663  				}
 664  
 665  				preimage = *event.PaymentPreimage
 666  
 667  				if event.FeePaidMsat != nil {
 668  					feeMsat = *event.FeePaidMsat
 669  				}
 670  
 671  				logger.Logger.WithFields(logrus.Fields{
 672  					"duration":     time.Since(paymentStart).Milliseconds(),
 673  					"fee":          feeMsat,
 674  					"payment_hash": event.PaymentHash,
 675  				}).Info("Successful payment")
 676  
 677  				return &lnclient.PayInvoiceResponse{
 678  					Preimage: preimage,
 679  					FeeMsat:  feeMsat,
 680  				}, nil
 681  			case ldk_node.EventPaymentFailed:
 682  				if event.PaymentHash != nil && *event.PaymentHash == paymentHash {
 683  					failureReasonMessage := ls.getPaymentFailReason(&event)
 684  					logger.Logger.WithFields(logrus.Fields{
 685  						"payment_hash": paymentHash,
 686  						"reason":       failureReasonMessage,
 687  					}).Error("Received payment failed event")
 688  					return nil, fmt.Errorf("received payment failed event: %s", failureReasonMessage)
 689  				}
 690  			}
 691  		}
 692  	}
 693  }
 694  
 695  func (ls *LDKService) SendKeysend(amountMsat uint64, destination string, custom_records []lnclient.TLVRecord, preimage string) (*lnclient.PayKeysendResponse, error) {
 696  	paymentStart := time.Now()
 697  	customTlvs := []ldk_node.CustomTlvRecord{}
 698  
 699  	for _, customRecord := range custom_records {
 700  		decodedValue, err := hex.DecodeString(customRecord.Value)
 701  		if err != nil {
 702  			return nil, err
 703  		}
 704  		customTlvs = append(customTlvs, ldk_node.CustomTlvRecord{
 705  			TypeNum: customRecord.Type,
 706  			Value:   decodedValue,
 707  		})
 708  	}
 709  
 710  	ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
 711  	defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
 712  
 713  	saturationPower := ls.cfg.GetEnv().LDKMaxChannelSaturationPowerOfHalf
 714  	maxPathCount := ls.cfg.GetEnv().LDKMaxPathCount
 715  	maxTotalRoutingFeeMsat := getMaxTotalRoutingFeeLimit(amountMsat)
 716  
 717  	routeParameters := &ldk_node.RouteParametersConfig{
 718  		MaxTotalRoutingFeeMsat:          &maxTotalRoutingFeeMsat,
 719  		MaxChannelSaturationPowerOfHalf: saturationPower,
 720  		MaxPathCount:                    maxPathCount,
 721  		MaxTotalCltvExpiryDelta:         1008, // TODO: remove and use default
 722  	}
 723  
 724  	paymentHash, err := ls.node.SpontaneousPayment().SendWithPreimageAndCustomTlvs(amountMsat, destination, customTlvs, preimage, routeParameters)
 725  	if err != nil {
 726  		logger.Logger.WithError(err).Error("Keysend failed")
 727  		return nil, err
 728  	}
 729  	feeMsat := uint64(0)
 730  	for {
 731  		select {
 732  		case <-ls.ctx.Done():
 733  			return nil, ls.ctx.Err()
 734  		case event := <-ldkEventSubscription:
 735  
 736  			eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
 737  			eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
 738  
 739  			if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentHash == paymentHash {
 740  				logger.Logger.Info("Got payment success event")
 741  
 742  				if eventPaymentSuccessful.FeePaidMsat != nil {
 743  					feeMsat = *eventPaymentSuccessful.FeePaidMsat
 744  				}
 745  				logger.Logger.WithFields(logrus.Fields{
 746  					"duration": time.Since(paymentStart).Milliseconds(),
 747  					"fee":      feeMsat,
 748  				}).Info("Successful keysend payment")
 749  				return &lnclient.PayKeysendResponse{
 750  					FeeMsat: feeMsat,
 751  				}, nil
 752  			}
 753  			if isEventPaymentFailedEvent && eventPaymentFailed.PaymentHash != nil && *eventPaymentFailed.PaymentHash == paymentHash {
 754  
 755  				failureReasonMessage := ls.getPaymentFailReason(&eventPaymentFailed)
 756  
 757  				logger.Logger.WithFields(logrus.Fields{
 758  					"payment_hash": paymentHash,
 759  					"reason":       failureReasonMessage,
 760  				}).Error("Received payment failed event")
 761  
 762  				return nil, fmt.Errorf("payment failed event: %s", failureReasonMessage)
 763  			}
 764  		}
 765  	}
 766  
 767  }
 768  
 769  func (ls *LDKService) getMaxReceivable() int64 {
 770  	var receivable int64 = 0
 771  	channels := ls.node.ListChannels()
 772  	for _, channel := range channels {
 773  		if channel.IsUsable {
 774  			receivable += min(int64(channel.InboundCapacityMsat), int64(*channel.InboundHtlcMaximumMsat))
 775  		}
 776  	}
 777  	return int64(receivable)
 778  }
 779  
 780  func (ls *LDKService) hasPublicChannel() bool {
 781  	channels := ls.node.ListChannels()
 782  	for _, channel := range channels {
 783  		if channel.IsAnnounced {
 784  			return true
 785  		}
 786  	}
 787  	return false
 788  }
 789  
 790  func (ls *LDKService) getMaxSpendable() uint64 {
 791  	var spendable uint64 = 0
 792  	channels := ls.node.ListChannels()
 793  	for _, channel := range channels {
 794  		if channel.IsUsable {
 795  			spendable += min(channel.OutboundCapacityMsat, *channel.CounterpartyOutboundHtlcMaximumMsat)
 796  		}
 797  	}
 798  	return spendable
 799  }
 800  
 801  func (ls *LDKService) MakeInvoice(ctx context.Context, amountMsat int64, description string, descriptionHash string, expirySeconds int64, throughNodePubkey *string) (transaction *lnclient.Transaction, err error) {
 802  
 803  	if expirySeconds < 0 || expirySeconds > int64(maxInvoiceExpiry/time.Second) {
 804  		return nil, errors.New("invalid invoice expiry")
 805  	}
 806  
 807  	maxReceivable := ls.getMaxReceivable()
 808  
 809  	jitChannelsEnabled, _ := ls.cfg.Get("JitChannelsEnabled", "")
 810  	// JIT channels are only used for users without a public channel - users with
 811  	// a public channel should increase inbound liquidity manually.
 812  	isJitInvoice := ls.lsps2Pubkey != "" &&
 813  		jitChannelsEnabled != "false" &&
 814  		!ls.hasPublicChannel() &&
 815  		amountMsat > maxReceivable
 816  
 817  	if amountMsat > maxReceivable && !isJitInvoice {
 818  		ls.eventPublisher.Publish(&events.Event{
 819  			Event: "nwc_incoming_liquidity_required",
 820  			Properties: map[string]interface{}{
 821  				// "amount":         amount / 1000,
 822  				// "max_receivable": maxReceivable,
 823  				// "num_channels":   len(gs.node.ListChannels()),
 824  				"node_type": config.LDKBackendType,
 825  			},
 826  		})
 827  	}
 828  
 829  	if expirySeconds == 0 {
 830  		expirySeconds = lnclient.DEFAULT_INVOICE_EXPIRY
 831  	}
 832  
 833  	var descriptionType ldk_node.Bolt11InvoiceDescription
 834  	descriptionType = ldk_node.Bolt11InvoiceDescriptionDirect{
 835  		Description: description,
 836  	}
 837  	if description == "" && descriptionHash != "" {
 838  		descriptionType = ldk_node.Bolt11InvoiceDescriptionHash{
 839  			Hash: descriptionHash,
 840  		}
 841  	}
 842  
 843  	var invoiceObj *ldk_node.Bolt11Invoice
 844  	if isJitInvoice {
 845  		// cap the opening fee the LSP may deduct from the incoming payment
 846  		maxLspFeeLimitMsat := ls.getLsps2MaxTotalOpeningFeeMsat(uint64(amountMsat))
 847  		invoiceObj, err = ls.node.Bolt11Payment().ReceiveViaJitChannel(
 848  			uint64(amountMsat),
 849  			descriptionType,
 850  			uint32(expirySeconds),
 851  			&maxLspFeeLimitMsat,
 852  		)
 853  	} else {
 854  		invoiceObj, err = ls.node.Bolt11Payment().Receive(
 855  			uint64(amountMsat),
 856  			descriptionType,
 857  			uint32(expirySeconds),
 858  		)
 859  	}
 860  
 861  	if err != nil {
 862  		logger.Logger.WithError(err).Error("MakeInvoice failed")
 863  		return nil, err
 864  	}
 865  
 866  	payment := ls.node.Payment(invoiceObj.PaymentHash())
 867  	invoice := invoiceObj.String()
 868  	paymentRequest, err := decodepay.Decodepay(invoice)
 869  	if err != nil {
 870  		logger.Logger.WithFields(logrus.Fields{
 871  			"bolt11": invoice,
 872  		}).WithError(err).Error("Failed to decode bolt11 invoice")
 873  
 874  		return nil, err
 875  	}
 876  	expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
 877  
 878  	preimage := ""
 879  	estimatedLspFeeMsat := int64(0)
 880  	if payment != nil {
 881  		switch kind := payment.Kind.(type) {
 882  		case ldk_node.PaymentKindBolt11:
 883  			if kind.Preimage != nil {
 884  				preimage = *kind.Preimage
 885  			}
 886  		case ldk_node.PaymentKindBolt11Jit:
 887  			if kind.Preimage != nil {
 888  				preimage = *kind.Preimage
 889  			}
 890  			if kind.LspFeeLimits.MaxTotalOpeningFeeMsat != nil {
 891  				estimatedLspFeeMsat = int64(*kind.LspFeeLimits.MaxTotalOpeningFeeMsat)
 892  			} else if kind.LspFeeLimits.MaxProportionalOpeningFeePpmMsat != nil && amountMsat > 0 {
 893  				estimatedLspFeeMsat = int64((uint64(amountMsat) * *kind.LspFeeLimits.MaxProportionalOpeningFeePpmMsat) / 1_000_000)
 894  			}
 895  		}
 896  	}
 897  
 898  	transaction = &lnclient.Transaction{
 899  		Type:            "incoming",
 900  		Invoice:         invoice,
 901  		PaymentHash:     paymentRequest.PaymentHash,
 902  		Preimage:        preimage,
 903  		AmountMsat:      amountMsat,
 904  		FeesPaidMsat:    estimatedLspFeeMsat,
 905  		CreatedAt:       int64(paymentRequest.CreatedAt),
 906  		ExpiresAt:       &expiresAtUnix,
 907  		Description:     paymentRequest.Description,
 908  		DescriptionHash: paymentRequest.DescriptionHash,
 909  	}
 910  
 911  	return transaction, nil
 912  }
 913  
 914  func (ls *LDKService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
 915  	// this method shouldn't be any more because this LNClient supports notifications
 916  	return nil, errors.New("this method should not be called")
 917  }
 918  
 919  func (ls *LDKService) ListOnchainTransactions(ctx context.Context) ([]lnclient.OnchainTransaction, error) {
 920  	transactions := []lnclient.OnchainTransaction{}
 921  	for _, payment := range ls.node.ListPayments() {
 922  		onchainPaymentKind, isOnchainPaymentKind := payment.Kind.(ldk_node.PaymentKindOnchain)
 923  		if !isOnchainPaymentKind {
 924  			continue
 925  		}
 926  
 927  		transactionType := "incoming"
 928  		if payment.Direction == ldk_node.PaymentDirectionOutbound {
 929  			transactionType = "outgoing"
 930  		}
 931  
 932  		var amountMsat uint64
 933  		if payment.AmountMsat != nil {
 934  			amountMsat = *payment.AmountMsat
 935  		}
 936  		var status string
 937  		var height uint32
 938  		var numConfirmations uint32
 939  		switch onchainPaymentStatus := onchainPaymentKind.Status.(type) {
 940  		case ldk_node.ConfirmationStatusConfirmed:
 941  			status = "confirmed"
 942  			height = onchainPaymentStatus.Height
 943  			nodeStatus := ls.node.Status()
 944  			numConfirmations = nodeStatus.CurrentBestBlock.Height - height
 945  		case ldk_node.ConfirmationStatusUnconfirmed:
 946  			status = "unconfirmed"
 947  		}
 948  
 949  		createdAt := payment.CreatedAt
 950  		if createdAt == 0 {
 951  			createdAt = payment.LatestUpdateTimestamp
 952  		}
 953  
 954  		transactions = append(transactions, lnclient.OnchainTransaction{
 955  			AmountSat:        amountMsat / 1000,
 956  			CreatedAt:        createdAt,
 957  			State:            status,
 958  			Type:             transactionType,
 959  			NumConfirmations: numConfirmations,
 960  			TxId:             onchainPaymentKind.Txid,
 961  		})
 962  
 963  	}
 964  	sort.SliceStable(transactions, func(i, j int) bool {
 965  		return transactions[i].CreatedAt > transactions[j].CreatedAt
 966  	})
 967  	return transactions, nil
 968  }
 969  
 970  func (ls *LDKService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
 971  	// TODO: should alias, color be configured in LDK-node? or can we manage them in NWC?
 972  	// an alias is only needed if the user has public channels and wants their node to be publicly visible?
 973  	status := ls.node.Status()
 974  	return &lnclient.NodeInfo{
 975  		Alias:       "NWC",
 976  		Color:       "#897FFF",
 977  		Pubkey:      ls.node.NodeId(),
 978  		Network:     ls.network,
 979  		BlockHeight: status.CurrentBestBlock.Height,
 980  		BlockHash:   status.CurrentBestBlock.BlockHash,
 981  	}, nil
 982  }
 983  
 984  func (ls *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
 985  
 986  	ldkChannels := ls.node.ListChannels()
 987  
 988  	channels := []lnclient.Channel{}
 989  
 990  	// logger.Logger.WithFields(logrus.Fields{
 991  	// 	"channels": ldkChannels,
 992  	// }).Debug("Listed Channels")
 993  
 994  	for _, ldkChannel := range ldkChannels {
 995  		fundingTxId := ""
 996  		fundingTxVout := uint32(0)
 997  		if ldkChannel.FundingTxo != nil {
 998  			fundingTxId = ldkChannel.FundingTxo.Txid
 999  			fundingTxVout = ldkChannel.FundingTxo.Vout
1000  		}
1001  
1002  		internalChannel := map[string]interface{}{}
1003  		internalChannel["channel"] = ldkChannel
1004  		internalChannel["config"] = map[string]interface{}{
1005  			"AcceptUnderpayingHtlcs":              ldkChannel.Config.AcceptUnderpayingHtlcs,
1006  			"CltvExpiryDelta":                     ldkChannel.Config.CltvExpiryDelta,
1007  			"ForceCloseAvoidanceMaxFeeSatoshis":   ldkChannel.Config.ForceCloseAvoidanceMaxFeeSatoshis,
1008  			"ForwardingFeeBaseMsat":               ldkChannel.Config.ForwardingFeeBaseMsat,
1009  			"ForwardingFeeProportionalMillionths": ldkChannel.Config.ForwardingFeeProportionalMillionths,
1010  			"MaxDustHtlcExposure":                 ldkChannel.Config.MaxDustHtlcExposure,
1011  		}
1012  
1013  		unspendablePunishmentReserveSat := uint64(0)
1014  		if ldkChannel.UnspendablePunishmentReserve != nil {
1015  			unspendablePunishmentReserveSat = *ldkChannel.UnspendablePunishmentReserve
1016  		}
1017  
1018  		var channelError *string
1019  
1020  		if fundingTxId == "" {
1021  			channelErrorValue := "This channel has no funding transaction. Please contact support@getalby.com"
1022  			channelError = &channelErrorValue
1023  		} else if ldkChannel.IsUsable && ldkChannel.CounterpartyForwardingInfoFeeBaseMsat == nil {
1024  			// if we don't have this, routing will not work (LND <-> LDK interoperability bug - https://github.com/lightningnetwork/lnd/issues/6870 )
1025  			channelErrorValue := "Counterparty forwarding info is not yet available, but normally resolves automatically. Try restarting Alby Hub if this warning does not resolve within a few hours."
1026  			channelError = &channelErrorValue
1027  		}
1028  
1029  		isActive := ldkChannel.IsUsable /* superset of ldkChannel.IsReady */ && channelError == nil
1030  
1031  		// Public channels require 6 confirmations before they can be gossiped/announced
1032  		// (BOLT-7), and they only become usable once announced. However, LDK accepts
1033  		// channels from trusted LSP peers as 0-conf, so it reports ConfirmationsRequired
1034  		// as nil/0. Override to 6 for public channels so the UI shows confirmation
1035  		// progress while opening instead of an indefinite blank loading spinner.
1036  		confirmationsRequired := ldkChannel.ConfirmationsRequired
1037  		if ldkChannel.IsAnnounced {
1038  			publicChannelConfirmationsRequired := uint32(6)
1039  			if confirmationsRequired == nil || *confirmationsRequired < publicChannelConfirmationsRequired {
1040  				confirmationsRequired = &publicChannelConfirmationsRequired
1041  			}
1042  		}
1043  
1044  		channels = append(channels, lnclient.Channel{
1045  			InternalChannel:                     internalChannel,
1046  			LocalBalanceMsat:                    int64(ldkChannel.ChannelValueSats*1000 - ldkChannel.InboundCapacityMsat - ldkChannel.CounterpartyUnspendablePunishmentReserve*1000),
1047  			LocalSpendableBalanceMsat:           int64(ldkChannel.OutboundCapacityMsat),
1048  			RemoteBalanceMsat:                   int64(ldkChannel.InboundCapacityMsat),
1049  			RemotePubkey:                        ldkChannel.CounterpartyNodeId,
1050  			Id:                                  ldkChannel.UserChannelId, // CloseChannel takes the UserChannelId
1051  			Active:                              isActive,
1052  			Public:                              ldkChannel.IsAnnounced,
1053  			FundingTxId:                         fundingTxId,
1054  			FundingTxVout:                       fundingTxVout,
1055  			Confirmations:                       ldkChannel.Confirmations,
1056  			ConfirmationsRequired:               confirmationsRequired,
1057  			ForwardingFeeBaseMsat:               ldkChannel.Config.ForwardingFeeBaseMsat,
1058  			ForwardingFeeProportionalMillionths: ldkChannel.Config.ForwardingFeeProportionalMillionths,
1059  			UnspendablePunishmentReserveSat:     unspendablePunishmentReserveSat,
1060  			CounterpartyUnspendablePunishmentReserveSat: ldkChannel.CounterpartyUnspendablePunishmentReserve,
1061  			Error:      channelError,
1062  			IsOutbound: ldkChannel.IsOutbound,
1063  		})
1064  	}
1065  
1066  	return channels, nil
1067  }
1068  
1069  func (ls *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
1070  	nodeConnectionInfo = &lnclient.NodeConnectionInfo{
1071  		Pubkey: ls.node.NodeId(),
1072  	}
1073  
1074  	if ls.cfg.GetEnv().LDKAnnouncementAddresses != "" {
1075  		addresses := strings.Split(ls.cfg.GetEnv().LDKAnnouncementAddresses, ",")
1076  		for _, address := range addresses {
1077  			address = strings.TrimSpace(address)
1078  			if address == "" {
1079  				continue
1080  			}
1081  
1082  			var ip string
1083  			var portStr string
1084  
1085  			if strings.HasPrefix(address, "[") {
1086  				// IPv6 format: [ipv6]:port
1087  				closeBracket := strings.Index(address, "]")
1088  				if closeBracket > 0 {
1089  					ip = address[0 : closeBracket+1]
1090  					if closeBracket+2 < len(address) && address[closeBracket+1] == ':' {
1091  						portStr = address[closeBracket+2:]
1092  					}
1093  				}
1094  			} else {
1095  				// IPv4 or hostname format: ip:port
1096  				parts := strings.Split(address, ":")
1097  				if len(parts) >= 2 {
1098  					portStr = parts[len(parts)-1]
1099  					ip = strings.Join(parts[:len(parts)-1], ":")
1100  				}
1101  			}
1102  
1103  			if portStr != "" {
1104  				if port, parseErr := strconv.Atoi(portStr); parseErr == nil && ip != "" {
1105  					nodeConnectionInfo.Address = ip
1106  					nodeConnectionInfo.Port = port
1107  					break
1108  				}
1109  			}
1110  		}
1111  	}
1112  
1113  	return nodeConnectionInfo, nil
1114  }
1115  
1116  func (ls *LDKService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
1117  	peers := ls.node.ListPeers()
1118  
1119  	var foundPeer *ldk_node.PeerDetails
1120  	for _, peer := range peers {
1121  		if peer.NodeId == connectPeerRequest.Pubkey {
1122  			foundPeer = &peer
1123  			break
1124  		}
1125  	}
1126  
1127  	if foundPeer != nil && !strings.Contains(foundPeer.Address, connectPeerRequest.Address) {
1128  		logger.Logger.WithFields(logrus.Fields{
1129  			"existing_address": foundPeer.Address,
1130  			"new_address":      connectPeerRequest.Address,
1131  		}).Warn("peer address changed, disconnecting first")
1132  		// disconnect first to ensure new IP address is saved in case of re-connecting
1133  		err := ls.node.Disconnect(connectPeerRequest.Pubkey)
1134  		if err != nil {
1135  			// non-critical: only log an error
1136  			logger.Logger.WithField("request", connectPeerRequest).WithError(err).Error("Disconnect failed while connecting peer")
1137  		}
1138  	}
1139  
1140  	err := ls.node.Connect(connectPeerRequest.Pubkey, connectPeerRequest.Address+":"+strconv.Itoa(int(connectPeerRequest.Port)), true)
1141  	if err != nil {
1142  		logger.Logger.WithField("request", connectPeerRequest).WithError(err).Error("ConnectPeer failed")
1143  		return err
1144  	}
1145  
1146  	return nil
1147  }
1148  
1149  func (ls *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
1150  	peers := ls.node.ListPeers()
1151  	var foundPeer *ldk_node.PeerDetails
1152  	for _, peer := range peers {
1153  		if peer.NodeId == openChannelRequest.Pubkey {
1154  
1155  			foundPeer = &peer
1156  			break
1157  		}
1158  	}
1159  
1160  	if foundPeer == nil {
1161  		return nil, errors.New("node is not peered yet")
1162  	}
1163  
1164  	ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
1165  	defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
1166  
1167  	logger.Logger.WithField("peer_id", foundPeer.NodeId).Info("Opening channel")
1168  	var userChannelId string
1169  	var err error
1170  	if openChannelRequest.Public {
1171  		userChannelId, err = ls.node.OpenAnnouncedChannel(foundPeer.NodeId, foundPeer.Address, uint64(openChannelRequest.AmountSats), nil, nil)
1172  	} else {
1173  		userChannelId, err = ls.node.OpenChannel(foundPeer.NodeId, foundPeer.Address, uint64(openChannelRequest.AmountSats), nil, nil)
1174  	}
1175  	if err != nil {
1176  		logger.Logger.WithError(err).Error("OpenChannel failed")
1177  		return nil, err
1178  	}
1179  
1180  	// userChannelId allows to locally keep track of the channel (and is also used to close the channel)
1181  	logger.Logger.WithFields(logrus.Fields{
1182  		"peer_id":    foundPeer.NodeId,
1183  		"channel_id": userChannelId,
1184  	}).Info("Funded channel")
1185  
1186  	for start := time.Now(); time.Since(start) < time.Second*60; {
1187  		event := <-ldkEventSubscription
1188  
1189  		channelPendingEvent, isChannelPendingEvent := (*event).(ldk_node.EventChannelPending)
1190  		channelClosedEvent, isChannelClosedEvent := (*event).(ldk_node.EventChannelClosed)
1191  
1192  		if isChannelClosedEvent {
1193  			closureReason := ls.getChannelCloseReason(&channelClosedEvent)
1194  			logger.Logger.WithFields(logrus.Fields{
1195  				"event":  channelClosedEvent,
1196  				"reason": closureReason,
1197  			}).Info("Failed to open channel")
1198  
1199  			return nil, fmt.Errorf("failed to open channel with %s: %s", foundPeer.NodeId, closureReason)
1200  		}
1201  
1202  		if !isChannelPendingEvent {
1203  			continue
1204  		}
1205  
1206  		return &lnclient.OpenChannelResponse{
1207  			FundingTxId: channelPendingEvent.FundingTxo.Txid,
1208  		}, nil
1209  	}
1210  
1211  	return nil, errors.New("open channel timeout")
1212  }
1213  
1214  func (ls *LDKService) UpdateChannel(ctx context.Context, updateChannelRequest *lnclient.UpdateChannelRequest) error {
1215  	channels := ls.node.ListChannels()
1216  
1217  	var foundChannel *ldk_node.ChannelDetails
1218  	for _, channel := range channels {
1219  		if channel.UserChannelId == updateChannelRequest.ChannelId && channel.CounterpartyNodeId == updateChannelRequest.NodeId {
1220  			foundChannel = &channel
1221  			break
1222  		}
1223  	}
1224  
1225  	if foundChannel == nil {
1226  		logger.Logger.WithField("request", updateChannelRequest).Error("failed to find channel to update")
1227  		return errors.New("channel not found")
1228  	}
1229  
1230  	existingConfig := foundChannel.Config
1231  	existingConfig.ForwardingFeeBaseMsat = updateChannelRequest.ForwardingFeeBaseMsat
1232  	existingConfig.ForwardingFeeProportionalMillionths = updateChannelRequest.ForwardingFeeProportionalMillionths
1233  
1234  	if updateChannelRequest.MaxDustHtlcExposureFromFeeRateMultiplier > 0 {
1235  		existingConfig.MaxDustHtlcExposure = ldk_node.MaxDustHtlcExposureFeeRateMultiplier{
1236  			Multiplier: updateChannelRequest.MaxDustHtlcExposureFromFeeRateMultiplier,
1237  		}
1238  	}
1239  
1240  	err := ls.node.UpdateChannelConfig(updateChannelRequest.ChannelId, updateChannelRequest.NodeId, existingConfig)
1241  	if err != nil {
1242  		logger.Logger.WithError(err).Error("UpdateChannelConfig failed")
1243  		return err
1244  	}
1245  	return nil
1246  }
1247  
1248  func (ls *LDKService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) error {
1249  	logger.Logger.WithFields(logrus.Fields{
1250  		"request": closeChannelRequest,
1251  	}).Info("Closing Channel")
1252  
1253  	var err error
1254  	if closeChannelRequest.Force {
1255  		err = ls.node.ForceCloseChannel(closeChannelRequest.ChannelId, closeChannelRequest.NodeId, nil)
1256  	} else {
1257  		err = ls.node.CloseChannel(closeChannelRequest.ChannelId, closeChannelRequest.NodeId)
1258  	}
1259  	if err != nil {
1260  		logger.Logger.WithError(err).Error("CloseChannel failed")
1261  		return err
1262  	}
1263  	return nil
1264  }
1265  
1266  func (ls *LDKService) GetNewOnchainAddress(ctx context.Context) (string, error) {
1267  	address, err := ls.node.OnchainPayment().NewAddress()
1268  	if err != nil {
1269  		logger.Logger.WithError(err).Error("NewOnchainAddress failed")
1270  		return "", err
1271  	}
1272  	return address, nil
1273  }
1274  
1275  func (ls *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
1276  	nodeStatus := ls.node.Status()
1277  	channels := ls.node.ListChannels()
1278  	balances := ls.node.ListBalances()
1279  	logger.Logger.WithFields(logrus.Fields{
1280  		"balances": balances,
1281  	}).Debug("Listed Balances")
1282  
1283  	type internalLightningBalance struct {
1284  		BalanceType string
1285  		Balance     ldk_node.LightningBalance
1286  	}
1287  
1288  	internalLightningBalances := []internalLightningBalance{}
1289  
1290  	pendingBalancesDetails := make([]lnclient.PendingBalanceDetails, 0)
1291  
1292  	pendingBalancesFromChannelClosuresSat := uint64(0)
1293  	// increase pending balance from any lightning balances for channels that are pending closure
1294  	// (they do not exist in our list of open channels)
1295  	for _, balance := range balances.LightningBalances {
1296  		increasePendingBalance := func(nodeId, channelId string, amountSat uint64, fundingTxId ldk_node.Txid, fundingTxIndex uint16) {
1297  			if !slices.ContainsFunc(channels, func(channel ldk_node.ChannelDetails) bool {
1298  				return channel.ChannelId == channelId
1299  			}) {
1300  				pendingBalancesFromChannelClosuresSat += amountSat
1301  				pendingBalancesDetails = append(pendingBalancesDetails, lnclient.PendingBalanceDetails{
1302  					NodeId:        nodeId,
1303  					ChannelId:     channelId,
1304  					AmountSat:     amountSat,
1305  					FundingTxId:   fundingTxId,
1306  					FundingTxVout: uint32(fundingTxIndex),
1307  				})
1308  			}
1309  		}
1310  
1311  		// include the balance type as it's useful to know the state of the channel
1312  		internalLightningBalances = append(internalLightningBalances, internalLightningBalance{
1313  			BalanceType: fmt.Sprintf("%T", balance),
1314  			Balance:     balance,
1315  		})
1316  		switch balanceType := (balance).(type) {
1317  		case ldk_node.LightningBalanceClaimableOnChannelClose:
1318  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1319  		case ldk_node.LightningBalanceClaimableAwaitingConfirmations:
1320  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1321  		case ldk_node.LightningBalanceContentiousClaimable:
1322  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1323  		case ldk_node.LightningBalanceMaybeTimeoutClaimableHtlc:
1324  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1325  		case ldk_node.LightningBalanceMaybePreimageClaimableHtlc:
1326  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1327  		case ldk_node.LightningBalanceCounterpartyRevokedOutputClaimable:
1328  			increasePendingBalance(balanceType.CounterpartyNodeId, balanceType.ChannelId, balanceType.AmountSatoshis, balanceType.FundingTxId, balanceType.FundingTxIndex)
1329  		}
1330  	}
1331  
1332  	pendingSweepBalanceDetails := make([]lnclient.PendingBalanceDetails, 0)
1333  	increasePendingBalanceFromClosure := func(nodeId, channelId *string, amountSat uint64, fundingTxId *ldk_node.Txid, fundingTxIndex *uint16) {
1334  		pendingBalancesFromChannelClosuresSat += amountSat
1335  
1336  		if nodeId != nil && channelId != nil && fundingTxId != nil && fundingTxIndex != nil {
1337  			pendingSweepBalanceDetails = append(pendingSweepBalanceDetails, lnclient.PendingBalanceDetails{
1338  				NodeId:        *nodeId,
1339  				ChannelId:     *channelId,
1340  				AmountSat:     amountSat,
1341  				FundingTxId:   *fundingTxId,
1342  				FundingTxVout: uint32(*fundingTxIndex),
1343  			})
1344  		}
1345  	}
1346  
1347  	// increase pending balance from any lightning balances for channels that were closed
1348  	for _, balance := range balances.PendingBalancesFromChannelClosures {
1349  		switch pendingType := (balance).(type) {
1350  		case ldk_node.PendingSweepBalancePendingBroadcast:
1351  			increasePendingBalanceFromClosure(pendingType.CounterpartyNodeId, pendingType.ChannelId, pendingType.AmountSatoshis, pendingType.FundingTxId, pendingType.FundingTxIndex)
1352  		case ldk_node.PendingSweepBalanceBroadcastAwaitingConfirmation:
1353  			increasePendingBalanceFromClosure(pendingType.CounterpartyNodeId, pendingType.ChannelId, pendingType.AmountSatoshis, pendingType.FundingTxId, pendingType.FundingTxIndex)
1354  		case ldk_node.PendingSweepBalanceAwaitingThresholdConfirmations:
1355  			if nodeStatus.CurrentBestBlock.Height < pendingType.ConfirmationHeight+6 {
1356  				// LDK now keeps the balance in this state for four weeks even after the funds are confirmed to be swept
1357  				// to confirm the channel monitors are archived before the sweeper entries are dropped
1358  				// so now we just check for 6 confirmations
1359  				increasePendingBalanceFromClosure(pendingType.CounterpartyNodeId, pendingType.ChannelId, pendingType.AmountSatoshis, pendingType.FundingTxId, pendingType.FundingTxIndex)
1360  			}
1361  		}
1362  	}
1363  
1364  	return &lnclient.OnchainBalanceResponse{
1365  		SpendableSat:                          int64(balances.SpendableOnchainBalanceSats),
1366  		TotalSat:                              int64(balances.TotalOnchainBalanceSats - balances.TotalAnchorChannelsReserveSats),
1367  		ReservedSat:                           int64(balances.TotalAnchorChannelsReserveSats),
1368  		PendingBalancesFromChannelClosuresSat: pendingBalancesFromChannelClosuresSat,
1369  		PendingBalancesDetails:                pendingBalancesDetails,
1370  		PendingSweepBalancesDetails:           pendingSweepBalanceDetails,
1371  		InternalBalances: map[string]interface{}{
1372  			"internal_lightning_balances": internalLightningBalances,
1373  			"all_balances":                balances,
1374  		},
1375  	}, nil
1376  }
1377  
1378  func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string, amountSat uint64, feeRate *uint64, sendAll bool) (string, error) {
1379  	if ls.redeemedOnchainFundsWithinThisSync {
1380  		return "", errors.New("please wait a minute for the wallet to sync before doing another on-chain payment")
1381  	}
1382  
1383  	var feePtr **ldk_node.FeeRate
1384  	if feeRate != nil {
1385  		fee := ldk_node.FeeRateFromSatPerVbUnchecked(*feeRate)
1386  		feePtr = &fee
1387  	}
1388  
1389  	var txId string
1390  	var err error
1391  
1392  	if !sendAll {
1393  		// NOTE: this may fail if user does not reserve enough for the onchain transaction
1394  		// and can also drain the anchor reserves if the user provides a too high amount.
1395  		txId, err = ls.node.OnchainPayment().SendToAddress(toAddress, amountSat, feePtr)
1396  	} else {
1397  		txId, err = ls.node.OnchainPayment().SendAllToAddress(toAddress, false, feePtr)
1398  	}
1399  
1400  	if err != nil {
1401  		logger.Logger.WithField("send_all", sendAll).WithError(err).Error("LDK onchain payment to redeem funds failed")
1402  		return "", err
1403  	}
1404  
1405  	// make sure we do a sync after sending on-chain funds
1406  	ls.redeemedOnchainFundsWithinThisSync = true
1407  	ls.lastWalletSyncRequest = time.Now()
1408  
1409  	// FIXME: remove once LDK-node returns an error if it can't broadcast the transaction
1410  
1411  	tryCheckTransactionWasBroadcasted := func() error {
1412  		url := ls.cfg.GetEnv().MempoolApi + "/tx/" + txId
1413  
1414  		client := http.Client{
1415  			Timeout: time.Second * 10,
1416  		}
1417  
1418  		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
1419  		if err != nil {
1420  			logger.Logger.WithError(err).WithFields(logrus.Fields{
1421  				"url": url,
1422  			}).Error("Failed to create http request")
1423  			return err
1424  		}
1425  
1426  		res, err := client.Do(req)
1427  		if err != nil {
1428  			logger.Logger.WithError(err).WithFields(logrus.Fields{
1429  				"url": url,
1430  			}).Error("Failed to send request")
1431  			return err
1432  		}
1433  
1434  		if res.StatusCode >= 300 {
1435  			// transaction not found
1436  			return errors.New("unexpected status code")
1437  		}
1438  
1439  		return nil
1440  	}
1441  
1442  	for attempt := 1; attempt < 30; attempt++ {
1443  		err := tryCheckTransactionWasBroadcasted()
1444  		if err != nil {
1445  			logger.Logger.WithError(err).WithField("attempt", attempt).Error("Failed to fetch broadcasted transaction")
1446  			time.Sleep(1 * time.Second)
1447  			continue
1448  		}
1449  
1450  		return txId, nil
1451  	}
1452  	return "", errors.New("ran out of attempts to fetch broadcasted transaction")
1453  }
1454  
1455  func (ls *LDKService) ResetRouter(key string) error {
1456  	err := ls.cfg.SetUpdate(resetRouterKey, key, "")
1457  	if err != nil {
1458  		logger.Logger.WithError(err).Error("Failed to set reset router key")
1459  		return err
1460  	}
1461  
1462  	return nil
1463  }
1464  
1465  func (ls *LDKService) SignMessage(ctx context.Context, message string) (string, error) {
1466  	signedMessage := ls.node.SignMessage([]byte(message))
1467  
1468  	return signedMessage, nil
1469  }
1470  
1471  func (ls *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails) (*lnclient.Transaction, error) {
1472  	// logger.Logger.WithField("payment", payment).Debug("Mapping LDK payment to transaction")
1473  
1474  	transactionType := "incoming"
1475  	if payment.Direction == ldk_node.PaymentDirectionOutbound {
1476  		transactionType = "outgoing"
1477  	}
1478  
1479  	var expiresAt *int64
1480  	var createdAt int64
1481  	var description string
1482  	var descriptionHash string
1483  	var bolt11Invoice string
1484  	var settledAt *int64
1485  	preimage := ""
1486  	paymentHash := ""
1487  	metadata := map[string]interface{}{}
1488  
1489  	bolt11PaymentKind, isBolt11PaymentKind := payment.Kind.(ldk_node.PaymentKindBolt11)
1490  
1491  	if isBolt11PaymentKind && bolt11PaymentKind.Bolt11Invoice != nil {
1492  		bolt11Invoice = *bolt11PaymentKind.Bolt11Invoice
1493  		paymentRequest, err := decodepay.Decodepay(strings.ToLower(bolt11Invoice))
1494  		if err != nil {
1495  			logger.Logger.WithFields(logrus.Fields{
1496  				"bolt11": bolt11Invoice,
1497  			}).WithError(err).Error("Failed to decode bolt11 invoice")
1498  
1499  			return nil, err
1500  		}
1501  		createdAt = int64(paymentRequest.CreatedAt)
1502  		expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
1503  		expiresAt = &expiresAtUnix
1504  		description = paymentRequest.Description
1505  		descriptionHash = paymentRequest.DescriptionHash
1506  		if payment.Status == ldk_node.PaymentStatusSucceeded {
1507  			if bolt11PaymentKind.Preimage != nil {
1508  				preimage = *bolt11PaymentKind.Preimage
1509  			}
1510  			settledAt = &createdAt // fallback settledAt to created at time
1511  			if payment.LatestUpdateTimestamp > 0 {
1512  				lastUpdate := int64(payment.LatestUpdateTimestamp)
1513  				settledAt = &lastUpdate
1514  			}
1515  		}
1516  		paymentHash = bolt11PaymentKind.Hash
1517  	}
1518  
1519  	bolt11JitPaymentKind, isBolt11JitPaymentKind := payment.Kind.(ldk_node.PaymentKindBolt11Jit)
1520  	if isBolt11JitPaymentKind {
1521  		createdAt = int64(payment.CreatedAt)
1522  		if payment.CreatedAt == 0 {
1523  			createdAt = int64(payment.LatestUpdateTimestamp)
1524  		}
1525  		if payment.Status == ldk_node.PaymentStatusSucceeded && bolt11JitPaymentKind.Preimage != nil {
1526  			preimage = *bolt11JitPaymentKind.Preimage
1527  			lastUpdate := int64(payment.LatestUpdateTimestamp)
1528  			settledAt = &lastUpdate
1529  		}
1530  		paymentHash = bolt11JitPaymentKind.Hash
1531  	}
1532  
1533  	bolt12PaymentKind, isBolt12PaymentKind := payment.Kind.(ldk_node.PaymentKindBolt12Offer)
1534  
1535  	if isBolt12PaymentKind {
1536  		createdAt = int64(payment.CreatedAt)
1537  
1538  		if bolt12PaymentKind.Hash == nil {
1539  			return nil, errors.New("BOLT-12 payment has no payment hash")
1540  		}
1541  		paymentHash = *bolt12PaymentKind.Hash
1542  
1543  		offer := map[string]interface{}{}
1544  		offer["id"] = bolt12PaymentKind.OfferId
1545  
1546  		if bolt12PaymentKind.PayerNote != nil {
1547  			offer["payer_note"] = *bolt12PaymentKind.PayerNote
1548  		}
1549  
1550  		metadata["offer"] = offer
1551  
1552  		if payment.Status == ldk_node.PaymentStatusSucceeded {
1553  			if bolt12PaymentKind.Preimage != nil {
1554  				preimage = *bolt12PaymentKind.Preimage
1555  			}
1556  			lastUpdate := int64(payment.LatestUpdateTimestamp)
1557  			settledAt = &lastUpdate
1558  		}
1559  	}
1560  
1561  	spontaneousPaymentKind, isSpontaneousPaymentKind := payment.Kind.(ldk_node.PaymentKindSpontaneous)
1562  	if isSpontaneousPaymentKind {
1563  		// keysend payment
1564  		lastUpdate := int64(payment.LatestUpdateTimestamp)
1565  		createdAt = int64(payment.CreatedAt)
1566  		// TODO: remove this check some point in the future
1567  		// all payments after v0.6.2 will have createdAt set
1568  		if createdAt == 0 {
1569  			createdAt = lastUpdate
1570  		}
1571  		if payment.Status == ldk_node.PaymentStatusSucceeded {
1572  			settledAt = &lastUpdate
1573  		}
1574  		paymentHash = spontaneousPaymentKind.Hash
1575  		if spontaneousPaymentKind.Preimage != nil {
1576  			preimage = *spontaneousPaymentKind.Preimage
1577  		}
1578  
1579  		tlvRecords := []lnclient.TLVRecord{}
1580  		for _, tlv := range spontaneousPaymentKind.CustomTlvs {
1581  			tlvRecords = append(tlvRecords, lnclient.TLVRecord{
1582  				Type:  tlv.Type,
1583  				Value: hex.EncodeToString(tlv.Value),
1584  			})
1585  		}
1586  		metadata["tlv_records"] = tlvRecords
1587  	}
1588  
1589  	var amountMsat uint64 = 0
1590  	if payment.AmountMsat != nil {
1591  		amountMsat = *payment.AmountMsat
1592  	}
1593  
1594  	var feeMsat uint64 = 0
1595  	if payment.FeePaidMsat != nil {
1596  		feeMsat = *payment.FeePaidMsat
1597  	}
1598  	if isBolt11JitPaymentKind && bolt11JitPaymentKind.CounterpartySkimmedFeeMsat != nil {
1599  		feeMsat = *bolt11JitPaymentKind.CounterpartySkimmedFeeMsat
1600  	}
1601  
1602  	return &lnclient.Transaction{
1603  		Type:            transactionType,
1604  		Preimage:        preimage,
1605  		PaymentHash:     paymentHash,
1606  		SettledAt:       settledAt,
1607  		AmountMsat:      int64(amountMsat),
1608  		Invoice:         bolt11Invoice,
1609  		FeesPaidMsat:    int64(feeMsat),
1610  		CreatedAt:       createdAt,
1611  		Description:     description,
1612  		DescriptionHash: descriptionHash,
1613  		ExpiresAt:       expiresAt,
1614  		Metadata:        metadata,
1615  	}, nil
1616  }
1617  
1618  func (ls *LDKService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
1619  	peers := ls.node.ListPeers()
1620  	ret := make([]lnclient.PeerDetails, 0, len(peers))
1621  	for _, peer := range peers {
1622  		ret = append(ret, lnclient.PeerDetails{
1623  			NodeId:      peer.NodeId,
1624  			Address:     peer.Address,
1625  			IsPersisted: peer.IsPersisted,
1626  			IsConnected: peer.IsConnected,
1627  		})
1628  	}
1629  	return ret, nil
1630  }
1631  
1632  func (ls *LDKService) GetNetworkGraph(ctx context.Context, nodeIds []string) (lnclient.NetworkGraphResponse, error) {
1633  	graph := ls.node.NetworkGraph()
1634  
1635  	type NodeInfoWithId struct {
1636  		Node   *ldk_node.NodeInfo `json:"node"`
1637  		NodeId string             `json:"nodeId"`
1638  	}
1639  
1640  	nodes := []NodeInfoWithId{}
1641  	channels := []*ldk_node.ChannelInfo{}
1642  	for _, nodeId := range nodeIds {
1643  		_, err := hex.DecodeString(nodeId)
1644  		if err != nil {
1645  			return nil, err
1646  		}
1647  		if len(nodeId) != 66 {
1648  			return nil, errors.New("unexpected node ID length")
1649  		}
1650  		graphNode := graph.Node(nodeId)
1651  		if graphNode != nil {
1652  			nodes = append(nodes, NodeInfoWithId{
1653  				Node:   graphNode,
1654  				NodeId: nodeId,
1655  			})
1656  			if graphNode.Channels != nil {
1657  				for _, channelId := range graphNode.Channels {
1658  					graphChannel := graph.Channel(channelId)
1659  					if graphChannel != nil {
1660  						channels = append(channels, graphChannel)
1661  					}
1662  				}
1663  			}
1664  		}
1665  	}
1666  
1667  	networkGraph := map[string]interface{}{
1668  		"nodes":    nodes,
1669  		"channels": channels,
1670  	}
1671  	return networkGraph, nil
1672  }
1673  
1674  func (ls *LDKService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
1675  	return []byte("Node logs are now included in application logs"), nil
1676  }
1677  
1678  func (ls *LDKService) handleLdkEvent(event *ldk_node.Event) {
1679  	logger.Logger.WithFields(logrus.Fields{
1680  		"event": event,
1681  	}).Info("Received LDK event")
1682  
1683  	switch eventType := (*event).(type) {
1684  	case ldk_node.EventChannelReady:
1685  		channels := ls.node.ListChannels()
1686  		channelIndex := slices.IndexFunc(channels, func(c ldk_node.ChannelDetails) bool {
1687  			return c.ChannelId == eventType.ChannelId
1688  		})
1689  		if channelIndex == -1 {
1690  			logger.Logger.WithField("event", eventType).Error("Failed to find channel by ID")
1691  			return
1692  		}
1693  
1694  		channel := channels[channelIndex]
1695  
1696  		// assume it's a JIT channel if the channel peer matches
1697  		// checking outbound capacity doesn't work (outbound capacity can be initially 0)
1698  		isJit := !channel.IsOutbound && ls.lsps2Pubkey != "" && *eventType.CounterpartyNodeId == ls.lsps2Pubkey
1699  
1700  		isTrusted := eventType.CounterpartyNodeId != nil &&
1701  			(slices.Contains(ls.node.Config().AnchorChannelsConfig.TrustedPeersNoReserve, *eventType.CounterpartyNodeId) || isJit)
1702  
1703  		ls.eventPublisher.Publish(&events.Event{
1704  			Event: "nwc_channel_ready",
1705  			Properties: map[string]interface{}{
1706  				"counterparty_node_id": eventType.CounterpartyNodeId,
1707  				"node_type":            config.LDKBackendType,
1708  				"public":               channel.IsAnnounced,
1709  				"jit":                  isJit,
1710  				"capacity":             channel.ChannelValueSats,
1711  				"is_outbound":          channel.IsOutbound,
1712  				"trusted":              isTrusted,
1713  			},
1714  		})
1715  
1716  		ls.backupChannels()
1717  
1718  		if eventType.CounterpartyNodeId == nil {
1719  			logger.Logger.WithField("event", eventType).Error("channel ready event has no counterparty node ID")
1720  			return
1721  		}
1722  
1723  		maxDustHtlcExposureFromFeeRateMultiplier := uint64(0)
1724  		if isTrusted {
1725  			// avoid closures like "ProcessingError: Peer sent update_fee with a feerate (62500)
1726  			// which may over-expose us to dust-in-flight on our counterparty's transactions (totaling 69348000 msat)"
1727  			maxDustHtlcExposureFromFeeRateMultiplier = 100_000 // default * 10
1728  		}
1729  
1730  		// set a super-high forwarding fee of 100K sats by default to disable unwanted routing by default
1731  		forwardingFeeBaseMsat := uint32(100_000_000)
1732  
1733  		err := ls.UpdateChannel(context.Background(), &lnclient.UpdateChannelRequest{
1734  			ChannelId:                                eventType.UserChannelId,
1735  			NodeId:                                   *eventType.CounterpartyNodeId,
1736  			MaxDustHtlcExposureFromFeeRateMultiplier: maxDustHtlcExposureFromFeeRateMultiplier,
1737  			ForwardingFeeBaseMsat:                    forwardingFeeBaseMsat,
1738  		})
1739  
1740  		if err != nil {
1741  			logger.Logger.WithField("event", eventType).Error("channel ready event has no counterparty node ID")
1742  			return
1743  		}
1744  
1745  	case ldk_node.EventChannelClosed:
1746  		// make sure we do a sync after receiving a channel closed event
1747  		ls.lastWalletSyncRequest = time.Now()
1748  
1749  		closureReason := ls.getChannelCloseReason(&eventType)
1750  		logger.Logger.WithFields(logrus.Fields{
1751  			"event":  event,
1752  			"reason": closureReason,
1753  		}).Info("Channel closed")
1754  		onchainBalance, err := ls.GetOnchainBalance(context.Background())
1755  		if err != nil {
1756  			logger.Logger.WithError(err).Error("failed to retrieve on-chain balance when closing channel")
1757  		}
1758  		var pendingBalance uint64
1759  		var fundingTxId string
1760  		var fundingTxVout uint32
1761  		var fundingTxUrl string
1762  
1763  		if onchainBalance != nil {
1764  			logger.Logger.WithField("onchain_balance", onchainBalance).Info("got on-chain balance when closing channel")
1765  
1766  			for _, details := range onchainBalance.PendingBalancesDetails {
1767  				if details.ChannelId == eventType.ChannelId {
1768  					fundingTxId = details.FundingTxId
1769  					fundingTxVout = details.FundingTxVout
1770  					fundingTxUrl = fmt.Sprintf("https://mempool.space/tx/%s#flow=&vout=%d", fundingTxId, fundingTxVout)
1771  					pendingBalance += details.AmountSat
1772  				}
1773  			}
1774  			for _, details := range onchainBalance.PendingSweepBalancesDetails {
1775  				if details.ChannelId == eventType.ChannelId {
1776  					fundingTxId = details.FundingTxId
1777  					fundingTxVout = details.FundingTxVout
1778  					fundingTxUrl = fmt.Sprintf("https://mempool.space/tx/%s#flow=&vout=%d", fundingTxId, fundingTxVout)
1779  					pendingBalance += details.AmountSat
1780  				}
1781  			}
1782  		}
1783  
1784  		var counterpartyNodeId string
1785  		var counterpartyNodeUrl string
1786  		if eventType.CounterpartyNodeId != nil {
1787  			counterpartyNodeId = *eventType.CounterpartyNodeId
1788  			counterpartyNodeUrl = "https://amboss.space/node/" + counterpartyNodeId
1789  		}
1790  
1791  		ls.eventPublisher.Publish(&events.Event{
1792  			Event: "nwc_channel_closed",
1793  			Properties: map[string]interface{}{
1794  				"counterparty_node_id":  counterpartyNodeId,
1795  				"counterparty_node_url": counterpartyNodeUrl,
1796  				"reason":                closureReason,
1797  				"node_type":             config.LDKBackendType,
1798  				"pending_balance":       pendingBalance,
1799  				"funding_tx_id":         fundingTxId,
1800  				"funding_tx_vout":       fundingTxVout,
1801  				"funding_tx_url":        fundingTxUrl,
1802  			},
1803  		})
1804  	case ldk_node.EventPaymentReceived:
1805  		if eventType.PaymentId == nil {
1806  			logger.Logger.WithField("payment_hash", eventType.PaymentHash).Error("payment received event has no payment ID")
1807  			return
1808  		}
1809  		payment := ls.node.Payment(*eventType.PaymentId)
1810  		if payment == nil {
1811  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("could not find LDK payment")
1812  			return
1813  		}
1814  
1815  		transaction, err := ls.ldkPaymentToTransaction(payment)
1816  		if err != nil {
1817  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("failed to convert LDK payment to transaction")
1818  			return
1819  		}
1820  
1821  		ls.eventPublisher.Publish(&events.Event{
1822  			Event:      "nwc_lnclient_payment_received",
1823  			Properties: transaction,
1824  		})
1825  	case ldk_node.EventPaymentSuccessful:
1826  		if eventType.PaymentId == nil {
1827  			logger.Logger.WithField("payment_hash", eventType.PaymentHash).Error("payment received event has no payment ID")
1828  			return
1829  		}
1830  		payment := ls.node.Payment(*eventType.PaymentId)
1831  		if payment == nil {
1832  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("could not find LDK payment")
1833  			return
1834  		}
1835  
1836  		transaction, err := ls.ldkPaymentToTransaction(payment)
1837  		if err != nil {
1838  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("failed to convert LDK payment to transaction")
1839  			return
1840  		}
1841  
1842  		ls.eventPublisher.Publish(&events.Event{
1843  			Event:      "nwc_lnclient_payment_sent",
1844  			Properties: transaction,
1845  		})
1846  	case ldk_node.EventPaymentFailed:
1847  		if eventType.PaymentId == nil {
1848  			logger.Logger.WithField("payment_hash", eventType.PaymentHash).Error("payment failed event has no payment ID")
1849  			return
1850  		}
1851  		payment := ls.node.Payment(*eventType.PaymentId)
1852  		if payment == nil {
1853  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("could not find LDK payment")
1854  			return
1855  		}
1856  
1857  		transaction, err := ls.ldkPaymentToTransaction(payment)
1858  		if err != nil {
1859  			logger.Logger.WithField("payment_id", *eventType.PaymentId).Error("failed to convert LDK payment to transaction")
1860  			return
1861  		}
1862  
1863  		reason := ls.getPaymentFailReason(&eventType)
1864  
1865  		ls.eventPublisher.Publish(&events.Event{
1866  			Event: "nwc_lnclient_payment_failed",
1867  			Properties: &lnclient.PaymentFailedEventProperties{
1868  				Transaction: transaction,
1869  				Reason:      reason,
1870  			},
1871  		})
1872  	case ldk_node.EventPaymentForwarded:
1873  		logger.Logger.WithFields(logrus.Fields{
1874  			"total_fee_earned_msat":          eventType.TotalFeeEarnedMsat,
1875  			"outbound_amount_forwarded_msat": eventType.OutboundAmountForwardedMsat,
1876  		}).Info("LDK Payment forwarded")
1877  		if eventType.TotalFeeEarnedMsat == nil || eventType.OutboundAmountForwardedMsat == nil {
1878  			logger.Logger.WithFields(logrus.Fields{
1879  				"earned_msat":                    eventType.TotalFeeEarnedMsat,
1880  				"outbound_amount_forwarded_msat": eventType.OutboundAmountForwardedMsat,
1881  			}).Error("forwarded payment has missing required fields")
1882  			return
1883  		}
1884  		ls.eventPublisher.Publish(&events.Event{
1885  			Event: "nwc_payment_forwarded",
1886  			Properties: &lnclient.PaymentForwardedEventProperties{
1887  				TotalFeeEarnedMsat:          *eventType.TotalFeeEarnedMsat,
1888  				OutboundAmountForwardedMsat: *eventType.OutboundAmountForwardedMsat,
1889  			},
1890  		})
1891  
1892  	case ldk_node.EventPaymentClaimable:
1893  		if eventType.ClaimDeadline == nil {
1894  			logger.Logger.WithField("payment_id", eventType.PaymentId).Error("claimable payment has no claim deadline")
1895  			return
1896  		}
1897  
1898  		logger.Logger.WithFields(logrus.Fields{
1899  			"claimable_amount_msats": eventType.ClaimableAmountMsat,
1900  			"payment_hash":           eventType.PaymentHash,
1901  			"claim_deadline":         *eventType.ClaimDeadline,
1902  		}).Info("LDK Payment Claimable")
1903  
1904  		payment := ls.node.Payment(eventType.PaymentId)
1905  		if payment == nil {
1906  			logger.Logger.WithField("payment_id", eventType.PaymentId).Error("could not find LDK payment")
1907  			return
1908  		}
1909  
1910  		transaction, err := ls.ldkPaymentToTransaction(payment)
1911  		if err != nil {
1912  			logger.Logger.WithField("payment_id", eventType.PaymentId).Error("failed to convert LDK payment to transaction")
1913  			return
1914  		}
1915  		transaction.SettleDeadline = eventType.ClaimDeadline
1916  		ls.eventPublisher.Publish(&events.Event{
1917  			Event:      "nwc_lnclient_hold_invoice_accepted",
1918  			Properties: transaction,
1919  		})
1920  	}
1921  }
1922  
1923  func (ls *LDKService) backupChannels() {
1924  	ldkChannels := ls.node.ListChannels()
1925  	ldkPeers := ls.node.ListPeers()
1926  	channels := make([]events.ChannelBackup, 0, len(ldkChannels))
1927  	for _, ldkChannel := range ldkChannels {
1928  		var fundingTxId string
1929  		var fundingTxVout uint32
1930  		if ldkChannel.FundingTxo != nil {
1931  			fundingTxId = ldkChannel.FundingTxo.Txid
1932  			fundingTxVout = ldkChannel.FundingTxo.Vout
1933  		}
1934  
1935  		var peer *ldk_node.PeerDetails
1936  		for _, matchingPeer := range ldkPeers {
1937  			if matchingPeer.NodeId == ldkChannel.CounterpartyNodeId {
1938  				peer = &matchingPeer
1939  			}
1940  		}
1941  		if peer == nil {
1942  			logger.Logger.WithField("peer_id", ldkChannel.CounterpartyNodeId).Error("failed to find peer for channel")
1943  			continue
1944  		}
1945  
1946  		channels = append(channels, events.ChannelBackup{
1947  			ChannelID:         ldkChannel.ChannelId,
1948  			PeerID:            ldkChannel.CounterpartyNodeId,
1949  			PeerSocketAddress: peer.Address,
1950  			ChannelSize:       ldkChannel.ChannelValueSats,
1951  			FundingTxID:       fundingTxId,
1952  			FundingTxVout:     fundingTxVout,
1953  		})
1954  	}
1955  
1956  	monitors, err := ls.node.GetEncodedChannelMonitors()
1957  	if err != nil {
1958  		logger.Logger.WithError(err).Error("Failed to list channel monitors")
1959  		return
1960  	}
1961  	encodedMonitors := []events.EncodedChannelMonitorBackup{}
1962  
1963  	for _, monitor := range monitors {
1964  		encodedMonitors = append(encodedMonitors, events.EncodedChannelMonitorBackup{
1965  			Key:   monitor.Key,
1966  			Value: hex.EncodeToString(monitor.Value),
1967  		})
1968  	}
1969  
1970  	event := &events.StaticChannelsBackupEvent{
1971  		Channels: channels,
1972  		Monitors: encodedMonitors,
1973  		NodeID:   ls.node.NodeId(),
1974  	}
1975  
1976  	ls.saveStaticChannelBackupToDisk(event)
1977  
1978  	ls.eventPublisher.Publish(&events.Event{
1979  		Event:      "nwc_backup_channels",
1980  		Properties: event,
1981  	})
1982  }
1983  
1984  func (ls *LDKService) saveStaticChannelBackupToDisk(event *events.StaticChannelsBackupEvent) {
1985  	backupDirectory := filepath.Join(ls.workdir, "static_channel_backups")
1986  	err := os.MkdirAll(backupDirectory, os.ModePerm)
1987  	if err != nil {
1988  		logger.Logger.WithError(err).Error("Failed to make static channel backup directory")
1989  		return
1990  	}
1991  
1992  	backupFilePath := filepath.Join(backupDirectory, time.Now().Format("2006-01-02T15-04-05")+".json")
1993  	eventBytes, err := json.Marshal(event)
1994  	if err != nil {
1995  		logger.Logger.WithError(err).Error("Failed to serialize static channel backup to json")
1996  		return
1997  	}
1998  	err = os.WriteFile(backupFilePath, eventBytes, 0644)
1999  	if err != nil {
2000  		logger.Logger.WithError(err).Error("Failed to write static channel backup to disk")
2001  		return
2002  	}
2003  	logger.Logger.WithField("backupPath", backupFilePath).Debug("Saved static channel backup to disk")
2004  }
2005  
2006  func (ls *LDKService) GetBalances(ctx context.Context, includeInactiveChannels bool) (*lnclient.BalancesResponse, error) {
2007  	onchainBalance, err := ls.GetOnchainBalance(ctx)
2008  	if err != nil {
2009  		logger.Logger.WithError(err).Error("Failed to retrieve onchain balance")
2010  		return nil, err
2011  	}
2012  
2013  	var totalReceivable int64 = 0
2014  	var totalSpendable int64 = 0
2015  	var nextMaxReceivable int64 = 0
2016  	var nextMaxSpendable int64 = 0
2017  	var nextMaxReceivableMPP int64 = 0
2018  	var nextMaxSpendableMPP int64 = 0
2019  	channels := ls.node.ListChannels()
2020  	for _, channel := range channels {
2021  		if channel.IsUsable || includeInactiveChannels {
2022  			// spending or receiving amount may be constrained by channel configuration (e.g. ACINQ does this)
2023  			channelConstrainedSpendable := min(int64(channel.OutboundCapacityMsat), int64(*channel.CounterpartyOutboundHtlcMaximumMsat))
2024  			channelConstrainedReceivable := min(int64(channel.InboundCapacityMsat), int64(*channel.InboundHtlcMaximumMsat))
2025  
2026  			nextMaxSpendable = max(nextMaxSpendable, channelConstrainedSpendable)
2027  			nextMaxReceivable = max(nextMaxReceivable, channelConstrainedReceivable)
2028  
2029  			nextMaxSpendableMPP += channelConstrainedSpendable
2030  			nextMaxReceivableMPP += channelConstrainedReceivable
2031  
2032  			// these are what the wallet can send and receive, but not necessarily in one go
2033  			totalSpendable += int64(channel.OutboundCapacityMsat)
2034  			totalReceivable += int64(channel.InboundCapacityMsat)
2035  		}
2036  	}
2037  
2038  	return &lnclient.BalancesResponse{
2039  		Onchain: *onchainBalance,
2040  		Lightning: lnclient.LightningBalanceResponse{
2041  			TotalSpendableMsat:       totalSpendable,
2042  			TotalReceivableMsat:      totalReceivable,
2043  			NextMaxSpendableMsat:     nextMaxSpendable,
2044  			NextMaxReceivableMsat:    nextMaxReceivable,
2045  			NextMaxSpendableMPPMsat:  nextMaxSpendableMPP,
2046  			NextMaxReceivableMPPMsat: nextMaxReceivableMPP,
2047  		},
2048  	}, nil
2049  }
2050  
2051  func (ls *LDKService) GetStorageDir() (string, error) {
2052  	// Note: the below will return the path including the WORK_DIR which is harder to use,
2053  	// so for now we just return a hardcoded value.
2054  	// cfg := ls.node.Config()
2055  	// return cfg.StorageDirPath, nil
2056  	return "ldk/storage", nil
2057  }
2058  
2059  func (ls *LDKService) deleteOldLDKPayments() {
2060  	payments := ls.node.ListPayments()
2061  
2062  	now := time.Now()
2063  	for _, payment := range payments {
2064  		paymentCreatedAt := time.Unix(int64(payment.CreatedAt), 0)
2065  
2066  		deletablePaymentKind := false
2067  		switch (payment.Kind).(type) {
2068  		case ldk_node.PaymentKindBolt11:
2069  			deletablePaymentKind = true
2070  		case ldk_node.PaymentKindBolt11Jit:
2071  			deletablePaymentKind = true
2072  		case ldk_node.PaymentKindSpontaneous:
2073  			deletablePaymentKind = true
2074  		}
2075  		if !deletablePaymentKind {
2076  			logger.Logger.WithFields(logrus.Fields{
2077  				"created_at": paymentCreatedAt,
2078  				"payment_id": payment.Id,
2079  			}).Debug("Skipping undeletable payment kind")
2080  			continue
2081  		}
2082  
2083  		if paymentCreatedAt.Add(maxInvoiceExpiry).Before(now) {
2084  			logger.Logger.WithFields(logrus.Fields{
2085  				"created_at": paymentCreatedAt,
2086  				"payment_id": payment.Id,
2087  			}).Debug("Deleting old payment")
2088  			err := ls.node.RemovePayment(payment.Id)
2089  			if err != nil {
2090  				logger.Logger.WithError(err).WithField("id", payment.Id).Error("failed to delete old payment")
2091  			}
2092  		}
2093  	}
2094  }
2095  
2096  func deleteOldLDKLogs(ldkLogDir string) {
2097  	logger.Logger.WithField("ldkLogDir", ldkLogDir).Debug("Deleting old LDK logs")
2098  	files, err := os.ReadDir(ldkLogDir)
2099  	if err != nil {
2100  		if errors.Is(err, os.ErrNotExist) {
2101  			// no log file directory - expected when VSS is enabled
2102  			return
2103  		}
2104  		logger.Logger.WithField("path", ldkLogDir).WithError(err).Error("Failed to list ldk log directory")
2105  		return
2106  	}
2107  
2108  	for _, file := range files {
2109  		// get files with a date (e.g. ldk_node_2024_03_29.log)
2110  		if strings.HasPrefix(file.Name(), "ldk_node_2") && strings.HasSuffix(file.Name(), ".log") {
2111  			filePath := filepath.Join(ldkLogDir, file.Name())
2112  			fileInfo, err := file.Info()
2113  			if err != nil {
2114  				logger.Logger.WithField("filePath", filePath).WithError(err).Error("Failed to get file info")
2115  				continue
2116  			}
2117  			// delete files last modified over 3 days ago
2118  			if fileInfo.ModTime().Before(time.Now().AddDate(0, 0, -3)) {
2119  				err := os.Remove(filePath)
2120  				if err != nil {
2121  					logger.Logger.WithField("filePath", filePath).WithError(err).Error("Failed to get file info")
2122  					continue
2123  				}
2124  				logger.Logger.WithField("filePath", filePath).Info("Deleted old LDK log file")
2125  			}
2126  		}
2127  	}
2128  }
2129  
2130  func (ls *LDKService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.NodeStatus, err error) {
2131  	status := ls.node.Status()
2132  	return &lnclient.NodeStatus{
2133  		IsReady:            status.IsRunning,
2134  		InternalNodeStatus: status,
2135  	}, nil
2136  }
2137  
2138  func (ls *LDKService) DisconnectPeer(ctx context.Context, peerId string) error {
2139  	return ls.node.Disconnect(peerId)
2140  }
2141  
2142  func (ls *LDKService) UpdateLastWalletSyncRequest() {
2143  	ls.lastWalletSyncRequest = time.Now()
2144  }
2145  
2146  func (ls *LDKService) GetSupportedNIP47Methods() []string {
2147  	return []string{
2148  		models.PAY_INVOICE_METHOD,
2149  		models.PAY_KEYSEND_METHOD,
2150  		models.GET_BALANCE_METHOD,
2151  		models.GET_BUDGET_METHOD,
2152  		models.GET_INFO_METHOD,
2153  		models.MAKE_INVOICE_METHOD,
2154  		models.LOOKUP_INVOICE_METHOD,
2155  		models.LIST_TRANSACTIONS_METHOD,
2156  		models.MULTI_PAY_INVOICE_METHOD,
2157  		models.MULTI_PAY_KEYSEND_METHOD,
2158  		models.SIGN_MESSAGE_METHOD,
2159  		models.MAKE_HOLD_INVOICE_METHOD,
2160  		models.SETTLE_HOLD_INVOICE_METHOD,
2161  		models.CANCEL_HOLD_INVOICE_METHOD,
2162  	}
2163  }
2164  
2165  func (ls *LDKService) GetSupportedNIP47NotificationTypes() []string {
2166  	return []string{
2167  		notifications.PAYMENT_RECEIVED_NOTIFICATION,
2168  		notifications.PAYMENT_SENT_NOTIFICATION,
2169  		notifications.HOLD_INVOICE_ACCEPTED_NOTIFICATION,
2170  	}
2171  }
2172  
2173  func (ls *LDKService) getPaymentFailReason(eventPaymentFailed *ldk_node.EventPaymentFailed) string {
2174  	var failureReason ldk_node.PaymentFailureReason
2175  	var failureReasonMessage string
2176  	if eventPaymentFailed.Reason != nil {
2177  		failureReason = *eventPaymentFailed.Reason
2178  	}
2179  	switch failureReason {
2180  	case ldk_node.PaymentFailureReasonRecipientRejected:
2181  		failureReasonMessage = "RecipientRejected"
2182  	case ldk_node.PaymentFailureReasonUserAbandoned:
2183  		failureReasonMessage = "UserAbandoned"
2184  	case ldk_node.PaymentFailureReasonRetriesExhausted:
2185  		failureReasonMessage = "RetriesExhausted"
2186  	case ldk_node.PaymentFailureReasonPaymentExpired:
2187  		failureReasonMessage = "PaymentExpired"
2188  	case ldk_node.PaymentFailureReasonRouteNotFound:
2189  		failureReasonMessage = "RouteNotFound"
2190  	case ldk_node.PaymentFailureReasonUnexpectedError:
2191  		failureReasonMessage = "UnexpectedError"
2192  	case ldk_node.PaymentFailureReasonUnknownRequiredFeatures:
2193  		failureReasonMessage = "UnknownRequiredFeatures"
2194  	case ldk_node.PaymentFailureReasonInvoiceRequestExpired:
2195  		failureReasonMessage = "InvoiceRequestExpired"
2196  	case ldk_node.PaymentFailureReasonInvoiceRequestRejected:
2197  		failureReasonMessage = "InvoiceRequestRejected"
2198  	case ldk_node.PaymentFailureReasonBlindedPathCreationFailed:
2199  		failureReasonMessage = "BlindedPathCreationFailed"
2200  	default:
2201  		failureReasonMessage = "UnknownError"
2202  	}
2203  	return failureReasonMessage
2204  }
2205  
2206  func (ls *LDKService) getChannelCloseReason(event *ldk_node.EventChannelClosed) string {
2207  	var reason string
2208  
2209  	switch reasonType := (*event.Reason).(type) {
2210  	case ldk_node.ClosureReasonCounterpartyForceClosed:
2211  		reason = fmt.Sprintf("CounterpartyForceClosed (Peer message: %s)", reasonType.PeerMsg)
2212  	case ldk_node.ClosureReasonHolderForceClosed:
2213  		reason = "HolderForceClosed"
2214  	case ldk_node.ClosureReasonLegacyCooperativeClosure:
2215  		reason = "LegacyCooperativeClosure"
2216  	case ldk_node.ClosureReasonCounterpartyInitiatedCooperativeClosure:
2217  		reason = "CounterpartyInitiatedCooperativeClosure"
2218  	case ldk_node.ClosureReasonLocallyInitiatedCooperativeClosure:
2219  		reason = "LocallyInitiatedCooperativeClosure"
2220  	case ldk_node.ClosureReasonCommitmentTxConfirmed:
2221  		reason = "CommitmentTxConfirmed"
2222  	case ldk_node.ClosureReasonFundingTimedOut:
2223  		reason = "FundingTimedOut"
2224  	case ldk_node.ClosureReasonProcessingError:
2225  		reason = fmt.Sprintf("ProcessingError: %s", reasonType.Err)
2226  	case ldk_node.ClosureReasonDisconnectedPeer:
2227  		reason = "DisconnectedPeer"
2228  	case ldk_node.ClosureReasonOutdatedChannelManager:
2229  		reason = "OutdatedChannelManager"
2230  	case ldk_node.ClosureReasonCounterpartyCoopClosedUnfundedChannel:
2231  		reason = "CounterpartyCoopClosedUnfundedChannel"
2232  	case ldk_node.ClosureReasonFundingBatchClosure:
2233  		reason = "FundingBatchClosure"
2234  	case ldk_node.ClosureReasonHtlCsTimedOut:
2235  		reason = "HTLCsTimedOut"
2236  	default:
2237  		reason = fmt.Sprintf("Unknown: %s", *event.Reason)
2238  	}
2239  
2240  	return reason
2241  }
2242  
2243  func (ls *LDKService) GetPubkey() string {
2244  	return ls.pubkey
2245  }
2246  
2247  func (ls *LDKService) PayOfferSync(ctx context.Context, offer string, amount uint64, payerNote string) (*lnclient.PayOfferResponse, error) {
2248  	// TODO: this is only for testing MakeOffer and needs improvements
2249  	// (+ BOLT-12 payments need to go through transactions service)
2250  	// TODO: send liquidity event if amount too large
2251  	offerObj, err := ldk_node.OfferFromStr(offer)
2252  	if err != nil {
2253  		return nil, err
2254  	}
2255  
2256  	paymentStart := time.Now()
2257  	ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
2258  	defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
2259  
2260  	// TODO: use normal send if no amount is provided
2261  	// TODO: configure sending params to ensure fee reserve is used, etc.
2262  	paymentId, err := ls.node.Bolt12Payment().SendUsingAmount(offerObj, amount, nil, &payerNote, nil)
2263  	if err != nil {
2264  		logger.Logger.WithError(err).Error("Failed to initiate BOLT-12 variable amount payment")
2265  		return nil, errors.New("failed to initiate BOLT-12 variable amount payment")
2266  	}
2267  
2268  	logger.Logger.WithFields(logrus.Fields{
2269  		"payment_id": paymentId,
2270  	}).Info("Initiated BOLT-12 variable amount payment")
2271  
2272  	feeMsat := uint64(0)
2273  	preimage := ""
2274  
2275  	payment := ls.node.Payment(paymentId)
2276  	if payment == nil {
2277  		return nil, errors.New("payment not found by payment ID")
2278  	}
2279  
2280  	paymentHash := ""
2281  
2282  	for start := time.Now(); time.Since(start) < time.Second*60; {
2283  		event := <-ldkEventSubscription
2284  
2285  		eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
2286  		eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
2287  
2288  		if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentId != nil && *eventPaymentSuccessful.PaymentId == paymentId {
2289  			logger.Logger.Info("Got payment success event")
2290  			payment := ls.node.Payment(paymentId)
2291  			if payment == nil {
2292  				logger.Logger.Errorf("Couldn't find payment by payment ID: %v", paymentId)
2293  				return nil, errors.New("payment not found")
2294  			}
2295  
2296  			bolt12PaymentKind, ok := payment.Kind.(ldk_node.PaymentKindBolt12Offer)
2297  
2298  			if !ok {
2299  				logger.Logger.WithFields(logrus.Fields{
2300  					"payment": payment,
2301  				}).Error("Payment is not a BOLT-12 offer kind")
2302  				return nil, errors.New("payment is not a BOLT-12 offer")
2303  			}
2304  
2305  			if bolt12PaymentKind.Preimage == nil {
2306  				logger.Logger.Errorf("No payment preimage for payment ID: %v", paymentId)
2307  				return nil, errors.New("payment preimage not found")
2308  			}
2309  			preimage = *bolt12PaymentKind.Preimage
2310  
2311  			if bolt12PaymentKind.Hash == nil {
2312  				logger.Logger.Errorf("No payment hash for payment ID: %v", paymentId)
2313  				return nil, errors.New("payment hash not found")
2314  			}
2315  			paymentHash = *bolt12PaymentKind.Hash
2316  
2317  			if eventPaymentSuccessful.FeePaidMsat != nil {
2318  				feeMsat = *eventPaymentSuccessful.FeePaidMsat
2319  			}
2320  			break
2321  		}
2322  		if isEventPaymentFailedEvent && eventPaymentFailed.PaymentId != nil && *eventPaymentFailed.PaymentId == paymentId {
2323  			reason := ls.getPaymentFailReason(&eventPaymentFailed)
2324  
2325  			logger.Logger.WithFields(logrus.Fields{
2326  				"payment_id": paymentId,
2327  				"reason":     reason,
2328  			}).Error("Received payment failed event")
2329  
2330  			return nil, fmt.Errorf("received payment failed event: %s", reason)
2331  		}
2332  	}
2333  
2334  	logger.Logger.WithFields(logrus.Fields{
2335  		"duration": time.Since(paymentStart).Milliseconds(),
2336  		"feeMsat":  feeMsat,
2337  	}).Info("Successful BOLT-12 payment")
2338  
2339  	return &lnclient.PayOfferResponse{
2340  		PaymentHash: paymentHash,
2341  		Preimage:    preimage,
2342  		FeeMsat:     feeMsat,
2343  	}, nil
2344  }
2345  
2346  const nodeCommandPayBOLT12Offer = "pay_bolt12_offer"
2347  const nodeCommandExportPathfindingScores = "export_pathfinding_scores"
2348  const nodeCommandListChannelMonitorSizes = "list_channel_monitor_sizes"
2349  
2350  func (ls *LDKService) GetCustomNodeCommandDefinitions() []lnclient.CustomNodeCommandDef {
2351  	return []lnclient.CustomNodeCommandDef{
2352  		{
2353  			Name:        nodeCommandPayBOLT12Offer,
2354  			Description: "Send payments to a BOLT-12 offer. NOTE: this is for testing only. Payment will not show in transaction list.",
2355  			Args: []lnclient.CustomNodeCommandArgDef{
2356  				{
2357  					Name:        "offer",
2358  					Description: "BOLT-12 offer of receiver",
2359  				},
2360  				{
2361  					Name:        "amount",
2362  					Description: "amount to send in millisats",
2363  				},
2364  				{
2365  					Name:        "payer_note",
2366  					Description: "note to the recepient",
2367  				},
2368  			},
2369  		},
2370  		{
2371  			Name:        nodeCommandExportPathfindingScores,
2372  			Description: "Exports pathfinding scores from the LDK node.",
2373  			Args:        []lnclient.CustomNodeCommandArgDef{}, // Assuming no arguments for now
2374  		},
2375  		{
2376  			Name:        nodeCommandListChannelMonitorSizes,
2377  			Description: "List Channel Monitor sizes from the LDK node.",
2378  			Args:        []lnclient.CustomNodeCommandArgDef{},
2379  		},
2380  	}
2381  }
2382  
2383  func (ls *LDKService) ExecuteCustomNodeCommand(ctx context.Context, command *lnclient.CustomNodeCommandRequest) (*lnclient.CustomNodeCommandResponse, error) {
2384  	switch command.Name {
2385  	case nodeCommandPayBOLT12Offer:
2386  		var offer string
2387  		var amount uint64
2388  		var payerNote string
2389  		var err error
2390  		for i := range command.Args {
2391  			switch command.Args[i].Name {
2392  			case "offer":
2393  				offer = command.Args[i].Value
2394  			case "amount":
2395  				amount, err = strconv.ParseUint(string(command.Args[i].Value), 10, 64)
2396  			case "payer_note":
2397  				payerNote = command.Args[i].Value
2398  			}
2399  		}
2400  		if err != nil {
2401  			return nil, err
2402  		}
2403  
2404  		payOfferResponse, err := ls.PayOfferSync(ctx, offer, amount, payerNote)
2405  
2406  		if err != nil {
2407  			return nil, err
2408  		}
2409  
2410  		return &lnclient.CustomNodeCommandResponse{
2411  			Response: map[string]interface{}{
2412  				"paymentHash": payOfferResponse.PaymentHash,
2413  				"preimage":    payOfferResponse.Preimage,
2414  				"feeMsat":     payOfferResponse.FeeMsat,
2415  			},
2416  		}, nil
2417  	case nodeCommandExportPathfindingScores:
2418  		scores, err := ls.node.ExportPathfindingScores()
2419  		if err != nil {
2420  			logger.Logger.WithError(err).Error("ExportPathfindingScores command failed")
2421  			return nil, fmt.Errorf("failed to export pathfinding scores: %w", err)
2422  		}
2423  		return &lnclient.CustomNodeCommandResponse{
2424  			Response: map[string]interface{}{
2425  				"scores": hex.EncodeToString(scores),
2426  			},
2427  		}, nil
2428  	case nodeCommandListChannelMonitorSizes:
2429  		channelMonitorSizes := ls.node.ListChannelMonitorSizes()
2430  		channels := ls.node.ListChannels()
2431  		type channelMonitorSizeResponse struct {
2432  			SizeBytes    uint64 `json:"sizeBytes"`
2433  			RemotePubkey string `json:"remotePubkey"`
2434  			HasWarning   bool   `json:"hasWarning"`
2435  		}
2436  		channelMonitorSizesResponse := []channelMonitorSizeResponse{}
2437  		for _, channelMonitorSizeInfo := range channelMonitorSizes {
2438  			for _, channel := range channels {
2439  				if channel.ChannelId == channelMonitorSizeInfo.ChannelId {
2440  					channelMonitorSizesResponse = append(channelMonitorSizesResponse, channelMonitorSizeResponse{
2441  						SizeBytes:    channelMonitorSizeInfo.SizeBytes,
2442  						RemotePubkey: channel.CounterpartyNodeId,
2443  						HasWarning:   channelMonitorSizeInfo.SizeBytes >= ls.cfg.GetEnv().LDKChannelMonitorWarningSizeBytes,
2444  					})
2445  				}
2446  			}
2447  		}
2448  
2449  		return &lnclient.CustomNodeCommandResponse{
2450  			Response: channelMonitorSizesResponse,
2451  		}, nil
2452  	}
2453  
2454  	return nil, lnclient.ErrUnknownCustomNodeCommand
2455  }
2456  
2457  func (ls *LDKService) MakeHoldInvoice(ctx context.Context, amountMsat int64, description string, descriptionHash string, expirySeconds int64, paymentHash string, minCltvExpiryDelta *uint64) (*lnclient.Transaction, error) {
2458  	if expirySeconds < 0 || expirySeconds > int64(maxInvoiceExpiry/time.Second) {
2459  		return nil, errors.New("invalid invoice expiry")
2460  	}
2461  
2462  	maxReceivable := ls.getMaxReceivable()
2463  
2464  	if amountMsat > maxReceivable {
2465  		ls.eventPublisher.Publish(&events.Event{
2466  			Event: "nwc_incoming_liquidity_required",
2467  			Properties: map[string]interface{}{
2468  				"node_type": config.LDKBackendType,
2469  			},
2470  		})
2471  	}
2472  
2473  	if expirySeconds == 0 {
2474  		expirySeconds = lnclient.DEFAULT_INVOICE_EXPIRY
2475  	}
2476  
2477  	var descriptionType ldk_node.Bolt11InvoiceDescription
2478  	descriptionType = ldk_node.Bolt11InvoiceDescriptionDirect{
2479  		Description: description,
2480  	}
2481  	if description == "" && descriptionHash != "" {
2482  		descriptionType = ldk_node.Bolt11InvoiceDescriptionHash{
2483  			Hash: descriptionHash,
2484  		}
2485  	}
2486  
2487  	decodedPaymentHash, err := hex.DecodeString(paymentHash)
2488  	if err != nil {
2489  		logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("Failed to decode payment hash for MakeHoldInvoice")
2490  		return nil, fmt.Errorf("failed to decode payment hash: %w", err)
2491  	}
2492  	if len(decodedPaymentHash) != 32 {
2493  		return nil, errors.New("payment hash must be 32 bytes")
2494  	}
2495  	var paymentHash32 [32]byte
2496  	copy(paymentHash32[:], decodedPaymentHash)
2497  
2498  	ldkPaymentHash := ldk_node.PaymentHash(hex.EncodeToString(paymentHash32[:]))
2499  
2500  	var invoiceObj *ldk_node.Bolt11Invoice
2501  	if minCltvExpiryDelta != nil {
2502  		if *minCltvExpiryDelta > uint64(65535) {
2503  			return nil, errors.New("min_cltv_expiry_delta must be <= 65535")
2504  		}
2505  		invoiceObj, err = ls.node.Bolt11Payment().ReceiveForHashWithMinCltvExpiryDelta(
2506  			uint64(amountMsat),
2507  			descriptionType,
2508  			uint32(expirySeconds),
2509  			ldkPaymentHash,
2510  			uint16(*minCltvExpiryDelta),
2511  		)
2512  	} else {
2513  		invoiceObj, err = ls.node.Bolt11Payment().ReceiveForHash(
2514  			uint64(amountMsat),
2515  			descriptionType,
2516  			uint32(expirySeconds),
2517  			ldkPaymentHash,
2518  		)
2519  	}
2520  
2521  	if err != nil {
2522  		logger.Logger.WithError(err).Error("MakeHoldInvoice failed")
2523  		return nil, err
2524  	}
2525  
2526  	payment := ls.node.Payment(invoiceObj.PaymentHash())
2527  	invoice := *payment.Kind.(ldk_node.PaymentKindBolt11).Bolt11Invoice
2528  	paymentRequest, err := decodepay.Decodepay(invoice)
2529  	if err != nil {
2530  		logger.Logger.WithFields(logrus.Fields{
2531  			"bolt11": invoice,
2532  		}).WithError(err).Error("Failed to decode bolt11 invoice")
2533  		return nil, err
2534  	}
2535  	expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
2536  
2537  	transaction := &lnclient.Transaction{
2538  		Type:            "incoming",
2539  		Invoice:         *payment.Kind.(ldk_node.PaymentKindBolt11).Bolt11Invoice,
2540  		PaymentHash:     paymentRequest.PaymentHash,
2541  		AmountMsat:      amountMsat,
2542  		CreatedAt:       int64(payment.CreatedAt),
2543  		ExpiresAt:       &expiresAtUnix,
2544  		Description:     paymentRequest.Description,
2545  		DescriptionHash: paymentRequest.DescriptionHash,
2546  	}
2547  
2548  	return transaction, nil
2549  }
2550  
2551  func (ls *LDKService) CancelHoldInvoice(ctx context.Context, paymentHash string) error {
2552  	_, err := hex.DecodeString(paymentHash)
2553  	if err != nil {
2554  		logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("Failed to decode payment hash for CancelHoldInvoice")
2555  		return err
2556  	}
2557  
2558  	err = ls.node.Bolt11Payment().FailForHash(paymentHash)
2559  	if err != nil {
2560  		logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("CancelHoldInvoice failed")
2561  	}
2562  	return err
2563  }
2564  
2565  func (ls *LDKService) SettleHoldInvoice(ctx context.Context, preimage string) error {
2566  	decodedPreimage, err := hex.DecodeString(preimage)
2567  	if err != nil {
2568  		logger.Logger.WithError(err).WithField("preimage", preimage).Error("Failed to decode preimage for SettleHoldInvoice")
2569  		return err
2570  	}
2571  	if len(decodedPreimage) != 32 {
2572  		return errors.New("preimage must be 32 bytes")
2573  	}
2574  
2575  	paymentHash256 := sha256.New()
2576  	paymentHash256.Write(decodedPreimage)
2577  	paymentHashBytes := paymentHash256.Sum(nil)
2578  	paymentHash := hex.EncodeToString(paymentHashBytes)
2579  
2580  	paymentDetails := ls.node.Payment(paymentHash)
2581  
2582  	if paymentDetails == nil {
2583  		logger.Logger.WithField("payment_hash", paymentHash).Error("SettleHoldInvoice: Could not find payment by derived hash")
2584  		return errors.New("payment not found for derived hash")
2585  	}
2586  	if paymentDetails.AmountMsat == nil {
2587  		logger.Logger.WithField("payment_hash", paymentHash).Error("SettleHoldInvoice: Payment has no amount_msat")
2588  		return errors.New("payment has no amount_msat")
2589  	}
2590  
2591  	err = ls.node.Bolt11Payment().ClaimForHash(paymentHash, *paymentDetails.AmountMsat, preimage)
2592  	if err != nil {
2593  		logger.Logger.WithError(err).WithField("preimage", preimage).WithField("derived_payment_hash", paymentHash).Error("SettleHoldInvoice failed")
2594  	}
2595  	return err
2596  }
2597  
2598  func GetVssNodeIdentifier(keys keys.Keys) (string, error) {
2599  	key, err := keys.DeriveKey([]uint32{bip32.FirstHardenedChild + 2})
2600  
2601  	if err != nil {
2602  		return "", err
2603  	}
2604  
2605  	// return a 6-character hex string of the hash of a derived key to ensure if same user
2606  	// runs multiple hubs with different mnemonics, they are all
2607  	// saved in the VSS under different user_tokens.
2608  	pubkeyHash256 := sha256.New()
2609  	pubkeyHash256.Write(key.Key)
2610  	pubkeyHashBytes := pubkeyHash256.Sum(nil)
2611  	return hex.EncodeToString(pubkeyHashBytes[0:3]), nil
2612  }
2613  
2614  func getResetStateRequest(cfg config.Config) *ldk_node.ResetState {
2615  	resetKey, err := cfg.Get(resetRouterKey, "")
2616  	if err != nil {
2617  		logger.Logger.Error("Failed to retrieve ResetRouter key")
2618  		return nil
2619  	}
2620  
2621  	if resetKey == "" {
2622  		return nil
2623  	}
2624  
2625  	err = cfg.SetUpdate(resetRouterKey, "", "")
2626  	if err != nil {
2627  		logger.Logger.WithError(err).Error("Failed to remove reset router key")
2628  		return nil
2629  	}
2630  
2631  	var ret ldk_node.ResetState
2632  
2633  	switch resetKey {
2634  	case "ALL":
2635  		ret = ldk_node.ResetStateAll
2636  	case "Scorer":
2637  		ret = ldk_node.ResetStateScorer
2638  	case "NetworkGraph":
2639  		ret = ldk_node.ResetStateNetworkGraph
2640  	case "NodeMetrics":
2641  		ret = ldk_node.ResetStateNodeMetrics
2642  	default:
2643  		logger.Logger.WithField("key", resetKey).Error("Unknown reset router key")
2644  		return nil
2645  	}
2646  
2647  	return &ret
2648  }
2649  
2650  func (ls *LDKService) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
2651  	if event.Event == "nwc_alby_account_connected" {
2652  		// backup existing channels to the user's Alby Account on first connect
2653  		ls.backupChannels()
2654  	}
2655  }
2656  
2657  func (ls *LDKService) GetChainDataSource() (string, string) {
2658  	if endpoint := ls.cfg.GetEnv().LDKBitcoindRpcHost; endpoint != "" {
2659  		rpcPort := ls.cfg.GetEnv().LDKBitcoindRpcPort
2660  		return "bitcoind", sanitizeChainEndpoint(endpoint, rpcPort)
2661  	}
2662  	if endpoint := ls.cfg.GetEnv().LDKElectrumServer; endpoint != "" {
2663  		return "electrum", sanitizeChainEndpoint(endpoint, "")
2664  	}
2665  
2666  	// Fallback to Esplora
2667  	endpoint := ls.cfg.GetEnv().LDKEsploraServer
2668  	return "esplora", sanitizeChainEndpoint(endpoint, "")
2669  }
2670  
2671  func (ls *LDKService) GetLiquiditySourceLsps2() string {
2672  	if ls.lsps2Pubkey == "" || ls.lsps2Address == "" {
2673  		return ""
2674  	}
2675  	return fmt.Sprintf("%s@%s", ls.lsps2Pubkey, ls.lsps2Address)
2676  }
2677  
2678  func (ls *LDKService) GetLiquiditySourceLsps2MinPaymentSizeMsat() *uint64 {
2679  	ls.fetchLsps2OpeningFeeParams(lsps2InfoCacheTTL)
2680  
2681  	ls.lsps2InfoMu.Lock()
2682  	defer ls.lsps2InfoMu.Unlock()
2683  
2684  	return ls.lsps2MinPaymentSizeMsat
2685  }
2686  
2687  func (ls *LDKService) GetLiquiditySourceLsps2MaxPaymentSizeMsat() *uint64 {
2688  	ls.fetchLsps2OpeningFeeParams(lsps2InfoCacheTTL)
2689  
2690  	ls.lsps2InfoMu.Lock()
2691  	defer ls.lsps2InfoMu.Unlock()
2692  
2693  	return ls.lsps2MaxPaymentSizeMsat
2694  }
2695  
2696  // getLsps2MaxTotalOpeningFeeMsat returns the maximum opening fee to accept
2697  // for a JIT channel invoice of the given payment size, derived from the
2698  // LSP's advertised opening fee menu and an absolute ceiling.
2699  func (ls *LDKService) getLsps2MaxTotalOpeningFeeMsat(paymentSizeMsat uint64) uint64 {
2700  	ls.fetchLsps2OpeningFeeParams(lsps2FeeCapCacheTTL)
2701  
2702  	ls.lsps2InfoMu.Lock()
2703  	defer ls.lsps2InfoMu.Unlock()
2704  
2705  	return computeLsps2MaxTotalOpeningFeeMsat(paymentSizeMsat, ls.lsps2OpeningFeeParamsMenu)
2706  }
2707  
2708  func (ls *LDKService) fetchLsps2OpeningFeeParams(maxCacheAge time.Duration) {
2709  	if ls.lsps2Pubkey == "" || ls.lsps2Address == "" {
2710  		return
2711  	}
2712  
2713  	ls.lsps2InfoMu.Lock()
2714  	defer ls.lsps2InfoMu.Unlock()
2715  
2716  	if !ls.lsps2InfoFetchedAt.IsZero() && time.Since(ls.lsps2InfoFetchedAt) < maxCacheAge {
2717  		return
2718  	}
2719  
2720  	response, err := ls.node.Lsps2Liquidity().RequestOpeningFeeParams()
2721  	if err != nil {
2722  		logger.Logger.WithError(err).Warn("Failed to fetch LSPS2 opening fee params")
2723  		return
2724  	}
2725  
2726  	var minPaymentSizeMsat *uint64
2727  	var maxPaymentSizeMsat *uint64
2728  	for _, params := range response.OpeningFeeParamsMenu {
2729  		effectiveMinPaymentSizeMsat, ok := computeLsps2MinPaymentSizeMsat(params)
2730  		if !ok {
2731  			continue
2732  		}
2733  		if minPaymentSizeMsat == nil || effectiveMinPaymentSizeMsat < *minPaymentSizeMsat {
2734  			value := effectiveMinPaymentSizeMsat
2735  			minPaymentSizeMsat = &value
2736  		}
2737  		if maxPaymentSizeMsat == nil || params.MaxPaymentSizeMsat > *maxPaymentSizeMsat {
2738  			value := params.MaxPaymentSizeMsat
2739  			maxPaymentSizeMsat = &value
2740  		}
2741  	}
2742  
2743  	ls.lsps2MinPaymentSizeMsat = minPaymentSizeMsat
2744  	ls.lsps2MaxPaymentSizeMsat = maxPaymentSizeMsat
2745  	ls.lsps2OpeningFeeParamsMenu = response.OpeningFeeParamsMenu
2746  	ls.lsps2InfoFetchedAt = time.Now()
2747  }
2748  
2749  // computeLsps2MaxTotalOpeningFeeMsat returns the maximum LSPS2 opening fee to
2750  // accept for a payment of the given size: the highest fee the advertised fee
2751  // menu allows for that size, further limited by the absolute fee ceiling. The
2752  // ceiling alone is used when no menu entry covers the payment size.
2753  func computeLsps2MaxTotalOpeningFeeMsat(paymentSizeMsat uint64, menu []ldk_node.Lsps2OpeningFeeParams) uint64 {
2754  	maxAcceptableFeeMsat := lsps2MaxAcceptableOpeningFeeMsat(paymentSizeMsat)
2755  
2756  	var menuMaxFeeMsat *uint64
2757  	for _, params := range menu {
2758  		if paymentSizeMsat < params.MinPaymentSizeMsat || paymentSizeMsat > params.MaxPaymentSizeMsat {
2759  			continue
2760  		}
2761  		feeMsat := ldk_node.Lsps2ComputeOpeningFeeMsat(paymentSizeMsat, params)
2762  		if feeMsat == nil {
2763  			continue
2764  		}
2765  		if menuMaxFeeMsat == nil || *feeMsat > *menuMaxFeeMsat {
2766  			menuMaxFeeMsat = feeMsat
2767  		}
2768  	}
2769  
2770  	if menuMaxFeeMsat != nil && *menuMaxFeeMsat < maxAcceptableFeeMsat {
2771  		return *menuMaxFeeMsat
2772  	}
2773  	return maxAcceptableFeeMsat
2774  }
2775  
2776  // the absolute ceiling on the LSPS2 opening fee for a payment of the given
2777  // size, independent of the fees the LSP advertises
2778  func lsps2MaxAcceptableOpeningFeeMsat(paymentSizeMsat uint64) uint64 {
2779  	return max(lsps2MaxOpeningFeeBaseMsat, paymentSizeMsat/100*lsps2MaxOpeningFeePercent)
2780  }
2781  
2782  // finds the smallest incoming payment for which the user is left
2783  // with a usable amount after the LSP skims its LSPS2 opening fee and the fee
2784  // stays within the absolute fee ceiling applied when creating JIT invoices.
2785  func computeLsps2MinPaymentSizeMsat(params ldk_node.Lsps2OpeningFeeParams) (uint64, bool) {
2786  	// The smallest amount the user must net after the opening fee. We require a
2787  	// whole satoshi rather than a single millisat so the minimum payment size
2788  	// represents a usable receive.
2789  	const minNetReceiveMsat = 1000
2790  
2791  	paymentSizeMsat := params.MinPaymentSizeMsat
2792  
2793  	for range 8 {
2794  		openingFeeMsat := ldk_node.Lsps2ComputeOpeningFeeMsat(paymentSizeMsat, params)
2795  		if openingFeeMsat == nil {
2796  			return 0, false
2797  		}
2798  		// The incoming amount must exceed the opening fee by at least 1 sat,
2799  		// otherwise the user receives a sub-satoshi (effectively zero) amount
2800  		// after the LSP skims its fee. The fee must also stay within the
2801  		// absolute fee ceiling, otherwise invoices of this size are rejected.
2802  		if *openingFeeMsat+minNetReceiveMsat <= paymentSizeMsat &&
2803  			*openingFeeMsat <= lsps2MaxAcceptableOpeningFeeMsat(paymentSizeMsat) {
2804  			return paymentSizeMsat, paymentSizeMsat <= params.MaxPaymentSizeMsat
2805  		}
2806  
2807  		nextPaymentSizeMsat := *openingFeeMsat + minNetReceiveMsat
2808  		if *openingFeeMsat > lsps2MaxOpeningFeeBaseMsat {
2809  			// the smallest payment size at which a fee this large stays within
2810  			// the percentage part of the ceiling
2811  			minSizeForFeeMsat := (*openingFeeMsat + lsps2MaxOpeningFeePercent - 1) / lsps2MaxOpeningFeePercent * 100
2812  			nextPaymentSizeMsat = max(nextPaymentSizeMsat, minSizeForFeeMsat)
2813  		}
2814  		if nextPaymentSizeMsat <= paymentSizeMsat || nextPaymentSizeMsat > params.MaxPaymentSizeMsat {
2815  			return 0, false
2816  		}
2817  		paymentSizeMsat = nextPaymentSizeMsat
2818  	}
2819  
2820  	return 0, false
2821  }
2822  
2823  func sanitizeChainEndpoint(endpoint string, port string) string {
2824  	u, err := url.Parse(endpoint)
2825  	if err != nil || u.Host == "" {
2826  		u, err = url.Parse("//" + endpoint)
2827  	}
2828  	if err != nil {
2829  		return endpoint
2830  	}
2831  
2832  	u.User = nil
2833  	host := u.Hostname()
2834  	if host == "" {
2835  		return endpoint
2836  	}
2837  
2838  	existingPort := u.Port()
2839  	if existingPort == "" {
2840  		existingPort = port
2841  	}
2842  
2843  	if existingPort != "" {
2844  		u.Host = net.JoinHostPort(host, existingPort)
2845  	} else {
2846  		u.Host = host
2847  	}
2848  
2849  	sanitized := u.String()
2850  	if u.Scheme == "" {
2851  		return strings.TrimPrefix(sanitized, "//")
2852  	}
2853  
2854  	return sanitized
2855  }
2856  
2857  func parseLiquiditySourceLsps2(lsps2Address string) (pubkey string, address string) {
2858  	entry := strings.TrimSpace(lsps2Address)
2859  	if entry == "" {
2860  		return "", ""
2861  	}
2862  
2863  	pubkey, address, hasSeparator := strings.Cut(entry, "@")
2864  	if !hasSeparator || pubkey == "" || address == "" {
2865  		logger.Logger.WithField("entry", entry).Warn("Invalid LDK_LSPS2_ADDRESS, expected <pubkey>@<host>:<port>")
2866  		return "", ""
2867  	}
2868  
2869  	if _, _, err := net.SplitHostPort(address); err != nil {
2870  		logger.Logger.WithField("entry", entry).WithError(err).Warn("Invalid LDK_LSPS2_ADDRESS host:port")
2871  		return "", ""
2872  	}
2873  
2874  	return pubkey, address
2875  }
2876