From 25187ef35c86b2a0ca033297d16e3ae1455891af Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Thu, 31 Jul 2025 17:00:04 +0200 Subject: [PATCH 1/2] litcli: update payinvoice for multirfq With the introduction of multi-rfq send in taproot assets (see related PR https://github.com/lightninglabs/taproot-assets/pull/1613) we extended the behavior of the SendPayment RPC slightly. Now we may not specify an RFQ peer in order for a list of peers to be created & used automatically. When that happens, we will return multiple quotes over the RPC. The content of this commit changes our client wrapper around the handling of the payment result. Previously we'd expect for exactly a single quote to be returned over the stream (for the single defined peer). Now we read all of the quotes that are returned in the new quotes array field of the RPC. To maintain backwards compatibility we also kept the previous single-quote RPC field, which we make sure to only read once now that we read both fields (avoid a duplicate quote from appearing in the logs). --- cmd/litcli/ln.go | 99 +++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/cmd/litcli/ln.go b/cmd/litcli/ln.go index d4d99c109..b7c4d8e9b 100644 --- a/cmd/litcli/ln.go +++ b/cmd/litcli/ln.go @@ -5,7 +5,6 @@ import ( "context" "crypto/rand" "encoding/hex" - "errors" "fmt" "time" @@ -13,6 +12,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rpcutils" "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightningnetwork/lnd/cmd/commands" "github.com/lightningnetwork/lnd/lnrpc" @@ -210,9 +210,8 @@ var ( rfqPeerPubKeyFlag = cli.StringFlag{ Name: "rfq_peer_pubkey", Usage: "(optional) the public key of the peer to ask for a " + - "quote when converting from assets to sats; must be " + - "set if there are multiple channels with the same " + - "asset ID present", + "quote when converting from assets to sats; if left " + + "unset then rfq peers will be picked automatically", } allowOverpayFlag = cli.BoolFlag{ @@ -237,74 +236,80 @@ type resultStreamWrapper struct { // // NOTE: This method is part of the PaymentResultStream interface. func (w *resultStreamWrapper) Recv() (*lnrpc.Payment, error) { - resp, err := w.stream.Recv() - if err != nil { - return nil, err - } - - res := resp.Result - switch r := res.(type) { - // The very first response might be an accepted sell order, which we - // just print out. - case *tchrpc.SendPaymentResponse_AcceptedSellOrder: - quote := r.AcceptedSellOrder + // printQuote unmarshals and prints an accepted quote. + printQuote := func(quote *rfqrpc.PeerAcceptedSellQuote) error { rpcRate := quote.BidAssetRate rate, err := rpcutils.UnmarshalRfqFixedPoint(rpcRate) if err != nil { - return nil, fmt.Errorf("unable to unmarshal fixed "+ - "point: %w", err) + return fmt.Errorf("unable to unmarshal fixed point: %w", + err) } amountMsat := lnwire.MilliSatoshi(w.amountMsat) milliSatsFP := rfqmath.MilliSatoshiToUnits(amountMsat, *rate) numUnits := milliSatsFP.ScaleTo(0).ToUint64() - // If the calculated number of units is 0 then the asset rate - // was not sufficient to represent the value of this payment. + // The purpose of this function is just to print, so let's avoid + // dividing by zero or reporting an invalid msat/unit rate. if numUnits == 0 { - // We will calculate the minimum amount that can be - // effectively sent with this asset by calculating the - // value of a single asset unit, based on the provided - // asset rate. - - // We create the single unit. - unit := rfqmath.FixedPointFromUint64[rfqmath.BigInt]( - 1, 0, - ) - - // We derive the minimum amount. - minAmt := rfqmath.UnitsToMilliSatoshi(unit, *rate) - - // We return the error to the user. - return nil, fmt.Errorf("smallest payment with asset "+ - "rate %v is %v, cannot send %v", - rate.ToUint64(), minAmt, amountMsat) + return nil } msatPerUnit := uint64(w.amountMsat) / numUnits fmt.Printf("Got quote for %v asset units at %v msat/unit from "+ - "peer %s with SCID %d\n", numUnits, msatPerUnit, + " peer %s with SCID %d\n", numUnits, msatPerUnit, quote.Peer, quote.Scid) - resp, err = w.stream.Recv() + return nil + } + + // A boolean to indicate whether the first quote was printed via the + // legacy single-rfq response field. + legacyFirstPrint := false + + for { + resp, err := w.stream.Recv() if err != nil { return nil, err } - if resp == nil || resp.Result == nil || - resp.GetPaymentResult() == nil { + res := resp.Result - return nil, errors.New("unexpected nil result") - } + switch r := res.(type) { + case *tchrpc.SendPaymentResponse_AcceptedSellOrder: + err := printQuote(r.AcceptedSellOrder) + if err != nil { + return nil, err + } - return resp.GetPaymentResult(), nil + legacyFirstPrint = true - case *tchrpc.SendPaymentResponse_PaymentResult: - return r.PaymentResult, nil + case *tchrpc.SendPaymentResponse_AcceptedSellOrders: + quotes := r.AcceptedSellOrders.AcceptedSellOrders - default: - return nil, fmt.Errorf("unexpected response type: %T", r) + for _, quote := range quotes { + // If the first item was returned via the legacy + // field then skip printing it again here. This + // skip only applies to the first element. + if legacyFirstPrint { + legacyFirstPrint = false + continue + } + + err := printQuote(quote) + if err != nil { + return nil, err + } + } + + case *tchrpc.SendPaymentResponse_PaymentResult: + return r.PaymentResult, nil + + default: + return nil, fmt.Errorf("unexpected response type: %T", + r) + } } } From d0094187faa5a8a0d458ef4dfd9f30b13c6c4cdb Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 15 Oct 2025 14:14:40 +0200 Subject: [PATCH 2/2] docs: add release note --- docs/release-notes/release-notes-0.16.0.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index 4c3785ce0..ed0c5497c 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -20,6 +20,10 @@ ### Technical and Architectural Updates ## RPC Updates +- [Updated litcli](https://github.com/lightninglabs/lightning-terminal/pull/1125) +to support sending an asset payment without specifying a single peer. This will +use the new multi-rfq feature of taproot-assets which will automatically pick +the rfq peers for you. ## Integrated Binary Updates