Skip to content

Commit 93a4b4c

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 1aed171 commit 93a4b4c

File tree

6 files changed

+220
-250
lines changed

6 files changed

+220
-250
lines changed

cmd/litcli/ln.go

Lines changed: 129 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,61 @@ 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+
type resultStreamWrapper struct {
244+
amountMsat int64
245+
stream tchrpc.TaprootAssetChannels_SendPaymentClient
246+
}
247+
248+
func (w *resultStreamWrapper) Recv() (*lnrpc.Payment, error) {
249+
resp, err := w.stream.Recv()
250+
if err != nil {
251+
return nil, err
252+
}
253+
254+
res := resp.Result
255+
switch r := res.(type) {
256+
// The very first response might be an accepted sell order, which we
257+
// just print out.
258+
case *tchrpc.SendPaymentResponse_AcceptedSellOrder:
259+
quote := r.AcceptedSellOrder
260+
msatPerUnit := quote.BidPrice
261+
numUnits := uint64(w.amountMsat) / msatPerUnit
262+
263+
fmt.Printf("Got quote for %v asset units at %v msat/unit from "+
264+
"peer %s with SCID %d\n", numUnits, msatPerUnit,
265+
quote.Peer, quote.Scid)
266+
267+
resp, err = w.stream.Recv()
268+
if err != nil {
269+
return nil, err
270+
}
271+
272+
return resp.GetPaymentResult(), nil
273+
274+
case *tchrpc.SendPaymentResponse_PaymentResult:
275+
return r.PaymentResult, nil
276+
277+
default:
278+
return nil, fmt.Errorf("unexpected response type: %T", r)
279+
}
280+
}
281+
228282
var sendPaymentCommand = cli.Command{
229283
Name: "sendpayment",
230284
Category: commands.SendPaymentCommand.Category,
@@ -236,14 +290,16 @@ var sendPaymentCommand = cli.Command{
236290
237291
Note that this will only work in concert with the --keysend argument.
238292
`,
239-
ArgsUsage: commands.SendPaymentCommand.ArgsUsage + " --asset_id=X",
240-
Flags: append(commands.SendPaymentCommand.Flags, assetIDFlag),
241-
Action: sendPayment,
293+
ArgsUsage: commands.SendPaymentCommand.ArgsUsage + " --asset_id=X " +
294+
"--asset_amount=Y [--rfq_peer_pubkey=Z]",
295+
Flags: append(
296+
commands.SendPaymentCommand.Flags, assetIDFlag, assetAmountFlag,
297+
rfqPeerPubKeyFlag,
298+
),
299+
Action: sendPayment,
242300
}
243301

244302
func sendPayment(ctx *cli.Context) error {
245-
ctxb := context.Background()
246-
247303
// Show command help if no arguments provided
248304
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
249305
_ = cli.ShowCommandHelp(ctx, "sendpayment")
@@ -254,67 +310,32 @@ func sendPayment(ctx *cli.Context) error {
254310
if err != nil {
255311
return fmt.Errorf("unable to make rpc con: %w", err)
256312
}
257-
258313
defer cleanup()
259314

260-
lndClient := lnrpc.NewLightningClient(lndConn)
315+
tapdConn, cleanup, err := connectTapdClient(ctx)
316+
if err != nil {
317+
return fmt.Errorf("error creating tapd connection: %w", err)
318+
}
319+
defer cleanup()
261320

262321
switch {
263322
case !ctx.IsSet(assetIDFlag.Name):
264323
return fmt.Errorf("the --asset_id flag must be set")
265324
case !ctx.IsSet("keysend"):
266325
return fmt.Errorf("the --keysend flag must be set")
267-
case !ctx.IsSet("amt"):
268-
return fmt.Errorf("--amt must be set")
326+
case !ctx.IsSet(assetAmountFlag.Name):
327+
return fmt.Errorf("--asset_amount must be set")
269328
}
270329

271330
assetIDStr := ctx.String(assetIDFlag.Name)
272-
_, err = hex.DecodeString(assetIDStr)
331+
assetIDBytes, err := hex.DecodeString(assetIDStr)
273332
if err != nil {
274333
return fmt.Errorf("unable to decode assetID: %v", err)
275334
}
276335

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)
336+
assetAmountToSend := ctx.Uint64(assetAmountFlag.Name)
337+
if assetAmountToSend == 0 {
338+
return fmt.Errorf("must specify asset amount to send")
318339
}
319340

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

363+
rfqPeerKey, err := hex.DecodeString(ctx.String(rfqPeerPubKeyFlag.Name))
364+
if err != nil {
365+
return fmt.Errorf("unable to decode RFQ peer public key: "+
366+
"%w", err)
367+
}
368+
342369
// We use a constant amount of 500 to carry the asset HTLCs. In the
343370
// future, we can use the double HTLC trick here, though it consumes
344371
// more commitment space.
345372
const htlcCarrierAmt = 500
346373
req := &routerrpc.SendPaymentRequest{
347-
Dest: destNode,
348-
Amt: htlcCarrierAmt,
349-
DestCustomRecords: make(map[uint64][]byte),
350-
FirstHopCustomRecords: encodeResp.CustomRecords,
374+
Dest: destNode,
375+
Amt: htlcCarrierAmt,
376+
DestCustomRecords: make(map[uint64][]byte),
351377
}
352378

353379
if ctx.IsSet("payment_hash") {
@@ -370,7 +396,33 @@ func sendPayment(ctx *cli.Context) error {
370396

371397
req.PaymentHash = rHash
372398

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

376428
var payInvoiceCommand = cli.Command{
@@ -434,24 +486,6 @@ func payInvoice(ctx *cli.Context) error {
434486
return fmt.Errorf("unable to decode assetID: %v", err)
435487
}
436488

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-
455489
var assetID asset.ID
456490
copy(assetID[:], assetIDBytes)
457491

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

463497
defer cleanup()
464498

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

470-
rfqClient := rfqrpc.NewRfqClient(tapdConn)
503+
return commands.SendPaymentRequest(
504+
ctx, req, lndConn, tapdConn, func(ctx context.Context,
505+
payConn grpc.ClientConnInterface,
506+
req *routerrpc.SendPaymentRequest) (
507+
commands.PaymentResultStream, error) {
471508

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)
509+
tchrpcClient := tchrpc.NewTaprootAssetChannelsClient(
510+
payConn,
511+
)
476512

477-
resp, err := rfqClient.AddAssetSellOrder(
478-
ctxb, &rfqrpc.AddAssetSellOrderRequest{
479-
AssetSpecifier: &rfqrpc.AssetSpecifier{
480-
Id: &rfqrpc.AssetSpecifier_AssetIdStr{
481-
AssetIdStr: assetIDStr,
513+
stream, err := tchrpcClient.SendPayment(
514+
ctx, &tchrpc.SendPaymentRequest{
515+
AssetId: assetIDBytes,
482516
},
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)
517+
)
518+
if err != nil {
519+
return nil, err
520+
}
526521

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,
522+
return &resultStreamWrapper{
523+
amountMsat: decodeResp.NumMsat,
524+
stream: stream,
525+
}, nil
535526
},
536527
)
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)
547528
}
548529

549530
var addInvoiceCommand = cli.Command{

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ require (
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.20240730160739-664a6a1bb085
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

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,12 +1175,12 @@ github.com/lightninglabs/pool/auctioneerrpc v1.1.2 h1:Dbg+9Z9jXnhimR27EN37foc4aB
11751175
github.com/lightninglabs/pool/auctioneerrpc v1.1.2/go.mod h1:1wKDzN2zEP8srOi0B9iySlEsPdoPhw6oo3Vbm1v4Mhw=
11761176
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
11771177
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
1178-
github.com/lightninglabs/taproot-assets v0.4.1 h1:XtOIbw2sUl0GkjWKgZXxopU76BCjxXMW1Dqj8Ia9dGI=
1179-
github.com/lightninglabs/taproot-assets v0.4.1/go.mod h1:9ne4bQJkvp9b6Ez0DZhmDzXM/vbBKAHw6v3zc2FqTFQ=
1178+
github.com/lightninglabs/taproot-assets v0.4.2-0.20240730160739-664a6a1bb085 h1:76lG0Le7Jo06u77mG/Zbe7HeMUisfSh8IxLjOzKJ+kw=
1179+
github.com/lightninglabs/taproot-assets v0.4.2-0.20240730160739-664a6a1bb085/go.mod h1:GGL1wvezBV0TdW4V2YD1EN9hElNclwU5HVjIu9Hx38A=
11801180
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
11811181
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
1182-
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4 h1:LPnz0JxnzXJvCro714eBanzO7FKx5HF0ldU++zIu9yY=
1183-
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4/go.mod h1:0gen58n0DVnqJJqCMN3AXNtqWRT0KltQanlvehnhCq0=
1182+
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240730143253-1b353b0bfd58 h1:qmJAHLGfpeYIl1qUKyQViOjNAVRqF4afKuORzeIAwjA=
1183+
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240730143253-1b353b0bfd58/go.mod h1:0gen58n0DVnqJJqCMN3AXNtqWRT0KltQanlvehnhCq0=
11841184
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
11851185
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
11861186
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=

0 commit comments

Comments
 (0)