Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 52 additions & 47 deletions cmd/litcli/ln.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"time"

"github.com/lightninglabs/taproot-assets/rfq"
"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"
Expand Down Expand Up @@ -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",
Comment on lines +213 to +214
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm not seeing that the rest of the changes in this PR actually changes this? If this is due to some other change that's already been merged previously, consider splitting this change into a separate commit and explain why this is now added.

}

allowOverpayFlag = cli.BoolFlag{
Expand All @@ -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
}
}
Comment on lines +280 to +304
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking that this is correct behaviour:
There is no return statement for the *tchrpc.SendPaymentResponse_AcceptedSellOrder & *tchrpc.SendPaymentResponse_AcceptedSellOrders cases here unless there's no error, meaning that they won't break the for loop without any errors being thrown somewhere in the next loop iteration. Is that correct behaviour?


case *tchrpc.SendPaymentResponse_PaymentResult:
return r.PaymentResult, nil

default:
return nil, fmt.Errorf("unexpected response type: %T",
r)
}
}
}

Expand Down
Loading