From f2b930da702f69a4bcfe373dd8618b105b03d5b2 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Thu, 31 Jul 2025 17:00:04 +0200 Subject: [PATCH] litcli: update payinvoice for multirfq --- 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) + } } }