From 032d6b81ea8c20826be1197050752885f0fbb105 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 31 Aug 2025 19:36:26 -0300 Subject: [PATCH 01/26] SendCoins: use SatPerVbyte instead of SatPerByte SatPerByte is deprecated. --- lightning_client.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lightning_client.go b/lightning_client.go index 8060274..67eff0f 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -209,12 +209,12 @@ type LightningClient interface { // SendCoins sends the passed amount of (or all) coins to the passed // address. Either amount or sendAll must be specified, while - // confTarget, satsPerByte are optional and may be set to zero in which + // confTarget, satsPerVByte are optional and may be set to zero in which // case automatic conf target and fee will be used. Returns the tx id // upon success. SendCoins(ctx context.Context, addr btcutil.Address, amount btcutil.Amount, sendAll bool, confTarget int32, - satsPerByte int64, label string) (string, error) + satsPerVByte chainfee.SatPerVByte, label string) (string, error) // ChannelBalance returns a summary of our channel balances. ChannelBalance(ctx context.Context) (*ChannelBalance, error) @@ -3592,12 +3592,12 @@ func (s *lightningClient) Connect(ctx context.Context, peer route.Vertex, } // SendCoins sends the passed amount of (or all) coins to the passed address. -// Either amount or sendAll must be specified, while confTarget, satsPerByte are -// optional and may be set to zero in which case automatic conf target and fee -// will be used. Returns the tx id upon success. +// Either amount or sendAll must be specified, while confTarget, satsPerVByte +// are optional and may be set to zero in which case automatic conf target and +// fee will be used. Returns the tx id upon success. func (s *lightningClient) SendCoins(ctx context.Context, addr btcutil.Address, amount btcutil.Amount, sendAll bool, confTarget int32, - satsPerByte int64, label string) (string, error) { + satsPerVByte chainfee.SatPerVByte, label string) (string, error) { rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() @@ -3605,12 +3605,12 @@ func (s *lightningClient) SendCoins(ctx context.Context, addr btcutil.Address, rpcCtx = s.adminMac.WithMacaroonAuth(rpcCtx) req := &lnrpc.SendCoinsRequest{ - Addr: addr.String(), - Amount: int64(amount), - TargetConf: confTarget, - SatPerByte: satsPerByte, - SendAll: sendAll, - Label: label, + Addr: addr.String(), + Amount: int64(amount), + TargetConf: confTarget, + SatPerVbyte: uint64(satsPerVByte), + SendAll: sendAll, + Label: label, } resp, err := s.client.SendCoins(rpcCtx, req) From 213729991b28c53edad312bf38e714aeee60de3f Mon Sep 17 00:00:00 2001 From: Primexz Date: Sat, 20 Sep 2025 23:05:01 +0200 Subject: [PATCH 02/26] lndclient: expose peer alias --- lightning_client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lightning_client.go b/lightning_client.go index dc38747..9a5d276 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -465,6 +465,9 @@ type ChannelInfo struct { // CustomChannelData is an optional field that can be used to store // data for custom channels. CustomChannelData []byte + + // PeerAlias is the alias of the peer node. + PeerAlias string } func (s *lightningClient) newChannelInfo(channel *lnrpc.Channel) (*ChannelInfo, @@ -509,6 +512,7 @@ func (s *lightningClient) newChannelInfo(channel *lnrpc.Channel) (*ChannelInfo, ZeroConf: channel.ZeroConf, ZeroConfScid: channel.ZeroConfConfirmedScid, CustomChannelData: channel.CustomChannelData, + PeerAlias: channel.PeerAlias, } chanInfo.AliasScids = make([]uint64, len(channel.AliasScids)) From c2919a5b9f5572a72219a031138ee3cc2982a80d Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 1 Oct 2025 15:47:47 +0200 Subject: [PATCH 03/26] build: bump lnd --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index d55e0ba..45bac73 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/btcsuite/btcd/btcutil/psbt v1.1.8 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b - github.com/btcsuite/btcwallet v0.16.15-0.20250811092146-05b3a40651e6 + github.com/btcsuite/btcwallet v0.16.17 github.com/btcsuite/btcwallet/wtxmgr v1.5.6 - github.com/lightningnetwork/lnd v0.19.0-beta.rc5.0.20250905045430-9d74ec47701c + github.com/lightningnetwork/lnd v0.20.0-beta.rc1 github.com/lightningnetwork/lnd/kvdb v1.4.16 github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.59.0 @@ -102,7 +102,7 @@ require ( github.com/lightningnetwork/lnd/fn/v2 v2.0.8 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect github.com/lightningnetwork/lnd/queue v1.1.1 // indirect - github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250905045430-9d74ec47701c // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 // indirect github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect github.com/lightningnetwork/lnd/tor v1.1.6 // indirect @@ -130,7 +130,7 @@ require ( github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect @@ -139,7 +139,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.3.11 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/etcd/api/v3 v3.5.12 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect go.etcd.io/etcd/client/v2 v2.305.12 // indirect diff --git a/go.sum b/go.sum index 43bdd4d..1ac132f 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.15-0.20250811092146-05b3a40651e6 h1:s6NCipDdvDK5rBrC4dIlni1iHsuDOKdfwpL32I3b6Tw= -github.com/btcsuite/btcwallet v0.16.15-0.20250811092146-05b3a40651e6/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA= +github.com/btcsuite/btcwallet v0.16.17 h1:1N6lHznRdcjDopBvcofxaIHknArkJ/EcVKgLKfGL4Dg= +github.com/btcsuite/btcwallet v0.16.17/go.mod h1:YO+W745BAH8n/Rpgj68QsLR6eLlgM4W2do4RejT0buo= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= @@ -355,8 +355,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= -github.com/lightningnetwork/lnd v0.19.0-beta.rc5.0.20250905045430-9d74ec47701c h1:GVMiT7IwHgRCCsih7KWlOHlsyFBy7JZIGKhiSTOzduw= -github.com/lightningnetwork/lnd v0.19.0-beta.rc5.0.20250905045430-9d74ec47701c/go.mod h1:qvNSmsYOEmvb6JIewrv7vF4S2mjUTEYSxAp9m/U94Lw= +github.com/lightningnetwork/lnd v0.20.0-beta.rc1 h1:8Rm3/pcSLQI+tpCjKfYADfMjmEVFkrtoEom470siKRA= +github.com/lightningnetwork/lnd v0.20.0-beta.rc1/go.mod h1:SgniBRmo5pE7IImxIfhUofhgdXkutcV9Znrf/rEZ7TM= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= @@ -367,8 +367,8 @@ github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250905045430-9d74ec47701c h1:tW4o/gn5OdQ8SiSso1SM5jdcXw6O6awKZ4bG7azX9vY= -github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250905045430-9d74ec47701c/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= +github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 h1:f351uGAVayRRe7NEp94kGPC6X7u0lLEvGhLkE+4V3GI= +github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= @@ -481,8 +481,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -521,8 +521,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= From 1d0dbefd787a76752cdca59d95a05bdb0ee24550 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 6 Aug 2025 12:45:13 +0200 Subject: [PATCH 04/26] lndclient: add XFindBaseLocalChanAlias --- router_client.go | 27 +++++++++++++++++++++++++++ testdata/permissions.json | 8 ++++++++ 2 files changed, 35 insertions(+) diff --git a/router_client.go b/router_client.go index 28cf129..d603be9 100644 --- a/router_client.go +++ b/router_client.go @@ -89,6 +89,11 @@ type RouterClient interface { // will not be communicated to the channel peer via any message. XDeleteLocalChanAlias(ctx context.Context, alias, baseScid lnwire.ShortChannelID) error + + // XFindBaseLocalChanAlias is an experimental API that looks up the base + // scid for a local chan alias that was registered. + XFindBaseLocalChanAlias(ctx context.Context, + alias lnwire.ShortChannelID) (lnwire.ShortChannelID, error) } // PaymentStatus describe the state of a payment. @@ -1174,3 +1179,25 @@ func (r *routerClient) XDeleteLocalChanAlias(ctx context.Context, alias, ) return err } + +// XFindBaseLocalChanAlias is an experimental API that looks up the base scid +// for a local chan alias that was registered. +func (r *routerClient) XFindBaseLocalChanAlias(ctx context.Context, + alias lnwire.ShortChannelID) (lnwire.ShortChannelID, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, r.timeout) + defer cancel() + + res, err := r.client.XFindBaseLocalChanAlias( + r.routerKitMac.WithMacaroonAuth(rpcCtx), + &routerrpc.FindBaseAliasRequest{ + Alias: alias.ToUint64(), + }, + ) + + if err != nil { + return lnwire.ShortChannelID{}, err + } + + return lnwire.NewShortChanIDFromInt(res.Base), nil +} diff --git a/testdata/permissions.json b/testdata/permissions.json index e1d7873..f3dc691 100644 --- a/testdata/permissions.json +++ b/testdata/permissions.json @@ -912,6 +912,14 @@ } ] }, + "/routerrpc.Router/XFindBaseLocalChanAlias": { + "permissions": [ + { + "entity": "offchain", + "action": "read" + } + ] + }, "/signrpc.Signer/ComputeInputScript": { "permissions": [ { From fcedbfab8473dbf33b1dc0376c2e0678a991eb5f Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Fri, 10 Oct 2025 13:16:55 -0300 Subject: [PATCH 05/26] SubscribeSingleInvoice: provide full invoice --- invoices_client.go | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/invoices_client.go b/invoices_client.go index 452e1ef..5946419 100644 --- a/invoices_client.go +++ b/invoices_client.go @@ -2,7 +2,6 @@ package lndclient import ( "context" - "errors" "fmt" "io" "sync" @@ -89,9 +88,18 @@ type InvoicesClient interface { handler InvoiceHtlcModifyHandler) error } -// InvoiceUpdate contains a state update for an invoice. +// InvoiceUpdate embeds an Invoice to expose the complete invoice view along +// with the legacy satoshis-paid field used by existing callers. type InvoiceUpdate struct { - State invpkg.ContractState + // Invoice holds the current state of the invoice. + Invoice + + // AmtPaid is the amount that was accepted for this invoice, in sats. + // This will ONLY be set if this invoice has been settled or accepted. + // We provide this field as if the invoice was created with a zero + // value, then we need to record what amount was ultimately accepted. + // Additionally, it's possible that the sender paid MORE that was + // specified in the original invoice. So we'll record that here as well. AmtPaid btcutil.Amount } @@ -203,17 +211,19 @@ func (s *invoicesClient) SubscribeSingleInvoice(ctx context.Context, return } - state, err := fromRPCInvoiceState(invoice.State) + clientInvoice, err := unmarshalInvoice(invoice) if err != nil { errChan <- err return } - select { - case updateChan <- InvoiceUpdate{ - State: state, + invoiceUpdate := InvoiceUpdate{ + Invoice: *clientInvoice, AmtPaid: btcutil.Amount(invoice.AmtPaidSat), - }: + } + + select { + case updateChan <- invoiceUpdate: case <-ctx.Done(): return } @@ -254,26 +264,6 @@ func (s *invoicesClient) AddHoldInvoice(ctx context.Context, return resp.PaymentRequest, nil } -func fromRPCInvoiceState(state lnrpc.Invoice_InvoiceState) ( - invpkg.ContractState, error) { - - switch state { - case lnrpc.Invoice_OPEN: - return invpkg.ContractOpen, nil - - case lnrpc.Invoice_ACCEPTED: - return invpkg.ContractAccepted, nil - - case lnrpc.Invoice_SETTLED: - return invpkg.ContractSettled, nil - - case lnrpc.Invoice_CANCELED: - return invpkg.ContractCanceled, nil - } - - return 0, errors.New("unknown state") -} - // HtlcModifier is a bidirectional streaming RPC that allows a client to // intercept and modify the HTLCs that attempt to settle the given invoice. The // server will send HTLCs of invoices to the client and the client can modify From 793110722cdcfb926d348eae4a88e7e13eec4478 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Thu, 11 Sep 2025 20:26:01 +0200 Subject: [PATCH 06/26] lndclient: handle CloseInstant update --- lightning_client.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lightning_client.go b/lightning_client.go index 8060274..200e6d9 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -3138,6 +3138,10 @@ func (p *PendingCloseUpdate) CloseTxid() chainhash.Hash { type ChannelClosedUpdate struct { // CloseTx is the closing transaction id. CloseTx chainhash.Hash + + // NumPendingHtlcs is the number of pending htlcs that we have + // present while a channel close with the NoWait option was in progress. + NumPendingHtlcs int32 } // CloseTxid returns the closing txid of the channel. @@ -3291,6 +3295,21 @@ func (s *lightningClient) CloseChannel(ctx context.Context, } sendUpdate(closeUpdate) + case *lnrpc.CloseStatusUpdate_CloseInstant: + instantUpdate := update.CloseInstant + if instantUpdate == nil { + sendErr(errors.New("instant update " + + "unavailable")) + + return + } + + numPendingHtlcs := instantUpdate.NumPendingHtlcs + closeUpdate := &ChannelClosedUpdate{ + NumPendingHtlcs: numPendingHtlcs, + } + sendUpdate(closeUpdate) + default: sendErr(fmt.Errorf("unknown channel close "+ "update: %T", resp.Update)) From 1fd28b9e01d3be62ac3dd4f1257b3838b1a1b6db Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Fri, 12 Sep 2025 09:16:17 +0200 Subject: [PATCH 07/26] lndclient: NumberOfPendingHtlcs method for close update --- lightning_client.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lightning_client.go b/lightning_client.go index 200e6d9..f64f408 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -3121,12 +3121,20 @@ func (s *lightningClient) OpenChannelStream(ctx context.Context, type CloseChannelUpdate interface { // CloseTxid returns the closing txid of the channel. CloseTxid() chainhash.Hash + + // NumberOfPendingHtlcs is the number of pending htlcs that we have + // present while a channel close with the NoWait option was in progress. + NumberOfPendingHtlcs() int32 } // PendingCloseUpdate indicates that our closing transaction has been broadcast. type PendingCloseUpdate struct { // CloseTx is the closing transaction id. CloseTx chainhash.Hash + + // NumPendingHtlcs is the number of pending htlcs that we have + // present while a channel close with the NoWait option was in progress. + NumPendingHtlcs int32 } // CloseTxid returns the closing txid of the channel. @@ -3134,6 +3142,12 @@ func (p *PendingCloseUpdate) CloseTxid() chainhash.Hash { return p.CloseTx } +// NumberOfPendingHtlcs returns the number of pending htlcs on a pending close +// channel. +func (p *PendingCloseUpdate) NumberOfPendingHtlcs() int32 { + return p.NumPendingHtlcs +} + // ChannelClosedUpdate indicates that our channel close has confirmed on chain. type ChannelClosedUpdate struct { // CloseTx is the closing transaction id. @@ -3149,6 +3163,12 @@ func (p *ChannelClosedUpdate) CloseTxid() chainhash.Hash { return p.CloseTx } +// NumberOfPendingHtlcs returns the number of pending htlcs on a pending close +// channel. +func (p *ChannelClosedUpdate) NumberOfPendingHtlcs() int32 { + return p.NumPendingHtlcs +} + // CloseChannelOption is a functional type for an option that modifies a // CloseChannelRequest. type CloseChannelOption func(r *lnrpc.CloseChannelRequest) @@ -3305,7 +3325,7 @@ func (s *lightningClient) CloseChannel(ctx context.Context, } numPendingHtlcs := instantUpdate.NumPendingHtlcs - closeUpdate := &ChannelClosedUpdate{ + closeUpdate := &PendingCloseUpdate{ NumPendingHtlcs: numPendingHtlcs, } sendUpdate(closeUpdate) From 08ce5749d586861ce3f1ec377e2e34c8cd653e98 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Fri, 10 Oct 2025 13:16:55 -0300 Subject: [PATCH 08/26] SubscribeSingleInvoice: provide full invoice --- invoices_client.go | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/invoices_client.go b/invoices_client.go index 452e1ef..5946419 100644 --- a/invoices_client.go +++ b/invoices_client.go @@ -2,7 +2,6 @@ package lndclient import ( "context" - "errors" "fmt" "io" "sync" @@ -89,9 +88,18 @@ type InvoicesClient interface { handler InvoiceHtlcModifyHandler) error } -// InvoiceUpdate contains a state update for an invoice. +// InvoiceUpdate embeds an Invoice to expose the complete invoice view along +// with the legacy satoshis-paid field used by existing callers. type InvoiceUpdate struct { - State invpkg.ContractState + // Invoice holds the current state of the invoice. + Invoice + + // AmtPaid is the amount that was accepted for this invoice, in sats. + // This will ONLY be set if this invoice has been settled or accepted. + // We provide this field as if the invoice was created with a zero + // value, then we need to record what amount was ultimately accepted. + // Additionally, it's possible that the sender paid MORE that was + // specified in the original invoice. So we'll record that here as well. AmtPaid btcutil.Amount } @@ -203,17 +211,19 @@ func (s *invoicesClient) SubscribeSingleInvoice(ctx context.Context, return } - state, err := fromRPCInvoiceState(invoice.State) + clientInvoice, err := unmarshalInvoice(invoice) if err != nil { errChan <- err return } - select { - case updateChan <- InvoiceUpdate{ - State: state, + invoiceUpdate := InvoiceUpdate{ + Invoice: *clientInvoice, AmtPaid: btcutil.Amount(invoice.AmtPaidSat), - }: + } + + select { + case updateChan <- invoiceUpdate: case <-ctx.Done(): return } @@ -254,26 +264,6 @@ func (s *invoicesClient) AddHoldInvoice(ctx context.Context, return resp.PaymentRequest, nil } -func fromRPCInvoiceState(state lnrpc.Invoice_InvoiceState) ( - invpkg.ContractState, error) { - - switch state { - case lnrpc.Invoice_OPEN: - return invpkg.ContractOpen, nil - - case lnrpc.Invoice_ACCEPTED: - return invpkg.ContractAccepted, nil - - case lnrpc.Invoice_SETTLED: - return invpkg.ContractSettled, nil - - case lnrpc.Invoice_CANCELED: - return invpkg.ContractCanceled, nil - } - - return 0, errors.New("unknown state") -} - // HtlcModifier is a bidirectional streaming RPC that allows a client to // intercept and modify the HTLCs that attempt to settle the given invoice. The // server will send HTLCs of invoices to the client and the client can modify From 7a15c84a7ae4b12510455578644d03cf009cfb40 Mon Sep 17 00:00:00 2001 From: ffranr Date: Fri, 31 Oct 2025 15:07:59 +0000 Subject: [PATCH 09/26] go.mod: bump LND to v0.19.3-beta Diff generated by running command: ``` go get github.com/lightningnetwork/lnd@v0.19.3-beta go mod tidy ``` --- go.mod | 7 +++---- go.sum | 14 ++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6fd550d..689584b 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/btcutil/psbt v1.1.8 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 - github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 - github.com/btcsuite/btcwallet v0.16.13 + github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b + github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3 github.com/btcsuite/btcwallet/wtxmgr v1.5.6 - github.com/lightningnetwork/lnd v0.19.0-beta + github.com/lightningnetwork/lnd v0.19.3-beta github.com/lightningnetwork/lnd/kvdb v1.4.16 github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.59.0 @@ -49,7 +49,6 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fergusstrange/embedded-postgres v1.25.0 // indirect - github.com/go-errors/errors v1.0.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 9e16b35..5b0f71a 100644 --- a/go.sum +++ b/go.sum @@ -53,11 +53,11 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.13 h1:JGu+wrihQ0I00ODb3w92JtBPbrHxZhbcvU01O+e+lKw= -github.com/btcsuite/btcwallet v0.16.13/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA= +github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3 h1:MAjNRpj3XhCOrhchq4wq0qI34TIBX/DCnT6OLWejx68= +github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= @@ -149,8 +149,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -353,8 +351,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.19.0-beta h1:/8i2UdARiEpI2iAmPoSDcwZSSEuWqXyfsMxz/mLGbdw= -github.com/lightningnetwork/lnd v0.19.0-beta/go.mod h1:hu6zo1zcznx7nViiFlJY8qGDwwGw5LNLdGJ7ICz5Ysc= +github.com/lightningnetwork/lnd v0.19.3-beta h1:sBOIn+0ZIkvEJh05VPJRSOOhWbJn2EoGtyUAaq/Fgk8= +github.com/lightningnetwork/lnd v0.19.3-beta/go.mod h1:MNRzea8Yrgk+ohyUhK7JSpoigE4T9JgerMQQUxMbl9I= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= From 8b7081d001468559ffcdfc168d91df6d78fa6f3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:45:57 +0000 Subject: [PATCH 10/26] build(deps): bump github.com/opencontainers/runc from 1.2.0 to 1.2.8 Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.2.0 to 1.2.8. - [Release notes](https://github.com/opencontainers/runc/releases) - [Changelog](https://github.com/opencontainers/runc/blob/v1.2.8/CHANGELOG.md) - [Commits](https://github.com/opencontainers/runc/compare/v1.2.0...v1.2.8) --- updated-dependencies: - dependency-name: github.com/opencontainers/runc dependency-version: 1.2.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 689584b..43dd10f 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect @@ -119,7 +120,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.2.0 // indirect + github.com/opencontainers/runc v1.2.8 // indirect github.com/ory/dockertest/v3 v3.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5b0f71a..452b388 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,9 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -425,8 +426,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.2.0 h1:qke7ZVCmJcKrJVY2iHJVC+0kql9uYdkusOPsQOOeBw4= -github.com/opencontainers/runc v1.2.0/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= +github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= +github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= From 4ae42de25658283debdd6131f3587e2885077e8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:45:57 +0000 Subject: [PATCH 11/26] build(deps): bump github.com/opencontainers/runc from 1.2.0 to 1.2.8 Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.2.0 to 1.2.8. - [Release notes](https://github.com/opencontainers/runc/releases) - [Changelog](https://github.com/opencontainers/runc/blob/v1.2.8/CHANGELOG.md) - [Commits](https://github.com/opencontainers/runc/compare/v1.2.0...v1.2.8) --- updated-dependencies: - dependency-name: github.com/opencontainers/runc dependency-version: 1.2.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 45bac73..7cd659c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect @@ -118,7 +119,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.2.0 // indirect + github.com/opencontainers/runc v1.2.8 // indirect github.com/ory/dockertest/v3 v3.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1ac132f..02261c6 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,9 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -429,8 +430,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.2.0 h1:qke7ZVCmJcKrJVY2iHJVC+0kql9uYdkusOPsQOOeBw4= -github.com/opencontainers/runc v1.2.0/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= +github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= +github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= From 0377cc8fe9dd66cfbb57cb37e705ca350baa4a16 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 31 Aug 2025 19:36:26 -0300 Subject: [PATCH 12/26] SendCoins: use SatPerVbyte instead of SatPerByte SatPerByte is deprecated. --- lightning_client.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lightning_client.go b/lightning_client.go index f64f408..71e00d0 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -209,12 +209,12 @@ type LightningClient interface { // SendCoins sends the passed amount of (or all) coins to the passed // address. Either amount or sendAll must be specified, while - // confTarget, satsPerByte are optional and may be set to zero in which + // confTarget, satsPerVByte are optional and may be set to zero in which // case automatic conf target and fee will be used. Returns the tx id // upon success. SendCoins(ctx context.Context, addr btcutil.Address, amount btcutil.Amount, sendAll bool, confTarget int32, - satsPerByte int64, label string) (string, error) + satsPerVByte chainfee.SatPerVByte, label string) (string, error) // ChannelBalance returns a summary of our channel balances. ChannelBalance(ctx context.Context) (*ChannelBalance, error) @@ -3631,12 +3631,12 @@ func (s *lightningClient) Connect(ctx context.Context, peer route.Vertex, } // SendCoins sends the passed amount of (or all) coins to the passed address. -// Either amount or sendAll must be specified, while confTarget, satsPerByte are -// optional and may be set to zero in which case automatic conf target and fee -// will be used. Returns the tx id upon success. +// Either amount or sendAll must be specified, while confTarget, satsPerVByte +// are optional and may be set to zero in which case automatic conf target and +// fee will be used. Returns the tx id upon success. func (s *lightningClient) SendCoins(ctx context.Context, addr btcutil.Address, amount btcutil.Amount, sendAll bool, confTarget int32, - satsPerByte int64, label string) (string, error) { + satsPerVByte chainfee.SatPerVByte, label string) (string, error) { rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() @@ -3644,12 +3644,12 @@ func (s *lightningClient) SendCoins(ctx context.Context, addr btcutil.Address, rpcCtx = s.adminMac.WithMacaroonAuth(rpcCtx) req := &lnrpc.SendCoinsRequest{ - Addr: addr.String(), - Amount: int64(amount), - TargetConf: confTarget, - SatPerByte: satsPerByte, - SendAll: sendAll, - Label: label, + Addr: addr.String(), + Amount: int64(amount), + TargetConf: confTarget, + SatPerVbyte: uint64(satsPerVByte), + SendAll: sendAll, + Label: label, } resp, err := s.client.SendCoins(rpcCtx, req) From 9d667a1c281626303e9802e0292ccec09145ef39 Mon Sep 17 00:00:00 2001 From: ffranr Date: Thu, 13 Nov 2025 12:21:26 +0000 Subject: [PATCH 13/26] go.mod: bump LND to v0.20.0-beta Updated LND dependency to v0.20.0-beta by running: go get github.com/lightningnetwork/lnd@v0.20.0-beta go mod tidy Also updated Go version in the lint Dockerfile to match go.mod. --- go.mod | 8 ++++---- go.sum | 12 ++++++------ tools/Dockerfile | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 45bac73..61b9332 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b github.com/btcsuite/btcwallet v0.16.17 github.com/btcsuite/btcwallet/wtxmgr v1.5.6 - github.com/lightningnetwork/lnd v0.20.0-beta.rc1 + github.com/lightningnetwork/lnd v0.20.0-beta github.com/lightningnetwork/lnd/kvdb v1.4.16 github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.59.0 @@ -99,10 +99,10 @@ require ( github.com/lightninglabs/neutrino/cache v1.1.2 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 // indirect github.com/lightningnetwork/lnd/clock v1.1.1 // indirect - github.com/lightningnetwork/lnd/fn/v2 v2.0.8 // indirect + github.com/lightningnetwork/lnd/fn/v2 v2.0.9 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect github.com/lightningnetwork/lnd/queue v1.1.1 // indirect - github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.11 // indirect github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect github.com/lightningnetwork/lnd/tor v1.1.6 // indirect @@ -192,4 +192,4 @@ require ( // allows us to specify that as an option. replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display -go 1.24.6 +go 1.24.9 diff --git a/go.sum b/go.sum index 1ac132f..ba26003 100644 --- a/go.sum +++ b/go.sum @@ -355,20 +355,20 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= -github.com/lightningnetwork/lnd v0.20.0-beta.rc1 h1:8Rm3/pcSLQI+tpCjKfYADfMjmEVFkrtoEom470siKRA= -github.com/lightningnetwork/lnd v0.20.0-beta.rc1/go.mod h1:SgniBRmo5pE7IImxIfhUofhgdXkutcV9Znrf/rEZ7TM= +github.com/lightningnetwork/lnd v0.20.0-beta h1:ML+jgJ3UKDGJdUf0m73ZeR/szJKWVtHxpQP+yFC79b8= +github.com/lightningnetwork/lnd v0.20.0-beta/go.mod h1:8hc55AnE3mMSJ/UAEJZgmhgNCcH0yWaPg0olpxhhp4M= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= -github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= -github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= +github.com/lightningnetwork/lnd/fn/v2 v2.0.9 h1:ZytG4ltPac/sCyg1EJDn10RGzPIDJeyennUMRdOw7Y8= +github.com/lightningnetwork/lnd/fn/v2 v2.0.9/go.mod h1:aPUJHJ31S+Lgoo8I5SxDIjnmeCifqujaiTXKZqpav3w= github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI= github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ= github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p3HX1xtUdbDI= github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 h1:f351uGAVayRRe7NEp94kGPC6X7u0lLEvGhLkE+4V3GI= -github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= +github.com/lightningnetwork/lnd/sqldb v1.0.11 h1:X8J3OvdIhJVniQG78Qsp3niErl1zdGMTPvzgiLMWOOo= +github.com/lightningnetwork/lnd/sqldb v1.0.11/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= diff --git a/tools/Dockerfile b/tools/Dockerfile index 21d08d6..82cdc11 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.6-bookworm +FROM golang:1.24.9-bookworm RUN apt-get update && apt-get install -y git ENV GOCACHE=/tmp/build/.cache From 93872e011565c8701d2ac6d040a30f4d182a26a1 Mon Sep 17 00:00:00 2001 From: ffranr Date: Thu, 13 Nov 2025 13:18:51 +0000 Subject: [PATCH 14/26] lnd_services: bump minimum compatible LND version to v0.19.0 While v0.18.5-beta likely still works, raising the minimum version reduces the need to handle deprecated fields, even if they're technically still supported. --- lnd_services.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnd_services.go b/lnd_services.go index 257068c..9ef5653 100644 --- a/lnd_services.go +++ b/lnd_services.go @@ -41,8 +41,8 @@ var ( // fallback version if none is specified in the configuration. minimalCompatibleVersion = &verrpc.Version{ AppMajor: 0, - AppMinor: 18, - AppPatch: 5, + AppMinor: 19, + AppPatch: 0, BuildTags: DefaultBuildTags, } From 7f865a705ed78cde4f1bc1f17cbc0938f731ad46 Mon Sep 17 00:00:00 2001 From: ffranr Date: Thu, 13 Nov 2025 12:34:47 +0000 Subject: [PATCH 15/26] README: update and simplify compatibility matrix table --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4e2a754..98021bd 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,10 @@ different versions. There are two "levels" of depending on a version of The current compatibility matrix reads as follows: -| `lndclient` git tag | `lnd` version in `go.mod` | minimum required `lnd` version | -|---------------------------------------------------------------------------------------|---------------------------|--------------------------------| -| `master` / [`v0.18.5-13`](https://github.com/lightninglabs/lndclient/blob/v0.18.5-13) | `v0.18.5-beta` | `v0.18.5-beta` | +| tag | `lnd` version in `go.mod` | minimum required `lnd` version | +|--------------|---------------------------|--------------------------------| +| `v0.18.5-13` | `v0.18.5-beta` | `v0.18.5-beta` | +| `v0.20.0-4` | `v0.20.0-beta` | `v0.19.0-beta` | By default, `lndclient` requires (and enforces) the following RPC subservers to From 8d4f93c0ed578b0e89ae6c082c9e07d1c89a55fa Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 10 Nov 2025 17:13:50 -0300 Subject: [PATCH 16/26] lndclient: block until chain notifier is ready LND commit c6f458e478f9 (v0.20.0-rc3) moved ChainNotifier startup later in the lifecycle, so RegisterBlockEpochNtfn callers now see "chain notifier RPC is still in the process of starting" coming from Recv(). The new BlockUntilChainNotifier config option repeatedly calls RegisterBlockEpochNtfn during startup and only proceeds once the stream yields its first block height, retrying solely when we detect the ErrChainNotifierServerNotActive condition introduced by the LND commit above. --- lnd_services.go | 126 +++++++++++++++++++++++++++++++++++++++++++ lnd_services_test.go | 24 +++++++++ 2 files changed, 150 insertions(+) diff --git a/lnd_services.go b/lnd_services.go index 9ef5653..ecb5268 100644 --- a/lnd_services.go +++ b/lnd_services.go @@ -143,6 +143,11 @@ type LndServicesConfig struct { // block download is still in progress. BlockUntilChainSynced bool + // BlockUntilChainNotifier indicates that the client should wait until + // the ChainNotifier RPC is accepting subscriptions. This requires lnd + // to be built with the "chainrpc" tag. + BlockUntilChainNotifier bool + // BlockUntilUnlocked denotes that the NewLndServices function should // block until lnd is unlocked. BlockUntilUnlocked bool @@ -453,6 +458,25 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) { log.Infof("lnd is now fully synced to its chain backend") } + // If requested, wait until the chain notifier RPC is ready before we + // return. This ensures sub-servers relying on the notifier don't fail + // during startup. + if cfg.BlockUntilChainNotifier { + log.Infof("Waiting for chain notifier RPC to be ready") + + err := services.waitForChainNotifier( + cfg.CallerCtx, timeout, cfg.ChainSyncPollInterval, + ) + if err != nil { + cleanup() + + return nil, fmt.Errorf("error waiting for chain "+ + "notifier readiness: %w", err) + } + + log.Infof("Chain notifier RPC is ready") + } + return services, nil } @@ -533,6 +557,77 @@ func (s *GrpcLndServices) waitForChainSync(ctx context.Context, return <-update } +// waitForChainNotifier blocks until the ChainNotifier RPC accepts block epoch +// subscriptions and delivers at least one block height. +func (s *GrpcLndServices) waitForChainNotifier(ctx context.Context, + timeout, pollInterval time.Duration) error { + + register := s.ChainNotifier.RegisterBlockEpochNtfn + + var errRetry = errors.New("retry RegisterBlockEpochNtfn") + + // attempt is a single attempt to make a RegisterBlockEpochNtfn call. + // It returns nil on success, errRetry if another retry is needed and + // other error in case of a final error. + attempt := func() error { + subCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Make new RegisterBlockEpochNtfn call. + blockChan, errChan, err := register(subCtx) + if err != nil { + return fmt.Errorf("register block epoch ntfn: %w", err) + } + + // Wait for block height notification, which indicates success. + select { + case <-subCtx.Done(): + return subCtx.Err() + + case err := <-errChan: + // If chainNotifier is not ready yet, retry. + if isChainNotifierStartingErr(err) { + select { + case <-time.After(pollInterval): + return errRetry + + case <-ctx.Done(): + return ctx.Err() + } + } + + return err + + // We got a block height. Success! + case <-blockChan: + return nil + } + } + + // Main retry loop. + for { + log.Info("Trying to make RegisterBlockEpochNtfn and receive " + + "the current height...") + err := attempt() + + if errors.Is(err, errRetry) { + log.Info("ChainNotifier is not ready yet. Will retry") + + continue + } else if err != nil { + log.Info("RegisterBlockEpochNtfn returned unexpected "+ + "error %v. LND client failed to start!", err) + + return err + } + + log.Info("RegisterBlockEpochNtfn returned a height. Success!") + break + } + + return nil +} + // getLndInfo queries lnd for information about the node it is connected to. // If the waitForUnlocked boolean is set, it will examine any errors returned // and back off if the failure is due to lnd currently being locked. Otherwise, @@ -676,6 +771,37 @@ func IsUnlockError(err error) bool { return false } +// chainNotifierStartupMessage matches the error string returned by lnd +// v0.20.0-rc3+ when a ChainNotifier RPC is invoked before the sub-server +// finishes initialization. +const chainNotifierStartupMessage = "chain notifier RPC is still in the " + + "process of starting" + +// isChainNotifierStartingErr reports whether err is due to the lnd +// ChainNotifier sub-server still starting up. Starting with lnd v0.20.0-rc3 +// the notifier is initialised later in the daemon lifecycle, and the RPC layer +// surfaces this as an Unknown gRPC status that contains the message defined in +// chainNotifierStartupMessage. There is a PR in LND to return code Unavailable +// instead of Unknown: https://github.com/lightningnetwork/lnd/pull/10352 +func isChainNotifierStartingErr(err error) bool { + if err == nil { + return false + } + + // gRPC code Unavailable means "the server can't handle this request + // now, retry later". LND's chain notifier returns this error when + // the server is starting. + // See https://github.com/lightningnetwork/lnd/pull/10352 + st, ok := status.FromError(err) + if ok && st.Code() == codes.Unavailable { + return true + } + + // TODO(ln-v0.20.0) remove the string fallback once lndclient depends on + // a version of lnd that returns codes.Unavailable for this condition. + return strings.Contains(err.Error(), chainNotifierStartupMessage) +} + // checkLndCompatibility makes sure the connected lnd instance is running on the // correct network, has the version RPC implemented, is the correct minimal // version and supports all required build tags/subservers. diff --git a/lnd_services_test.go b/lnd_services_test.go index 8bade98..4a23975 100644 --- a/lnd_services_test.go +++ b/lnd_services_test.go @@ -361,3 +361,27 @@ func TestCustomMacaroonHex(t *testing.T) { _, err = NewLndServices(testCfg) require.Error(t, err, "must set only one") } + +// TestIsChainNotifierStartingErr ensures we correctly detect the startup lag +// error returned by lnd v0.20.0-rc3+. +func TestIsChainNotifierStartingErr(t *testing.T) { + t.Parallel() + + require.True(t, isChainNotifierStartingErr( + status.Error(codes.Unavailable, chainNotifierStartupMessage), + )) + + require.True(t, isChainNotifierStartingErr( + status.Error(codes.Unknown, chainNotifierStartupMessage), + )) + + require.True(t, isChainNotifierStartingErr( + status.Error(codes.Unavailable, "some other error"), + )) + + require.False(t, isChainNotifierStartingErr(nil)) + + require.False(t, isChainNotifierStartingErr( + status.Error(codes.Unknown, "some other error"), + )) +} From d93a6a54b108dae5427b1e09abbf2f3e0e6128e7 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Thu, 22 Jan 2026 11:36:38 -0500 Subject: [PATCH 17/26] lndclient: silence expected error on shutdown During planned termination, conn.Close() can return ErrClientConnClosing from gRPC package. This is expected and should not be logged as an error. Downgrade that case to debug so logs stay clean during shutdown. Original log line: [ERR] LNDC: Error closing lnd connection: rpc error: code = Canceled desc = grpc: the client connection is closing --- lnd_services.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lnd_services.go b/lnd_services.go index ecb5268..c8558fb 100644 --- a/lnd_services.go +++ b/lnd_services.go @@ -332,7 +332,15 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) { cleanupConn := func() { closeErr := conn.Close() - if closeErr != nil { + switch { + case closeErr == nil: + // No error. + + case errors.Is(closeErr, grpc.ErrClientConnClosing): + log.Debugf("LND connection is already closing: %v", + closeErr) + + default: log.Errorf("Error closing lnd connection: %v", closeErr) } } From 067829821d035f38fc36954b1da676825de050a0 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Thu, 12 Feb 2026 17:08:51 +0100 Subject: [PATCH 18/26] go.mod: bump lnd to v0.20.1-beta, go to 1.25.5 --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index a07e762..986ab1c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b github.com/btcsuite/btcwallet v0.16.17 github.com/btcsuite/btcwallet/wtxmgr v1.5.6 - github.com/lightningnetwork/lnd v0.20.0-beta + github.com/lightningnetwork/lnd v0.20.1-beta github.com/lightningnetwork/lnd/kvdb v1.4.16 github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.59.0 @@ -103,7 +103,7 @@ require ( github.com/lightningnetwork/lnd/fn/v2 v2.0.9 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect github.com/lightningnetwork/lnd/queue v1.1.1 // indirect - github.com/lightningnetwork/lnd/sqldb v1.0.11 // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1 // indirect github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect github.com/lightningnetwork/lnd/tor v1.1.6 // indirect @@ -193,4 +193,4 @@ require ( // allows us to specify that as an option. replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display -go 1.24.9 +go 1.25.5 diff --git a/go.sum b/go.sum index 400b036..45c3c7c 100644 --- a/go.sum +++ b/go.sum @@ -356,8 +356,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= -github.com/lightningnetwork/lnd v0.20.0-beta h1:ML+jgJ3UKDGJdUf0m73ZeR/szJKWVtHxpQP+yFC79b8= -github.com/lightningnetwork/lnd v0.20.0-beta/go.mod h1:8hc55AnE3mMSJ/UAEJZgmhgNCcH0yWaPg0olpxhhp4M= +github.com/lightningnetwork/lnd v0.20.1-beta h1:wDMNgks5uST1CY+WwjIZ4+McPMMFpr2pIIGJp7ytDI4= +github.com/lightningnetwork/lnd v0.20.1-beta/go.mod h1:oIKh9EqE1sJJpQPq9ZCMFc4Ot287NrotZ1oZn0zUI+M= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn/v2 v2.0.9 h1:ZytG4ltPac/sCyg1EJDn10RGzPIDJeyennUMRdOw7Y8= @@ -368,8 +368,8 @@ github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.11 h1:X8J3OvdIhJVniQG78Qsp3niErl1zdGMTPvzgiLMWOOo= -github.com/lightningnetwork/lnd/sqldb v1.0.11/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= +github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1 h1:PkEppKL17cZh0Dr9h/T9BEVJUbd/p2tjJ/x8ffG3R0M= +github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1/go.mod h1:tB2jlqu79TIOR9uhAZOmPxpVFUhB2s+oxKnqRRL1oc0= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= From 8961eb587974e7c2ee4988ec6a036ea89ecf26e3 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Fri, 13 Feb 2026 09:00:02 +0100 Subject: [PATCH 19/26] tools: update go version --- .github/workflows/main.yml | 2 +- tools/Dockerfile | 2 +- tools/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bdc8280..2cdfa2b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ defaults: shell: bash env: - GO_VERSION: 1.24.6 + GO_VERSION: 1.26 jobs: build: diff --git a/tools/Dockerfile b/tools/Dockerfile index 82cdc11..00d43d5 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.9-bookworm +FROM golang:1.26.0-bookworm RUN apt-get update && apt-get install -y git ENV GOCACHE=/tmp/build/.cache diff --git a/tools/go.mod b/tools/go.mod index 02a0080..ed85df7 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,6 @@ module github.com/lightninglabs/lndclient/tools -go 1.24.6 +go 1.25.5 require ( // Once golangci-lint v2.4.1 update it here. From 6f15e2c56372bea665d0ce7d12853d0c6f003178 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 16:31:20 -0500 Subject: [PATCH 20/26] lndclient: preserve addinvoice route hints Marshal explicit route hints in lightningClient.AddInvoice so the standard invoice path matches AddHoldInvoice. Add regression coverage for direct AddInvoice route hint encoding and for route hint parity between AddInvoice and AddHoldInvoice. --- invoices_client_test.go | 157 +++++++++++++++++++++++++++++++++++++++ lightning_client.go | 8 ++ lightning_client_test.go | 70 ++++++++++++++++- 3 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 invoices_client_test.go diff --git a/invoices_client_test.go b/invoices_client_test.go new file mode 100644 index 0000000..6d48b05 --- /dev/null +++ b/invoices_client_test.go @@ -0,0 +1,157 @@ +package lndclient + +import ( + "bytes" + "context" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/zpay32" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +// testInvoiceRouteHints returns deterministic route hints for invoice tests. +func testInvoiceRouteHints() [][]zpay32.HopHint { + _, pubKey1 := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{1}, 32)) + _, pubKey2 := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{2}, 32)) + _, pubKey3 := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{3}, 32)) + + return [][]zpay32.HopHint{ + { + { + NodeID: pubKey1, + ChannelID: 101, + FeeBaseMSat: 1001, + FeeProportionalMillionths: 2001, + CLTVExpiryDelta: 40, + }, + { + NodeID: pubKey2, + ChannelID: 102, + FeeBaseMSat: 1002, + FeeProportionalMillionths: 2002, + CLTVExpiryDelta: 41, + }, + }, + { + { + NodeID: pubKey3, + ChannelID: 103, + FeeBaseMSat: 1003, + FeeProportionalMillionths: 2003, + CLTVExpiryDelta: 42, + }, + }, + } +} + +// testRPCRouteHints returns the RPC form of the deterministic route hints. +func testRPCRouteHints(t *testing.T) []*lnrpc.RouteHint { + t.Helper() + + rpcRouteHints, err := marshallRouteHints(testInvoiceRouteHints()) + require.NoError(t, err) + + return rpcRouteHints +} + +// addHoldInvoiceArg records the args used in +// mockInvoicesRPCClient.AddHoldInvoice. +type addHoldInvoiceArg struct { + in *invoicesrpc.AddHoldInvoiceRequest + opts []grpc.CallOption +} + +// mockInvoicesRPCClient implements invoicesrpc.InvoicesClient with a dynamic +// AddHoldInvoice implementation and call spying. +type mockInvoicesRPCClient struct { + invoicesrpc.InvoicesClient + + addHoldInvoice func(in *invoicesrpc.AddHoldInvoiceRequest, + opts ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, + error) + + addHoldInvoiceArgs []addHoldInvoiceArg +} + +// AddHoldInvoice records the call and forwards it to the test hook. +func (m *mockInvoicesRPCClient) AddHoldInvoice(ctx context.Context, + in *invoicesrpc.AddHoldInvoiceRequest, + opts ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, error) { + + m.addHoldInvoiceArgs = append(m.addHoldInvoiceArgs, addHoldInvoiceArg{ + in: in, + opts: opts, + }) + + return m.addHoldInvoice(in, opts...) +} + +// TestInvoiceClientAddInvoiceRouteHintParity ensures AddInvoice and +// AddHoldInvoice encode the same route hints for the same invoice input. +func TestInvoiceClientAddInvoiceRouteHintParity(t *testing.T) { + var validPreimage lntypes.Preimage + copy(validPreimage[:], "valid preimage") + + var validRHash lntypes.Hash + copy(validRHash[:], "valid hash") + + invoice := &invoicesrpc.AddInvoiceData{ + Memo: "fake memo", + Preimage: &validPreimage, + Hash: &validRHash, + Value: lnwire.MilliSatoshi(500000), + DescriptionHash: []byte("fake 32 byte hash"), + Expiry: 123, + CltvExpiry: 456, + RouteHints: testInvoiceRouteHints(), + } + + lightningRPC := &mockRPCClient{ + addInvoice: func(_ *lnrpc.Invoice, + _ ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, + error) { + + return &lnrpc.AddInvoiceResponse{ + RHash: validRHash[:], + PaymentRequest: "swap invoice", + }, nil + }, + } + holdRPC := &mockInvoicesRPCClient{ + addHoldInvoice: func(_ *invoicesrpc.AddHoldInvoiceRequest, + _ ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, + error) { + + return &invoicesrpc.AddHoldInvoiceResp{ + PaymentRequest: "probe invoice", + }, nil + }, + } + + lightning := &lightningClient{ + client: lightningRPC, + } + invoices := &invoicesClient{ + client: holdRPC, + } + + _, _, err := lightning.AddInvoice(t.Context(), invoice) + require.NoError(t, err) + + _, err = invoices.AddHoldInvoice(t.Context(), invoice) + require.NoError(t, err) + + require.Len(t, lightningRPC.addInvoiceArgs, 1) + require.Len(t, holdRPC.addHoldInvoiceArgs, 1) + + require.Equal( + t, holdRPC.addHoldInvoiceArgs[0].in.RouteHints, + lightningRPC.addInvoiceArgs[0].in.RouteHints, + ) +} diff --git a/lightning_client.go b/lightning_client.go index 35a70cb..f63b6e8 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1665,6 +1665,13 @@ func (s *lightningClient) AddInvoice(ctx context.Context, rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() + routeHints, err := marshallRouteHints(in.RouteHints) + if err != nil { + return lntypes.Hash{}, "", fmt.Errorf( + "failed to marshal route hints: %v", err, + ) + } + rpcIn := &lnrpc.Invoice{ Memo: in.Memo, ValueMsat: int64(in.Value), @@ -1672,6 +1679,7 @@ func (s *lightningClient) AddInvoice(ctx context.Context, Expiry: in.Expiry, CltvExpiry: in.CltvExpiry, Private: in.Private, + RouteHints: routeHints, } if in.Preimage != nil { diff --git a/lightning_client_test.go b/lightning_client_test.go index 44ec327..f5df568 100644 --- a/lightning_client_test.go +++ b/lightning_client_test.go @@ -40,6 +40,37 @@ func (m *mockRPCClient) AddInvoice(ctx context.Context, in *lnrpc.Invoice, return m.addInvoice(in, opts...) } +// assertAddInvoiceArgs verifies the recorded AddInvoice RPC calls. +func assertAddInvoiceArgs(t *testing.T, want, got []addInvoiceArg) { + t.Helper() + + require.Len(t, got, len(want)) + + for i := range want { + require.Equal(t, want[i].opts, got[i].opts) + require.Equal(t, want[i].in.Memo, got[i].in.Memo) + require.Equal(t, want[i].in.RPreimage, got[i].in.RPreimage) + require.Equal(t, want[i].in.RHash, got[i].in.RHash) + require.Equal(t, want[i].in.ValueMsat, got[i].in.ValueMsat) + require.Equal( + t, want[i].in.DescriptionHash, + got[i].in.DescriptionHash, + ) + require.Equal(t, want[i].in.Expiry, got[i].in.Expiry) + require.Equal( + t, want[i].in.CltvExpiry, got[i].in.CltvExpiry, + ) + require.Equal(t, want[i].in.Private, got[i].in.Private) + + if len(want[i].in.RouteHints) == 0 { + require.Empty(t, got[i].in.RouteHints) + continue + } + + require.Equal(t, want[i].in.RouteHints, got[i].in.RouteHints) + } +} + // TestLightningClientAddInvoice ensures that adding an invoice via // lightningClient is completed as expected. func TestLightningClientAddInvoice(t *testing.T) { @@ -48,6 +79,9 @@ func TestLightningClientAddInvoice(t *testing.T) { copy(validPreimage[:], "valid preimage") var validRHash lntypes.Hash copy(validRHash[:], "valid hash") + validRouteHints := testInvoiceRouteHints() + validRPCRouteHints := testRPCRouteHints(t) + validAddInvoiceData := &invoicesrpc.AddInvoiceData{ Memo: "fake memo", Preimage: &validPreimage, @@ -100,6 +134,22 @@ func TestLightningClientAddInvoice(t *testing.T) { {in: privateInvoice}, } + routeHintAddInvoiceData := *validAddInvoiceData + routeHintAddInvoiceData.RouteHints = validRouteHints + routeHintInvoice := &lnrpc.Invoice{ + Memo: validAddInvoiceData.Memo, + RPreimage: validAddInvoiceData.Preimage[:], + RHash: validAddInvoiceData.Hash[:], + ValueMsat: int64(validAddInvoiceData.Value), + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + CltvExpiry: validAddInvoiceData.CltvExpiry, + RouteHints: validRPCRouteHints, + } + routeHintAddInvoiceArgs := []addInvoiceArg{ + {in: routeHintInvoice}, + } + errorAddInvoice := func(in *lnrpc.Invoice, opts ...grpc.CallOption) ( *lnrpc.AddInvoiceResponse, error) { @@ -147,6 +197,18 @@ func TestLightningClientAddInvoice(t *testing.T) { payRequest: validPayReq, }, }, + { + name: "invoice with route hints", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: &routeHintAddInvoiceData, + expect: expect{ + addInvoiceArgs: routeHintAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, { name: "rpc client error", client: mockRPCClient{ @@ -167,7 +229,7 @@ func TestLightningClientAddInvoice(t *testing.T) { } hash, payRequest, err := ln.AddInvoice( - context.Background(), test.invoice, + t.Context(), test.invoice, ) // Check if an error (or no error) was received as @@ -192,9 +254,9 @@ func TestLightningClientAddInvoice(t *testing.T) { // Check if the expected args were passed to the RPC // client call. - require.Equal(t, test.client.addInvoiceArgs, - test.expect.addInvoiceArgs, - "rpc client call was not made as expected", + assertAddInvoiceArgs( + t, test.expect.addInvoiceArgs, + test.client.addInvoiceArgs, ) }) } From 157b89e68ac4d26e82aee52e6e704f51acaacced Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 17:21:36 -0500 Subject: [PATCH 21/26] lndclient: preserve addinvoice fallback address Set FallbackAddr on the standard AddInvoice RPC request so the lightning client preserves explicit on-chain fallback addresses. Extend the existing invoice tests with direct AddInvoice coverage and parity checks against AddHoldInvoice. --- invoices_client_test.go | 50 +++++++++++++++++++++++++++++++--------- lightning_client.go | 1 + lightning_client_test.go | 31 +++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/invoices_client_test.go b/invoices_client_test.go index 6d48b05..93706ed 100644 --- a/invoices_client_test.go +++ b/invoices_client_test.go @@ -50,6 +50,9 @@ func testInvoiceRouteHints() [][]zpay32.HopHint { } } +// fallbackAddr is just a Bitcoin address used for tests of FallbackAddr field. +const fallbackAddr = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080" + // testRPCRouteHints returns the RPC form of the deterministic route hints. func testRPCRouteHints(t *testing.T) []*lnrpc.RouteHint { t.Helper() @@ -92,26 +95,51 @@ func (m *mockInvoicesRPCClient) AddHoldInvoice(ctx context.Context, return m.addHoldInvoice(in, opts...) } -// TestInvoiceClientAddInvoiceRouteHintParity ensures AddInvoice and -// AddHoldInvoice encode the same route hints for the same invoice input. -func TestInvoiceClientAddInvoiceRouteHintParity(t *testing.T) { +// assertInvoiceRequestParity verifies the shared fields that should be encoded +// identically by AddInvoice and AddHoldInvoice. +func assertInvoiceRequestParity(t *testing.T, add *lnrpc.Invoice, + hold *invoicesrpc.AddHoldInvoiceRequest) { + + t.Helper() + + require.Equal(t, add.Memo, hold.Memo) + require.Equal(t, add.ValueMsat, hold.ValueMsat) + require.Equal(t, add.DescriptionHash, hold.DescriptionHash) + require.Equal(t, add.Expiry, hold.Expiry) + require.Equal(t, add.FallbackAddr, hold.FallbackAddr) + require.Equal(t, add.CltvExpiry, hold.CltvExpiry) + require.Equal(t, add.Private, hold.Private) + require.Equal(t, add.RouteHints, hold.RouteHints) +} + +// TestInvoiceClientAddInvoiceParity ensures AddInvoice and AddHoldInvoice +// encode the same explicit invoice fields for the same invoice input. +func TestInvoiceClientAddInvoiceParity(t *testing.T) { var validPreimage lntypes.Preimage copy(validPreimage[:], "valid preimage") var validRHash lntypes.Hash copy(validRHash[:], "valid hash") - invoice := &invoicesrpc.AddInvoiceData{ + sharedInvoice := invoicesrpc.AddInvoiceData{ Memo: "fake memo", - Preimage: &validPreimage, - Hash: &validRHash, Value: lnwire.MilliSatoshi(500000), DescriptionHash: []byte("fake 32 byte hash"), Expiry: 123, + FallbackAddr: fallbackAddr, CltvExpiry: 456, + Private: true, RouteHints: testInvoiceRouteHints(), } + // The two wrappers use different invoice creation RPCs, so we provide + // path-specific fixtures for their mutually exclusive fields. + lightningInvoice := sharedInvoice + lightningInvoice.Preimage = &validPreimage + + holdInvoice := sharedInvoice + holdInvoice.Hash = &validRHash + lightningRPC := &mockRPCClient{ addInvoice: func(_ *lnrpc.Invoice, _ ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, @@ -141,17 +169,17 @@ func TestInvoiceClientAddInvoiceRouteHintParity(t *testing.T) { client: holdRPC, } - _, _, err := lightning.AddInvoice(t.Context(), invoice) + _, _, err := lightning.AddInvoice(t.Context(), &lightningInvoice) require.NoError(t, err) - _, err = invoices.AddHoldInvoice(t.Context(), invoice) + _, err = invoices.AddHoldInvoice(t.Context(), &holdInvoice) require.NoError(t, err) require.Len(t, lightningRPC.addInvoiceArgs, 1) require.Len(t, holdRPC.addHoldInvoiceArgs, 1) - require.Equal( - t, holdRPC.addHoldInvoiceArgs[0].in.RouteHints, - lightningRPC.addInvoiceArgs[0].in.RouteHints, + assertInvoiceRequestParity( + t, lightningRPC.addInvoiceArgs[0].in, + holdRPC.addHoldInvoiceArgs[0].in, ) } diff --git a/lightning_client.go b/lightning_client.go index f63b6e8..4405837 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1677,6 +1677,7 @@ func (s *lightningClient) AddInvoice(ctx context.Context, ValueMsat: int64(in.Value), DescriptionHash: in.DescriptionHash, Expiry: in.Expiry, + FallbackAddr: in.FallbackAddr, CltvExpiry: in.CltvExpiry, Private: in.Private, RouteHints: routeHints, diff --git a/lightning_client_test.go b/lightning_client_test.go index f5df568..9704b8b 100644 --- a/lightning_client_test.go +++ b/lightning_client_test.go @@ -57,6 +57,9 @@ func assertAddInvoiceArgs(t *testing.T, want, got []addInvoiceArg) { got[i].in.DescriptionHash, ) require.Equal(t, want[i].in.Expiry, got[i].in.Expiry) + require.Equal( + t, want[i].in.FallbackAddr, got[i].in.FallbackAddr, + ) require.Equal( t, want[i].in.CltvExpiry, got[i].in.CltvExpiry, ) @@ -134,6 +137,22 @@ func TestLightningClientAddInvoice(t *testing.T) { {in: privateInvoice}, } + fallbackAddrAddInvoiceData := *validAddInvoiceData + fallbackAddrAddInvoiceData.FallbackAddr = fallbackAddr + fallbackAddrInvoice := &lnrpc.Invoice{ + Memo: validAddInvoiceData.Memo, + RPreimage: validAddInvoiceData.Preimage[:], + RHash: validAddInvoiceData.Hash[:], + ValueMsat: int64(validAddInvoiceData.Value), + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + FallbackAddr: fallbackAddrAddInvoiceData.FallbackAddr, + CltvExpiry: validAddInvoiceData.CltvExpiry, + } + fallbackAddrAddInvoiceArgs := []addInvoiceArg{ + {in: fallbackAddrInvoice}, + } + routeHintAddInvoiceData := *validAddInvoiceData routeHintAddInvoiceData.RouteHints = validRouteHints routeHintInvoice := &lnrpc.Invoice{ @@ -197,6 +216,18 @@ func TestLightningClientAddInvoice(t *testing.T) { payRequest: validPayReq, }, }, + { + name: "invoice with fallback address", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: &fallbackAddrAddInvoiceData, + expect: expect{ + addInvoiceArgs: fallbackAddrAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, { name: "invoice with route hints", client: mockRPCClient{ From 9c1b4f432b7f3438e770d1015dec3f210d047b3e Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 17:22:17 -0500 Subject: [PATCH 22/26] lndclient: preserve addinvoice amp flag Set IsAmp on the standard AddInvoice RPC request so callers can request AMP invoices through lightningClient.AddInvoice. Extend the existing AddInvoice unit test to assert AMP invoice requests preserve the flag on the outgoing RPC payload. --- lightning_client.go | 1 + lightning_client_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/lightning_client.go b/lightning_client.go index 4405837..85f8c64 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1680,6 +1680,7 @@ func (s *lightningClient) AddInvoice(ctx context.Context, FallbackAddr: in.FallbackAddr, CltvExpiry: in.CltvExpiry, Private: in.Private, + IsAmp: in.Amp, RouteHints: routeHints, } diff --git a/lightning_client_test.go b/lightning_client_test.go index 9704b8b..0e63f49 100644 --- a/lightning_client_test.go +++ b/lightning_client_test.go @@ -64,6 +64,7 @@ func assertAddInvoiceArgs(t *testing.T, want, got []addInvoiceArg) { t, want[i].in.CltvExpiry, got[i].in.CltvExpiry, ) require.Equal(t, want[i].in.Private, got[i].in.Private) + require.Equal(t, want[i].in.IsAmp, got[i].in.IsAmp) if len(want[i].in.RouteHints) == 0 { require.Empty(t, got[i].in.RouteHints) @@ -153,6 +154,26 @@ func TestLightningClientAddInvoice(t *testing.T) { {in: fallbackAddrInvoice}, } + ampAddInvoiceData := &invoicesrpc.AddInvoiceData{ + Memo: validAddInvoiceData.Memo, + Value: validAddInvoiceData.Value, + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + CltvExpiry: validAddInvoiceData.CltvExpiry, + Amp: true, + } + ampInvoice := &lnrpc.Invoice{ + Memo: ampAddInvoiceData.Memo, + ValueMsat: int64(ampAddInvoiceData.Value), + DescriptionHash: ampAddInvoiceData.DescriptionHash, + Expiry: ampAddInvoiceData.Expiry, + CltvExpiry: ampAddInvoiceData.CltvExpiry, + IsAmp: true, + } + ampAddInvoiceArgs := []addInvoiceArg{ + {in: ampInvoice}, + } + routeHintAddInvoiceData := *validAddInvoiceData routeHintAddInvoiceData.RouteHints = validRouteHints routeHintInvoice := &lnrpc.Invoice{ @@ -228,6 +249,18 @@ func TestLightningClientAddInvoice(t *testing.T) { payRequest: validPayReq, }, }, + { + name: "amp invoice", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: ampAddInvoiceData, + expect: expect{ + addInvoiceArgs: ampAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, { name: "invoice with route hints", client: mockRPCClient{ From db55732973fff6064691c3d6630564e47d726501 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 17:49:13 -0500 Subject: [PATCH 23/26] lndclient: ignore hold invoice args in addinvoice Stop forwarding Hash on the standard AddInvoice path and warn when Hash or HodlInvoice are provided, since callers should use InvoicesClient.AddHoldInvoice for hold invoices. Extend the existing AddInvoice test to assert the standard invoice request omits hold-invoice-only arguments. --- lightning_client.go | 9 ++++++--- lightning_client_test.go | 32 +++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lightning_client.go b/lightning_client.go index 85f8c64..36b705f 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1665,6 +1665,12 @@ func (s *lightningClient) AddInvoice(ctx context.Context, rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() + if in.Hash != nil || in.HodlInvoice { + log.Warnf("lightningClient.AddInvoice ignores " + + "Hash/HodlInvoice; use InvoicesClient.AddHoldInvoice " + + "for hold invoices") + } + routeHints, err := marshallRouteHints(in.RouteHints) if err != nil { return lntypes.Hash{}, "", fmt.Errorf( @@ -1687,9 +1693,6 @@ func (s *lightningClient) AddInvoice(ctx context.Context, if in.Preimage != nil { rpcIn.RPreimage = in.Preimage[:] } - if in.Hash != nil { - rpcIn.RHash = in.Hash[:] - } rpcCtx = s.adminMac.WithMacaroonAuth(rpcCtx) resp, err := s.client.AddInvoice(rpcCtx, rpcIn) diff --git a/lightning_client_test.go b/lightning_client_test.go index 0e63f49..87b8a3d 100644 --- a/lightning_client_test.go +++ b/lightning_client_test.go @@ -89,7 +89,6 @@ func TestLightningClientAddInvoice(t *testing.T) { validAddInvoiceData := &invoicesrpc.AddInvoiceData{ Memo: "fake memo", Preimage: &validPreimage, - Hash: &validRHash, Value: lnwire.MilliSatoshi(500000), DescriptionHash: []byte("fake 32 byte hash"), Expiry: 123, @@ -99,7 +98,6 @@ func TestLightningClientAddInvoice(t *testing.T) { validInvoice := &lnrpc.Invoice{ Memo: validAddInvoiceData.Memo, RPreimage: validAddInvoiceData.Preimage[:], - RHash: validAddInvoiceData.Hash[:], ValueMsat: int64(validAddInvoiceData.Value), DescriptionHash: validAddInvoiceData.DescriptionHash, Expiry: validAddInvoiceData.Expiry, @@ -127,7 +125,6 @@ func TestLightningClientAddInvoice(t *testing.T) { privateInvoice := &lnrpc.Invoice{ Memo: validAddInvoiceData.Memo, RPreimage: validAddInvoiceData.Preimage[:], - RHash: validAddInvoiceData.Hash[:], ValueMsat: int64(validAddInvoiceData.Value), DescriptionHash: validAddInvoiceData.DescriptionHash, Expiry: validAddInvoiceData.Expiry, @@ -143,7 +140,6 @@ func TestLightningClientAddInvoice(t *testing.T) { fallbackAddrInvoice := &lnrpc.Invoice{ Memo: validAddInvoiceData.Memo, RPreimage: validAddInvoiceData.Preimage[:], - RHash: validAddInvoiceData.Hash[:], ValueMsat: int64(validAddInvoiceData.Value), DescriptionHash: validAddInvoiceData.DescriptionHash, Expiry: validAddInvoiceData.Expiry, @@ -174,12 +170,26 @@ func TestLightningClientAddInvoice(t *testing.T) { {in: ampInvoice}, } + hashAddInvoiceData := *validAddInvoiceData + hashAddInvoiceData.Preimage = nil + hashAddInvoiceData.Hash = &validRHash + hashAddInvoiceData.HodlInvoice = true + hashInvoice := &lnrpc.Invoice{ + Memo: validAddInvoiceData.Memo, + ValueMsat: int64(validAddInvoiceData.Value), + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + CltvExpiry: validAddInvoiceData.CltvExpiry, + } + hashAddInvoiceArgs := []addInvoiceArg{ + {in: hashInvoice}, + } + routeHintAddInvoiceData := *validAddInvoiceData routeHintAddInvoiceData.RouteHints = validRouteHints routeHintInvoice := &lnrpc.Invoice{ Memo: validAddInvoiceData.Memo, RPreimage: validAddInvoiceData.Preimage[:], - RHash: validAddInvoiceData.Hash[:], ValueMsat: int64(validAddInvoiceData.Value), DescriptionHash: validAddInvoiceData.DescriptionHash, Expiry: validAddInvoiceData.Expiry, @@ -261,6 +271,18 @@ func TestLightningClientAddInvoice(t *testing.T) { payRequest: validPayReq, }, }, + { + name: "invoice with hash uses standard invoice path", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: &hashAddInvoiceData, + expect: expect{ + addInvoiceArgs: hashAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, { name: "invoice with route hints", client: mockRPCClient{ From ec7246e9f21d83c4199f1c561b5e762666496491 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 17:50:04 -0500 Subject: [PATCH 24/26] lndclient: map addinvoice blinded path config Translate BlindedPathCfg into the standard AddInvoice RPC by setting IsBlinded and forwarding MinNumPathHops as the compatible NumHops override. Warn when non-overlapping blinded path settings are provided, and extend the AddInvoice unit test to assert blinded invoice requests preserve the translated RPC fields. --- lightning_client.go | 19 +++++++++++ lightning_client_test.go | 70 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/lightning_client.go b/lightning_client.go index 36b705f..03cab16 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1693,6 +1693,25 @@ func (s *lightningClient) AddInvoice(ctx context.Context, if in.Preimage != nil { rpcIn.RPreimage = in.Preimage[:] } + if in.BlindedPathCfg != nil { + rpcIn.IsBlinded = true + + if in.BlindedPathCfg.MinNumPathHops != 0 { + numHops := uint32(in.BlindedPathCfg.MinNumPathHops) + rpcIn.BlindedPathConfig = &lnrpc.BlindedPathConfig{ + NumHops: &numHops, + } + } + + if in.BlindedPathCfg.RoutePolicyIncrMultiplier != 0 || + in.BlindedPathCfg.RoutePolicyDecrMultiplier != 0 || + in.BlindedPathCfg.DefaultDummyHopPolicy != nil { + + log.Warnf("lightningClient.AddInvoice only forwards " + + "MinNumPathHops from BlindedPathCfg; other " + + "blinded path settings use lnd defaults") + } + } rpcCtx = s.adminMac.WithMacaroonAuth(rpcCtx) resp, err := s.client.AddInvoice(rpcCtx, rpcIn) diff --git a/lightning_client_test.go b/lightning_client_test.go index 87b8a3d..b2451ef 100644 --- a/lightning_client_test.go +++ b/lightning_client_test.go @@ -65,6 +65,16 @@ func assertAddInvoiceArgs(t *testing.T, want, got []addInvoiceArg) { ) require.Equal(t, want[i].in.Private, got[i].in.Private) require.Equal(t, want[i].in.IsAmp, got[i].in.IsAmp) + require.Equal(t, want[i].in.IsBlinded, got[i].in.IsBlinded) + + if want[i].in.BlindedPathConfig == nil { + require.Nil(t, got[i].in.BlindedPathConfig) + } else { + require.Equal( + t, want[i].in.BlindedPathConfig, + got[i].in.BlindedPathConfig, + ) + } if len(want[i].in.RouteHints) == 0 { require.Empty(t, got[i].in.RouteHints) @@ -185,6 +195,42 @@ func TestLightningClientAddInvoice(t *testing.T) { {in: hashInvoice}, } + blindedAddInvoiceData := *validAddInvoiceData + blindedAddInvoiceData.BlindedPathCfg = &invoicesrpc.BlindedPathConfig{ + MinNumPathHops: 5, + } + numHops := uint32(blindedAddInvoiceData.BlindedPathCfg.MinNumPathHops) + blindedInvoice := &lnrpc.Invoice{ + Memo: validAddInvoiceData.Memo, + RPreimage: validAddInvoiceData.Preimage[:], + ValueMsat: int64(validAddInvoiceData.Value), + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + CltvExpiry: validAddInvoiceData.CltvExpiry, + IsBlinded: true, + BlindedPathConfig: &lnrpc.BlindedPathConfig{ + NumHops: &numHops, + }, + } + blindedAddInvoiceArgs := []addInvoiceArg{ + {in: blindedInvoice}, + } + + blindedZeroHopAddInvoiceData := *validAddInvoiceData + blindedZeroHopAddInvoiceData.BlindedPathCfg = &invoicesrpc.BlindedPathConfig{} + blindedZeroHopInvoice := &lnrpc.Invoice{ + Memo: validAddInvoiceData.Memo, + RPreimage: validAddInvoiceData.Preimage[:], + ValueMsat: int64(validAddInvoiceData.Value), + DescriptionHash: validAddInvoiceData.DescriptionHash, + Expiry: validAddInvoiceData.Expiry, + CltvExpiry: validAddInvoiceData.CltvExpiry, + IsBlinded: true, + } + blindedZeroHopAddInvoiceArgs := []addInvoiceArg{ + {in: blindedZeroHopInvoice}, + } + routeHintAddInvoiceData := *validAddInvoiceData routeHintAddInvoiceData.RouteHints = validRouteHints routeHintInvoice := &lnrpc.Invoice{ @@ -283,6 +329,30 @@ func TestLightningClientAddInvoice(t *testing.T) { payRequest: validPayReq, }, }, + { + name: "blinded invoice", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: &blindedAddInvoiceData, + expect: expect{ + addInvoiceArgs: blindedAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, + { + name: "blinded invoice with zero min path hops", + client: mockRPCClient{ + addInvoice: validAddInvoice, + }, + invoice: &blindedZeroHopAddInvoiceData, + expect: expect{ + addInvoiceArgs: blindedZeroHopAddInvoiceArgs, + hash: validRHash, + payRequest: validPayReq, + }, + }, { name: "invoice with route hints", client: mockRPCClient{ From e3748c1a23dab82e8df2be5eae1da50731456740 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Apr 2026 18:33:43 -0500 Subject: [PATCH 25/26] lndclient: warn on unsupported hold invoice fields Warn when AddHoldInvoice is called with Amp or BlindedPathCfg, since the hold-invoice RPC cannot represent those inputs. Add coverage to document that the wrapper still forwards the supported request fields unchanged when those unsupported inputs are present. --- invoices_client.go | 6 +++++ invoices_client_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/invoices_client.go b/invoices_client.go index 5946419..a1a2501 100644 --- a/invoices_client.go +++ b/invoices_client.go @@ -239,6 +239,12 @@ func (s *invoicesClient) AddHoldInvoice(ctx context.Context, rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() + if in.Amp || in.BlindedPathCfg != nil { + log.Warnf("invoicesClient.AddHoldInvoice ignores Amp/" + + "BlindedPathCfg; hold invoice RPC does not support " + + "those fields") + } + routeHints, err := marshallRouteHints(in.RouteHints) if err != nil { return "", fmt.Errorf("failed to marshal route hints: %v", err) diff --git a/invoices_client_test.go b/invoices_client_test.go index 93706ed..ee5bef6 100644 --- a/invoices_client_test.go +++ b/invoices_client_test.go @@ -183,3 +183,61 @@ func TestInvoiceClientAddInvoiceParity(t *testing.T) { holdRPC.addHoldInvoiceArgs[0].in, ) } + +// TestInvoicesClientAddHoldInvoiceIgnoresUnsupportedFields ensures the +// AddHoldInvoice wrapper still forwards the supported request fields when AMP +// or blinded-path-only inputs are provided. +func TestInvoicesClientAddHoldInvoiceIgnoresUnsupportedFields(t *testing.T) { + var validRHash lntypes.Hash + copy(validRHash[:], "valid hash") + + invoice := &invoicesrpc.AddInvoiceData{ + Memo: "fake memo", + Hash: &validRHash, + Value: lnwire.MilliSatoshi(500000), + DescriptionHash: []byte("fake 32 byte hash"), + Expiry: 123, + FallbackAddr: fallbackAddr, + CltvExpiry: 456, + Private: true, + Amp: true, + BlindedPathCfg: &invoicesrpc.BlindedPathConfig{ + MinNumPathHops: 5, + }, + RouteHints: testInvoiceRouteHints(), + } + + rpcRouteHints := testRPCRouteHints(t) + expectedRequest := &invoicesrpc.AddHoldInvoiceRequest{ + Memo: invoice.Memo, + Hash: invoice.Hash[:], + ValueMsat: int64(invoice.Value), + DescriptionHash: invoice.DescriptionHash, + Expiry: invoice.Expiry, + FallbackAddr: invoice.FallbackAddr, + CltvExpiry: invoice.CltvExpiry, + Private: invoice.Private, + RouteHints: rpcRouteHints, + } + + holdRPC := &mockInvoicesRPCClient{ + addHoldInvoice: func(_ *invoicesrpc.AddHoldInvoiceRequest, + _ ...grpc.CallOption) (*invoicesrpc.AddHoldInvoiceResp, + error) { + + return &invoicesrpc.AddHoldInvoiceResp{ + PaymentRequest: "probe invoice", + }, nil + }, + } + + invoices := &invoicesClient{ + client: holdRPC, + } + + _, err := invoices.AddHoldInvoice(t.Context(), invoice) + require.NoError(t, err) + + require.Len(t, holdRPC.addHoldInvoiceArgs, 1) + require.Equal(t, expectedRequest, holdRPC.addHoldInvoiceArgs[0].in) +} From 762bc6ebb0aaa923d5947ce14b347be43c36a439 Mon Sep 17 00:00:00 2001 From: lucasnaman Date: Tue, 28 Apr 2026 10:04:14 +0200 Subject: [PATCH 26/26] [BAC-325] reformat IBEX-added long lines for lll linter The merge bumped tools/Dockerfile golang from 1.24.6 to 1.26.0, which rebuilt golangci-lint and now strictly enforces .golangci.yml's 80-col lll limit on lines that have been over since the original IBEX additions in 2024 (commit 1993ca770, "ibex req"). All 9 violations were in IBEX-extension methods/comments (SendMany / EstimateFees / NewAddress interface + impl, ListChannels extension, TimePref doc). Wrapped them under 80 cols. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) --- lightning_client.go | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/lightning_client.go b/lightning_client.go index 228123f..0dff555 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -97,13 +97,16 @@ type LightningClient interface { amt btcutil.Amount, confTarget int32) (btcutil.Amount, error) // NewAddress generates a new address for the lnd client. - NewAddress(ctx context.Context, addressType lnrpc.AddressType) (string, error) + NewAddress(ctx context.Context, addressType lnrpc.AddressType) ( + string, error) - // SendMany handles a request for a transaction that creates multiple specified outputs in parallel. - SendMany(ctx context.Context, txBatch map[string]int64, satPerVbyte uint64) (string, error) + // SendMany handles a request for a transaction that creates multiple + // specified outputs in parallel. + SendMany(ctx context.Context, txBatch map[string]int64, + satPerVbyte uint64) (string, error) - // EstimateFees estimates the total fees for a batch of transactions that pay the given - // amounts to the passed addresses. + // EstimateFees estimates the total fees for a batch of transactions + // that pay the given amounts to the passed addresses. EstimateFees(ctx context.Context, txBatch map[string]int64, targetConf int32, ) (*lnrpc.EstimateFeeResponse, error) @@ -132,7 +135,8 @@ type LightningClient interface { opts ...ListTransactionsOption) ([]Transaction, error) // ListChannels retrieves all channels of the backing lnd node. - ListChannels(ctx context.Context, input *lnrpc.ListChannelsRequest) ([]ChannelInfo, error) + ListChannels(ctx context.Context, + input *lnrpc.ListChannelsRequest) ([]ChannelInfo, error) // PendingChannels returns a list of lnd's pending channels. PendingChannels(ctx context.Context) (*PendingChannels, error) @@ -1290,8 +1294,9 @@ type QueryRoutesRequest struct { // FeeLimitMsat is the fee limit to use in millisatoshis. FeeLimitMsat lnwire.MilliSatoshi - // The time preference for this payment. Set to -1 to optimize for fees only, to 1 to optimize for reliability only - // or a value in between for a mix. + // The time preference for this payment. Set to -1 to optimize for + // fees only, to 1 to optimize for reliability only or a value in + // between for a mix. TimePref float64 } @@ -4614,8 +4619,8 @@ func (s *lightningClient) SubscribeTransactions( } // NewAddress generates a new address for the lnd client. -func (s *lightningClient) NewAddress(ctx context.Context, addressType lnrpc.AddressType) ( - string, error) { +func (s *lightningClient) NewAddress(ctx context.Context, + addressType lnrpc.AddressType) (string, error) { rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() @@ -4635,9 +4640,10 @@ func (s *lightningClient) NewAddress(ctx context.Context, addressType lnrpc.Addr return resp.Address, nil } -// SendMany handles a request for a transaction that creates multiple specified outputs in parallel. -func (s *lightningClient) SendMany(ctx context.Context, txBatch map[string]int64, satPerVbyte uint64) ( - string, error) { +// SendMany handles a request for a transaction that creates multiple +// specified outputs in parallel. +func (s *lightningClient) SendMany(ctx context.Context, + txBatch map[string]int64, satPerVbyte uint64) (string, error) { rpcCtx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() @@ -4659,8 +4665,8 @@ func (s *lightningClient) SendMany(ctx context.Context, txBatch map[string]int64 return resp.Txid, nil } -// EstimateFees estimates the total fees for a batch of transactions that pay the given -// amounts to the passed addresses. +// EstimateFees estimates the total fees for a batch of transactions +// that pay the given amounts to the passed addresses. func (s *lightningClient) EstimateFees( ctx context.Context, txBatch map[string]int64,