Skip to content

Commit 1bb83d1

Browse files
committed
loopout: use asset client for payment
1 parent 2d6b41d commit 1bb83d1

File tree

1 file changed

+101
-2
lines changed

1 file changed

+101
-2
lines changed

loopout.go

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import (
2020
"github.com/lightninglabs/loop/sweep"
2121
"github.com/lightninglabs/loop/sweepbatcher"
2222
"github.com/lightninglabs/loop/utils"
23+
"github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
2324
"github.com/lightningnetwork/lnd/chainntnfs"
2425
"github.com/lightningnetwork/lnd/channeldb"
2526
"github.com/lightningnetwork/lnd/lnrpc"
27+
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
2628
"github.com/lightningnetwork/lnd/lntypes"
2729
)
2830

@@ -87,8 +89,9 @@ type loopOutSwap struct {
8789
// to calculate the total cost of the swap.
8890
prepayAmount btcutil.Amount
8991

90-
swapPaymentChan chan paymentResult
91-
prePaymentChan chan paymentResult
92+
swapPaymentChan chan paymentResult
93+
prePaymentChan chan paymentResult
94+
waitForRfqCompletedChan chan interface{}
9295

9396
wg sync.WaitGroup
9497
}
@@ -140,6 +143,15 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
140143
log.Infof("Initiating swap request at height %v: amt=%v, expiry=%v",
141144
currentHeight, request.Amount, request.Expiry)
142145

146+
// If we have an asset id, we'll add that to the user agent.
147+
if request.AssetId != nil {
148+
if request.AssetEdgeNode == nil {
149+
return nil, errors.New("asset edge node must be set")
150+
}
151+
152+
request.Initiator += " asset_out"
153+
}
154+
143155
// The swap deadline will be given to the server for it to use as the
144156
// latest swap publication time.
145157
swapResp, err := cfg.server.NewLoopOutSwap(
@@ -634,13 +646,28 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) {
634646
s.log.Infof("Server recommended routing plugin: %v", pluginType)
635647
}
636648

649+
if s.AssetId != nil {
650+
s.waitForRfqCompletedChan = make(chan interface{})
651+
}
652+
637653
// Use the recommended routing plugin.
638654
s.swapPaymentChan = s.payInvoice(
639655
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
640656
s.LoopOutContract.OutgoingChanSet,
641657
s.LoopOutContract.PaymentTimeout, pluginType, true,
642658
)
643659

660+
// We'll need to wait for the RFQ to be completed before we can start
661+
// paying the prepay invoice. This is due to a timing bug in tapd that
662+
// on simultaneous RFQs return the same quote for both RFQs.
663+
if s.AssetId != nil {
664+
select {
665+
case <-ctx.Done():
666+
return
667+
case <-s.waitForRfqCompletedChan:
668+
}
669+
}
670+
644671
// Pay the prepay invoice. Won't use the routing plugin here as the
645672
// prepay is trivially small and shouldn't normally need any help. We
646673
// are sending it over the same channel as the loop out payment.
@@ -696,6 +723,11 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
696723
)
697724
if err != nil {
698725
result.err = err
726+
// On error, we close the asset channel to signal that
727+
// we are done and to not block the payment loop.
728+
if s.waitForRfqCompletedChan != nil {
729+
close(s.waitForRfqCompletedChan)
730+
}
699731
sendResult(result)
700732
return
701733
}
@@ -717,13 +749,80 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
717749
return resultChan
718750
}
719751

752+
func (s *loopOutSwap) payInvoiceWithAssetClient(ctx context.Context,
753+
invoice string) (*lndclient.PaymentStatus, error) {
754+
755+
totalPaymentTimeout := s.executeConfig.totalPaymentTimeout
756+
757+
sendReq := &routerrpc.SendPaymentRequest{
758+
PaymentRequest: invoice,
759+
TimeoutSeconds: int32(totalPaymentTimeout.Seconds()),
760+
FeeLimitMsat: 1_000_000,
761+
}
762+
763+
paymentStream, err := s.assets.SendPayment(
764+
ctx, &tapchannelrpc.SendPaymentRequest{
765+
AssetId: s.AssetId,
766+
PeerPubkey: s.AssetEdgeNode,
767+
PaymentRequest: sendReq,
768+
})
769+
if err != nil {
770+
return nil, err
771+
}
772+
773+
// We want to receive the accepted quote message first, so we know how
774+
// many assets we're going to pay.
775+
for {
776+
select {
777+
case <-ctx.Done():
778+
return nil, ctx.Err()
779+
default:
780+
msg, err := paymentStream.Recv()
781+
if err != nil {
782+
return nil, err
783+
}
784+
785+
switch msg.GetResult().(type) {
786+
case *tapchannelrpc.SendPaymentResponse_AcceptedSellOrder:
787+
quote := msg.GetAcceptedSellOrder()
788+
log.Infof("Accepted quote: %v",
789+
quote.AssetAmount)
790+
791+
if s.waitForRfqCompletedChan != nil {
792+
close(s.waitForRfqCompletedChan)
793+
s.waitForRfqCompletedChan = nil
794+
}
795+
796+
case *tapchannelrpc.SendPaymentResponse_PaymentResult:
797+
payRes := msg.GetPaymentResult()
798+
if payRes.Status == lnrpc.Payment_SUCCEEDED ||
799+
payRes.Status == lnrpc.Payment_FAILED {
800+
801+
s.log.Infof("Asset Payment result: %v",
802+
payRes.Status)
803+
804+
return &lndclient.PaymentStatus{
805+
State: payRes.Status,
806+
}, nil
807+
}
808+
}
809+
}
810+
}
811+
}
812+
720813
// payInvoiceAsync is the asynchronously executed part of paying an invoice.
721814
func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
722815
invoice string, maxFee btcutil.Amount,
723816
outgoingChanIds loopdb.ChannelSet, paymentTimeout time.Duration,
724817
pluginType RoutingPluginType, reportPluginResult bool) (
725818
*lndclient.PaymentStatus, error) {
726819

820+
// If we want to use an asset for the payment, we need to use the asset
821+
// client.
822+
if s.AssetId != nil {
823+
return s.payInvoiceWithAssetClient(ctx, invoice)
824+
}
825+
727826
// Extract hash from payment request. Unfortunately the request
728827
// components aren't available directly.
729828
chainParams := s.lnd.ChainParams

0 commit comments

Comments
 (0)