Skip to content

Commit a641b68

Browse files
committed
multi: bump deps, use new tapchannelrpc.SendPayment RPC
With this commit we bump to the latest version of taproot assets and lnd (staging branch), so we can use the new SendPayment RPC that handles the RFQ part inline.
1 parent 3ee5ec2 commit a641b68

File tree

6 files changed

+231
-258
lines changed

6 files changed

+231
-258
lines changed

cmd/litcli/ln.go

Lines changed: 134 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/lightningnetwork/lnd/lnwire"
2424
"github.com/lightningnetwork/lnd/record"
2525
"github.com/urfave/cli"
26+
"google.golang.org/grpc"
2627
)
2728

2829
const (
@@ -223,8 +224,66 @@ var (
223224
Usage: "the asset ID of the asset to use when sending " +
224225
"payments with assets",
225226
}
227+
228+
assetAmountFlag = cli.Uint64Flag{
229+
Name: "asset_amount",
230+
Usage: "the amount of the asset to send in the asset keysend " +
231+
"payment",
232+
}
233+
234+
rfqPeerPubKeyFlag = cli.StringFlag{
235+
Name: "rfq_peer_pubkey",
236+
Usage: "(optional) the public key of the peer to ask for a " +
237+
"quote when converting from assets to sats; must be " +
238+
"set if there are multiple channels with the same " +
239+
"asset ID present",
240+
}
226241
)
227242

