@@ -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.
@@ -717,13 +744,80 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
717744 return resultChan
718745}
719746
747+ func (s * loopOutSwap ) payInvoiceWithAssetClient (ctx context.Context ,
748+ invoice string ) (* lndclient.PaymentStatus , error ) {
749+
750+ totalPaymentTimeout := s .executeConfig .totalPaymentTimeout
751+
752+ sendReq := & routerrpc.SendPaymentRequest {
753+ PaymentRequest : invoice ,
754+ TimeoutSeconds : int32 (totalPaymentTimeout .Seconds ()),
755+ FeeLimitMsat : 1_000_000 ,
756+ }
757+
758+ paymentStream , err := s .assets .SendPayment (
759+ ctx , & tapchannelrpc.SendPaymentRequest {
760+ AssetId : s .AssetId ,
761+ PeerPubkey : s .AssetEdgeNode ,
762+ PaymentRequest : sendReq ,
763+ })
764+ if err != nil {
765+ return nil , err
766+ }
767+
768+ // We want to receive the accepted quote message first, so we know how
769+ // many assets we're going to pay.
770+ for {
771+ select {
772+ case <- ctx .Done ():
773+ return nil , ctx .Err ()
774+ default :
775+ msg , err := paymentStream .Recv ()
776+ if err != nil {
777+ return nil , err
778+ }
779+
780+ switch msg .GetResult ().(type ) {
781+ case * tapchannelrpc.SendPaymentResponse_AcceptedSellOrder :
782+ quote := msg .GetAcceptedSellOrder ()
783+ log .Infof ("Accepted quote: %v" ,
784+ quote .AssetAmount )
785+
786+ if s .waitForRfqCompletedChan != nil {
787+ close (s .waitForRfqCompletedChan )
788+ s .waitForRfqCompletedChan = nil
789+ }
790+
791+ case * tapchannelrpc.SendPaymentResponse_PaymentResult :
792+ payRes := msg .GetPaymentResult ()
793+ if payRes .Status == lnrpc .Payment_SUCCEEDED ||
794+ payRes .Status == lnrpc .Payment_FAILED {
795+
796+ s .log .Infof ("Asset Payment result: %v" ,
797+ payRes .Status )
798+
799+ return & lndclient.PaymentStatus {
800+ State : payRes .Status ,
801+ }, nil
802+ }
803+ }
804+ }
805+ }
806+ }
807+
720808// payInvoiceAsync is the asynchronously executed part of paying an invoice.
721809func (s * loopOutSwap ) payInvoiceAsync (ctx context.Context ,
722810 invoice string , maxFee btcutil.Amount ,
723811 outgoingChanIds loopdb.ChannelSet , paymentTimeout time.Duration ,
724812 pluginType RoutingPluginType , reportPluginResult bool ) (
725813 * lndclient.PaymentStatus , error ) {
726814
815+ // If we want to use an asset for the payment, we need to use the asset
816+ // client.
817+ if s .AssetId != nil {
818+ return s .payInvoiceWithAssetClient (ctx , invoice )
819+ }
820+
727821 // Extract hash from payment request. Unfortunately the request
728822 // components aren't available directly.
729823 chainParams := s .lnd .ChainParams
0 commit comments