243+
// resultStreamWrapper is a wrapper around the SendPaymentClient stream that
244+
// implements the generic PaymentResultStream interface.
245+
type resultStreamWrapper struct {
246+
amountMsat int64
247+
stream tchrpc.TaprootAssetChannels_SendPaymentClient
248+
}
249+
250+
// Recv receives the next payment result from the stream.
251+
//
252+
// NOTE: This method is part of the PaymentResultStream interface.
253+
func (w *resultStreamWrapper) Recv() (*lnrpc.Payment, error) {
254+
resp, err := w.stream.Recv()
255+
if err != nil {
256+
return nil, err
257+
}
258+
259+
res := resp.Result
260+
switch r := res.(type) {
261+
// The very first response might be an accepted sell order, which we
262+
// just print out.
263+
case *tchrpc.SendPaymentResponse_AcceptedSellOrder:
264+
quote := r.AcceptedSellOrder
265+
msatPerUnit := quote.BidPrice
266+
numUnits := uint64(w.amountMsat) / msatPerUnit
267+
268+
fmt.Printf("Got quote for %v asset units at %v msat/unit from "+
269+
"peer %s with SCID %d\n", numUnits, msatPerUnit,
270+
quote.Peer, quote.Scid)
271+
272+
resp, err = w.stream.Recv()
273+
if err != nil {
274+
return nil, err
275+
}
276+
277+
return resp.GetPaymentResult(), nil
278+
279+
case *tchrpc.SendPaymentResponse_PaymentResult:
280+
return r.PaymentResult, nil
281+
282+
default:
283+
return nil, fmt.Errorf("unexpected response type: %T", r)
284+
}
285+
}
286+
228287
var sendPaymentCommand = cli.Command{
229288
Name: "sendpayment",
230289
Category: commands.SendPaymentCommand.Category,
@@ -236,14 +295,16 @@ var sendPaymentCommand = cli.Command{
236295
237296
Note that this will only work in concert with the --keysend argument.
238297
`,
239-
ArgsUsage: commands.SendPaymentCommand.ArgsUsage + " --asset_id=X",
240-
Flags: append(commands.SendPaymentCommand.Flags, assetIDFlag),
241-
Action: sendPayment,
298+
ArgsUsage: commands.SendPaymentCommand.ArgsUsage + " --asset_id=X " +
299+
"--asset_amount=Y [--rfq_peer_pubkey=Z]",
300+
Flags: append(
301+
commands.SendPaymentCommand.Flags, assetIDFlag, assetAmountFlag,
302+
rfqPeerPubKeyFlag,
303+
),
304+
Action: sendPayment,
242305
}
243306

244307
func sendPayment(ctx *cli.Context) error {
245-
ctxb := context.Background()
246-
247308
// Show command help if no arguments provided
248309
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
249310
_ = cli.ShowCommandHelp(ctx, "sendpayment")
@@ -254,67 +315,32 @@ func sendPayment(ctx *cli.Context) error {
254315
if err != nil {
255316
return fmt.Errorf("unable to make rpc con: %w", err)
256317
}
257-
258318
defer cleanup()
259319

260-
lndClient := lnrpc.NewLightningClient(lndConn)
320+
tapdConn, cleanup, err := connectTapdClient(ctx)
321+
if err != nil {
322+
return fmt.Errorf("error creating tapd connection: %w", err)
323+
}
324+
defer cleanup()
261325

262326
switch {
263327
case !ctx.IsSet(assetIDFlag.Name):
264328
return fmt.Errorf("the --asset_id flag must be set")
265329
case !ctx.IsSet("keysend"):
266330
return fmt.Errorf("the --keysend flag must be set")
267-
case !ctx.IsSet("amt"):
268-
return fmt.Errorf("--amt must be set")
331+
case !ctx.IsSet(assetAmountFlag.Name):
332+
return fmt.Errorf("--asset_amount must be set")
269333
}
270334

271335
assetIDStr := ctx.String(assetIDFlag.Name)
272-
_, err = hex.DecodeString(assetIDStr)
336+
assetIDBytes, err := hex.DecodeString(assetIDStr)
273337
if err != nil {
274338
return fmt.Errorf("unable to decode assetID: %v", err)
275339
}
276340

277-
// First, based on the asset ID and amount, we'll make sure that this
278-
// channel even has enough funds to send.
279-
assetBalances, err := computeAssetBalances(lndClient)
280-
if err != nil {
281-
return fmt.Errorf("unable to compute asset balances: %w", err)
282-
}
283-
284-
balance, ok := assetBalances.Assets[assetIDStr]
285-
if !ok {
286-
return fmt.Errorf("unable to send asset_id=%v, not in "+
287-
"channel", assetIDStr)
288-
}
289-
290-
amtToSend := ctx.Uint64("amt")
291-
if amtToSend > balance.LocalBalance {
292-
return fmt.Errorf("insufficient balance, want to send %v, "+
293-
"only have %v", amtToSend, balance.LocalBalance)
294-
}
295-
296-
tapdConn, cleanup, err := connectTapdClient(ctx)
297-
if err != nil {
298-
return fmt.Errorf("error creating tapd connection: %w", err)
299-
}
300-
defer cleanup()
301-
302-
tchrpcClient := tchrpc.NewTaprootAssetChannelsClient(tapdConn)
303-
304-
encodeReq := &tchrpc.EncodeCustomRecordsRequest_RouterSendPayment{
305-
RouterSendPayment: &tchrpc.RouterSendPaymentData{
306-
AssetAmounts: map[string]uint64{
307-
assetIDStr: amtToSend,
308-
},
309-
},
310-
}
311-
encodeResp, err := tchrpcClient.EncodeCustomRecords(
312-
ctxb, &tchrpc.EncodeCustomRecordsRequest{
313-
Input: encodeReq,
314-
},
315-
)
316-
if err != nil {
317-
return fmt.Errorf("error encoding custom records: %w", err)
341+
assetAmountToSend := ctx.Uint64(assetAmountFlag.Name)
342+
if assetAmountToSend == 0 {
343+
return fmt.Errorf("must specify asset amount to send")
318344
}
319345

320346
// With the asset specific work out of the way, we'll parse the rest of
@@ -339,15 +365,20 @@ func sendPayment(ctx *cli.Context) error {
339365
"is instead: %v", len(destNode))
340366
}
341367

368+
rfqPeerKey, err := hex.DecodeString(ctx.String(rfqPeerPubKeyFlag.Name))
369+
if err != nil {
370+
return fmt.Errorf("unable to decode RFQ peer public key: "+
371+
"%w", err)
372+
}
373+
342374
// We use a constant amount of 500 to carry the asset HTLCs. In the
343375
// future, we can use the double HTLC trick here, though it consumes
344376
// more commitment space.
345377
const htlcCarrierAmt = 500
346378
req := &routerrpc.SendPaymentRequest{
347-
Dest: destNode,
348-
Amt: htlcCarrierAmt,
349-
DestCustomRecords: make(map[uint64][]byte),
350-
FirstHopCustomRecords: encodeResp.CustomRecords,
379+
Dest: destNode,
380+
Amt: htlcCarrierAmt,
381+
DestCustomRecords: make(map[uint64][]byte),
351382
}
352383

353384
if ctx.IsSet("payment_hash") {
@@ -370,7 +401,33 @@ func sendPayment(ctx *cli.Context) error {
370401

371402
req.PaymentHash = rHash
372403

373-
return commands.SendPaymentRequest(ctx, req)
404+
return commands.SendPaymentRequest(
405+
ctx, req, lndConn, tapdConn, func(ctx context.Context,
406+
payConn grpc.ClientConnInterface,
407+
req *routerrpc.SendPaymentRequest) (
408+
commands.PaymentResultStream, error) {
409+
410+
tchrpcClient := tchrpc.NewTaprootAssetChannelsClient(
411+
payConn,
412+
)
413+
414+
stream, err := tchrpcClient.SendPayment(
415+
ctx, &tchrpc.SendPaymentRequest{
416+
AssetId: assetIDBytes,
417+
AssetAmount: assetAmountToSend,
418+
PeerPubkey: rfqPeerKey,
419+
PaymentRequest: req,
420+
},
421+
)
422+
if err != nil {
423+
return nil, err
424+
}
425+
426+
return &resultStreamWrapper{
427+
stream: stream,
428+
}, nil
429+
},
430+
)
374431
}
375432

376433
var payInvoiceCommand = cli.Command{
@@ -434,24 +491,6 @@ func payInvoice(ctx *cli.Context) error {
434491
return fmt.Errorf("unable to decode assetID: %v", err)
435492
}
436493

437-
// First, based on the asset ID and amount, we'll make sure that this
438-
// channel even has enough funds to send.
439-
assetBalances, err := computeAssetBalances(lndClient)
440-
if err != nil {
441-
return fmt.Errorf("unable to compute asset balances: %w", err)
442-
}
443-
444-
balance, ok := assetBalances.Assets[assetIDStr]
445-
if !ok {
446-
return fmt.Errorf("unable to send asset_id=%v, not in "+
447-
"channel", assetIDStr)
448-
}
449-
450-
if balance.LocalBalance == 0 {
451-
return fmt.Errorf("no asset balance available for asset_id=%v",
452-
assetIDStr)
453-
}
454-
455494
var assetID asset.ID
456495
copy(assetID[:], assetIDBytes)
457496

@@ -462,88 +501,35 @@ func payInvoice(ctx *cli.Context) error {
462501

463502
defer cleanup()
464503

465-
peerPubKey, err := hex.DecodeString(balance.Channel.RemotePubkey)
466-
if err != nil {
467-
return fmt.Errorf("unable to decode peer pubkey: %w", err)
504+
req := &routerrpc.SendPaymentRequest{
505+
PaymentRequest: commands.StripPrefix(payReq),
468506
}
469507

470-
rfqClient := rfqrpc.NewRfqClient(tapdConn)
508+
return commands.SendPaymentRequest(
509+
ctx, req, lndConn, tapdConn, func(ctx context.Context,
510+
payConn grpc.ClientConnInterface,
511+
req *routerrpc.SendPaymentRequest) (
512+
commands.PaymentResultStream, error) {
471513

472-
timeoutSeconds := uint32(60)
473-
fmt.Printf("Asking peer %x for quote to sell assets to pay for "+
474-
"invoice over %d msats; waiting up to %ds\n", peerPubKey,
475-
decodeResp.NumMsat, timeoutSeconds)
514+
tchrpcClient := tchrpc.NewTaprootAssetChannelsClient(
515+
payConn,
516+
)
476517

477-
resp, err := rfqClient.AddAssetSellOrder(
478-
ctxb, &rfqrpc.AddAssetSellOrderRequest{
479-
AssetSpecifier: &rfqrpc.AssetSpecifier{
480-
Id: &rfqrpc.AssetSpecifier_AssetIdStr{
481-
AssetIdStr: assetIDStr,
518+
stream, err := tchrpcClient.SendPayment(
519+
ctx, &tchrpc.SendPaymentRequest{
520+
AssetId: assetIDBytes,
482521
},
483-
},
484-
// TODO(guggero): This should actually be the max BTC
485-
// amount (invoice amount plus fee limit) in
486-
// milli-satoshi, not the asset amount. Need to change
487-
// the whole RFQ API to do that though.
488-
MaxAssetAmount: balance.LocalBalance,
489-
MinAsk: uint64(decodeResp.NumMsat),
490-
Expiry: uint64(decodeResp.Expiry),
491-
PeerPubKey: peerPubKey,
492-
TimeoutSeconds: timeoutSeconds,
493-
},
494-
)
495-
if err != nil {
496-
return fmt.Errorf("error adding sell order: %w", err)
497-
}
498-
499-
var acceptedQuote *rfqrpc.PeerAcceptedSellQuote
500-
switch r := resp.Response.(type) {
501-
case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote:
502-
acceptedQuote = r.AcceptedQuote
503-
504-
case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote:
505-
return fmt.Errorf("peer %v sent back an invalid quote, "+
506-
"status: %v", r.InvalidQuote.Peer,
507-
r.InvalidQuote.Status.String())
508-
509-
case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote:
510-
return fmt.Errorf("peer %v rejected the quote, code: %v, "+
511-
"error message: %v", r.RejectedQuote.Peer,
512-
r.RejectedQuote.ErrorCode, r.RejectedQuote.ErrorMessage)
513-
514-
default:
515-
return fmt.Errorf("unexpected response type: %T", r)
516-
}
517-
518-
msatPerUnit := acceptedQuote.BidPrice
519-
numUnits := uint64(decodeResp.NumMsat) / msatPerUnit
520-
521-
fmt.Printf("Got quote for %v asset units at %v msat/unit from peer "+
522-
"%x with SCID %d\n", numUnits, msatPerUnit, peerPubKey,
523-
acceptedQuote.Scid)
524-
525-
tchrpcClient := tchrpc.NewTaprootAssetChannelsClient(tapdConn)
522+
)
523+
if err != nil {
524+
return nil, err
525+
}
526526

527-
encodeReq := &tchrpc.EncodeCustomRecordsRequest_RouterSendPayment{
528-
RouterSendPayment: &tchrpc.RouterSendPaymentData{
529-
RfqId: acceptedQuote.Id,
530-
},
531-
}
532-
encodeResp, err := tchrpcClient.EncodeCustomRecords(
533-
ctxb, &tchrpc.EncodeCustomRecordsRequest{
534-
Input: encodeReq,
527+
return &resultStreamWrapper{
528+
amountMsat: decodeResp.NumMsat,
529+
stream: stream,
530+
}, nil
535531
},
536532
)
537-
if err != nil {
538-
return fmt.Errorf("error encoding custom records: %w", err)
539-
}
540-
541-
req := &routerrpc.SendPaymentRequest{
542-
PaymentRequest: commands.StripPrefix(payReq),
543-
FirstHopCustomRecords: encodeResp.CustomRecords,
544-
}
545-
546-
return commands.SendPaymentRequest(ctx, req)
547533
}
548534

549535
var addInvoiceCommand = cli.Command{

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ require (
1515
github.com/lightninglabs/faraday v0.2.13-alpha
1616
github.com/lightninglabs/lightning-node-connect v0.3.1-alpha
1717
github.com/lightninglabs/lightning-terminal/autopilotserverrpc v0.0.1
18-
github.com/lightninglabs/lndclient v1.0.1-0.20240724144614-a676c76e9eaa
18+
github.com/lightninglabs/lndclient v1.0.1-0.20240725080034-64a756aa4c36
1919
github.com/lightninglabs/loop v0.28.6-beta.0.20240729115851-63e976ab27a4
2020
github.com/lightninglabs/loop/swapserverrpc v1.0.8
2121
github.com/lightninglabs/pool v0.6.5-beta.0.20240604070222-e121aadb3289
2222
github.com/lightninglabs/pool/auctioneerrpc v1.1.2
23-
github.com/lightninglabs/taproot-assets v0.4.1
24-
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4
23+
github.com/lightninglabs/taproot-assets v0.4.2-0.20240807122703-23c09ff3b017
24+
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240730143253-1b353b0bfd58
2525
github.com/lightningnetwork/lnd/cert v1.2.2
2626
github.com/lightningnetwork/lnd/fn v1.1.0
2727
github.com/lightningnetwork/lnd/kvdb v1.4.8

0 commit comments

Comments
 (0)