mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopout: add asset loop out payment flow
This commit is contained in:
parent
0a0cda46b9
commit
1440b6bb1d
6 changed files with 317 additions and 14 deletions
106
client.go
106
client.go
|
|
@ -79,6 +79,13 @@ var (
|
|||
// quote call as the miner fee if the fee estimation in lnd's wallet
|
||||
// failed because of insufficient funds.
|
||||
MinerFeeEstimationFailed btcutil.Amount = -1
|
||||
|
||||
// defaultRFQExpiry is the default expiry time for RFQs.
|
||||
defaultRFQExpiry = 5 * time.Minute
|
||||
|
||||
// defaultRFQMaxLimitMultiplier is the default maximum fee multiplier for
|
||||
// RFQs.
|
||||
defaultRFQMaxLimitMultiplier = 1.2
|
||||
)
|
||||
|
||||
// Client performs the client side part of swaps. This interface exists to be
|
||||
|
|
@ -506,9 +513,30 @@ func (s *Client) resumeSwaps(ctx context.Context,
|
|||
func (s *Client) LoopOut(globalCtx context.Context,
|
||||
request *OutRequest) (*LoopOutSwapInfo, error) {
|
||||
|
||||
log.Infof("LoopOut %v to %v (channels: %v)",
|
||||
request.Amount, request.DestAddr, request.OutgoingChanSet,
|
||||
)
|
||||
if request.AssetId != nil {
|
||||
if request.AssetPrepayRfqId == nil ||
|
||||
request.AssetSwapRfqId == nil {
|
||||
|
||||
return nil, errors.New("asset prepay and swap rfq ids " +
|
||||
"must be set when using an asset id")
|
||||
}
|
||||
|
||||
// Verify that if we have an asset id set, we have a valid asset
|
||||
// client to use.
|
||||
if s.assetClient == nil {
|
||||
return nil, errors.New("asset client must be set " +
|
||||
"when using an asset id")
|
||||
}
|
||||
|
||||
log.Infof("LoopOut %v sats to %v with asset %x",
|
||||
request.Amount, request.DestAddr, request.AssetId,
|
||||
)
|
||||
} else {
|
||||
log.Infof("LoopOut %v to %v (channels: %v)",
|
||||
request.Amount, request.DestAddr,
|
||||
request.OutgoingChanSet,
|
||||
)
|
||||
}
|
||||
|
||||
if err := s.waitForInitialized(globalCtx); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -529,7 +557,10 @@ func (s *Client) LoopOut(globalCtx context.Context,
|
|||
}
|
||||
|
||||
// Create a new swap object for this swap.
|
||||
swapCfg := newSwapConfig(s.lndServices, s.Store, s.Server, s.assetClient)
|
||||
swapCfg := newSwapConfig(
|
||||
s.lndServices, s.Store, s.Server, s.assetClient,
|
||||
)
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
globalCtx, swapCfg, initiationHeight, request,
|
||||
)
|
||||
|
|
@ -574,6 +605,14 @@ func (s *Client) getExpiry(height int32, terms *LoopOutTerms,
|
|||
func (s *Client) LoopOutQuote(ctx context.Context,
|
||||
request *LoopOutQuoteRequest) (*LoopOutQuote, error) {
|
||||
|
||||
if request.AssetRFQRequest != nil {
|
||||
rfqReq := request.AssetRFQRequest
|
||||
if rfqReq.AssetId == nil || rfqReq.AssetEdgeNode == nil {
|
||||
return nil, errors.New("both asset edge node and " +
|
||||
"asset id must be set")
|
||||
}
|
||||
}
|
||||
|
||||
terms, err := s.Server.GetLoopOutTerms(ctx, request.Initiator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -608,12 +647,67 @@ func (s *Client) LoopOutQuote(ctx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &LoopOutQuote{
|
||||
loopOutQuote := &LoopOutQuote{
|
||||
SwapFee: quote.SwapFee,
|
||||
MinerFee: minerFee,
|
||||
PrepayAmount: quote.PrepayAmount,
|
||||
SwapPaymentDest: quote.SwapPaymentDest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If we use an Asset we'll rfq to get the asset amounts to use for
|
||||
// the swap.
|
||||
if request.AssetRFQRequest != nil {
|
||||
rfqReq := request.AssetRFQRequest
|
||||
if rfqReq.Expiry == 0 {
|
||||
rfqReq.Expiry = time.Now().Add(defaultRFQExpiry).Unix()
|
||||
}
|
||||
|
||||
if rfqReq.MaxLimitMultiplier == 0 {
|
||||
rfqReq.MaxLimitMultiplier = defaultRFQMaxLimitMultiplier
|
||||
}
|
||||
|
||||
// First we'll get the prepay rfq.
|
||||
prepayRfq, err := s.assetClient.GetRfqForAsset(
|
||||
ctx, quote.PrepayAmount, rfqReq.AssetId,
|
||||
rfqReq.AssetEdgeNode, rfqReq.Expiry,
|
||||
rfqReq.MaxLimitMultiplier,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The actual invoice swap amount is the requested amount plus
|
||||
// the swap fee minus the prepay amount.
|
||||
invoiceAmt := request.Amount + quote.SwapFee -
|
||||
quote.PrepayAmount
|
||||
|
||||
swapRfq, err := s.assetClient.GetRfqForAsset(
|
||||
ctx, invoiceAmt, rfqReq.AssetId,
|
||||
rfqReq.AssetEdgeNode, rfqReq.Expiry,
|
||||
rfqReq.MaxLimitMultiplier,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll also want the asset name to verify for the client.
|
||||
assetName, err := s.assetClient.GetAssetName(
|
||||
ctx, rfqReq.AssetId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loopOutQuote.LoopOutRfq = &LoopOutRfq{
|
||||
PrepayRfqId: prepayRfq.Id,
|
||||
PrepayAssetAmt: prepayRfq.AssetAmount,
|
||||
SwapRfqId: swapRfq.Id,
|
||||
SwapAssetAmt: swapRfq.AssetAmount,
|
||||
AssetName: assetName,
|
||||
}
|
||||
}
|
||||
|
||||
return loopOutQuote, nil
|
||||
}
|
||||
|
||||
// getLoopOutSweepFee is a helper method to estimate the loop out htlc sweep
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -29,6 +29,7 @@ require (
|
|||
github.com/lightningnetwork/lnd/clock v1.1.1
|
||||
github.com/lightningnetwork/lnd/queue v1.1.1
|
||||
github.com/lightningnetwork/lnd/ticker v1.1.1
|
||||
github.com/lightningnetwork/lnd/tlv v1.2.6
|
||||
github.com/lightningnetwork/lnd/tor v1.1.2
|
||||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
|
|
@ -123,7 +124,6 @@ require (
|
|||
github.com/lightningnetwork/lnd/healthcheck v1.2.5 // indirect
|
||||
github.com/lightningnetwork/lnd/kvdb v1.4.10 // indirect
|
||||
github.com/lightningnetwork/lnd/sqldb v1.0.4 // indirect
|
||||
github.com/lightningnetwork/lnd/tlv v1.2.6 // indirect
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
|
|
|
|||
59
interface.go
59
interface.go
|
|
@ -98,6 +98,18 @@ type OutRequest struct {
|
|||
// the configured maximum payment timeout) the total time spent may be
|
||||
// a multiple of this value.
|
||||
PaymentTimeout time.Duration
|
||||
|
||||
// AssetId is an optional asset id that can be used to specify the asset
|
||||
// that will be used to pay for the swap. If this is set, a connection
|
||||
// to a tapd server is required to pay for the asset.
|
||||
AssetId []byte
|
||||
|
||||
// AssetPrepayRfqId is the rfq id that is used to pay the prepay
|
||||
// invoice.
|
||||
AssetPrepayRfqId []byte
|
||||
|
||||
// AssetSwapRfqId is the rfq id that is used to pay the swap invoice.
|
||||
AssetSwapRfqId []byte
|
||||
}
|
||||
|
||||
// Out contains the full details of a loop out request. This includes things
|
||||
|
|
@ -145,6 +157,25 @@ type LoopOutQuoteRequest struct {
|
|||
// initiated the swap (loop CLI, autolooper, LiT UI and so on) and is
|
||||
// appended to the user agent string.
|
||||
Initiator string
|
||||
|
||||
// AssetRFQRequest is the optional RFQ request that can be used to quote
|
||||
// for asset rfqs using the asset client
|
||||
AssetRFQRequest *AssetRFQRequest
|
||||
}
|
||||
|
||||
type AssetRFQRequest struct {
|
||||
// AssetId is the asset that we'll quote for.
|
||||
AssetId []byte
|
||||
|
||||
// AssetEdgeNode is the pubkey of the peer that we'll quote for.
|
||||
AssetEdgeNode []byte
|
||||
|
||||
// Expiry is the unix timestamp when the rfq will expire.
|
||||
Expiry int64
|
||||
|
||||
// MaxLimitMultiplier is the multiplier that we'll use to calculate the
|
||||
// max limit we'll quote for.
|
||||
MaxLimitMultiplier float64
|
||||
}
|
||||
|
||||
// LoopOutTerms are the server terms on which it executes swaps.
|
||||
|
|
@ -181,6 +212,31 @@ type LoopOutQuote struct {
|
|||
// SwapPaymentDest is the node pubkey where to swap payment needs to be
|
||||
// sent to.
|
||||
SwapPaymentDest [33]byte
|
||||
|
||||
// LoopOutRfq is the RFQ that can be used in the actual loop out to
|
||||
// commit to an asset exchange rate.
|
||||
LoopOutRfq *LoopOutRfq
|
||||
}
|
||||
|
||||
// LoopOutRfq contains the details of an asset request for quote for a loop out
|
||||
// swap.
|
||||
type LoopOutRfq struct {
|
||||
// PrepayRfqId is the ID of the prepay RFQ.
|
||||
PrepayRfqId []byte
|
||||
|
||||
// PrepayAssetAmt is the amount of the asset that will be used to pay
|
||||
// for the prepay invoice.
|
||||
PrepayAssetAmt uint64
|
||||
|
||||
// SwapRfqId is the ID of the swap RFQ.
|
||||
SwapRfqId []byte
|
||||
|
||||
// SwapAssetAmt is the amount of the asset that will be used to pay for
|
||||
// the swap invoice.
|
||||
SwapAssetAmt uint64
|
||||
|
||||
// AssetName is the human readable name of the asset.
|
||||
AssetName string
|
||||
}
|
||||
|
||||
// LoopInRequest contains the required parameters for the swap.
|
||||
|
|
@ -430,6 +486,9 @@ type SwapInfo struct {
|
|||
// channels that may be used to loop out. On a loop in this field
|
||||
// is nil.
|
||||
OutgoingChanSet loopdb.ChannelSet
|
||||
|
||||
// AssetSwapInfo contains the asset information for the swap.
|
||||
AssetSwapInfo *loopdb.LoopOutAssetSwap
|
||||
}
|
||||
|
||||
// LastUpdate returns the last update time of the swap.
|
||||
|
|
|
|||
|
|
@ -210,6 +210,38 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
|
|||
PaymentTimeout: paymentTimeout,
|
||||
}
|
||||
|
||||
// If the asset id is set, we need to set the asset amount and asset id
|
||||
// in the request.
|
||||
if in.AssetInfo != nil {
|
||||
if len(in.AssetInfo.AssetId) != 0 &&
|
||||
len(in.AssetInfo.AssetId) != 32 {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"asset id must be set to a 32 byte value",
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.AssetRfqInfo.PrepayRfqId) != 0 &&
|
||||
len(in.AssetRfqInfo.PrepayRfqId) != 32 {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"prepay rfq id must be set to a 32 byte value",
|
||||
)
|
||||
}
|
||||
|
||||
if len(in.AssetRfqInfo.SwapRfqId) != 0 &&
|
||||
len(in.AssetRfqInfo.SwapRfqId) != 32 {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"swap rfq id must be set to a 32 byte value",
|
||||
)
|
||||
}
|
||||
|
||||
req.AssetId = in.AssetInfo.AssetId
|
||||
req.AssetPrepayRfqId = in.AssetRfqInfo.PrepayRfqId
|
||||
req.AssetSwapRfqId = in.AssetRfqInfo.SwapRfqId
|
||||
}
|
||||
|
||||
switch {
|
||||
case in.LoopOutChannel != 0 && len(in.OutgoingChanSet) > 0: // nolint:staticcheck
|
||||
return nil, errors.New("loop_out_channel and outgoing_" +
|
||||
|
|
@ -709,23 +741,52 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
|
|||
req.SwapPublicationDeadline,
|
||||
)
|
||||
|
||||
quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
|
||||
loopOutQuoteReq := &loop.LoopOutQuoteRequest{
|
||||
Amount: btcutil.Amount(req.Amt),
|
||||
SweepConfTarget: confTarget,
|
||||
SwapPublicationDeadline: publicactionDeadline,
|
||||
Initiator: defaultLoopdInitiator,
|
||||
})
|
||||
}
|
||||
|
||||
if req.AssetInfo != nil {
|
||||
if req.AssetInfo.AssetId == nil ||
|
||||
req.AssetInfo.AssetEdgeNode == nil {
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"asset id and edge node must both be set")
|
||||
}
|
||||
loopOutQuoteReq.AssetRFQRequest = &loop.AssetRFQRequest{
|
||||
AssetId: req.AssetInfo.AssetId,
|
||||
AssetEdgeNode: req.AssetInfo.AssetEdgeNode,
|
||||
Expiry: req.AssetInfo.Expiry,
|
||||
MaxLimitMultiplier: req.AssetInfo.MaxLimitMultiplier,
|
||||
}
|
||||
}
|
||||
|
||||
quote, err := s.impl.LoopOutQuote(ctx, loopOutQuoteReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.OutQuoteResponse{
|
||||
response := &looprpc.OutQuoteResponse{
|
||||
HtlcSweepFeeSat: int64(quote.MinerFee),
|
||||
PrepayAmtSat: int64(quote.PrepayAmount),
|
||||
SwapFeeSat: int64(quote.SwapFee),
|
||||
SwapPaymentDest: quote.SwapPaymentDest[:],
|
||||
ConfTarget: confTarget,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if quote.LoopOutRfq != nil {
|
||||
response.AssetRfqInfo = &looprpc.AssetRfqInfo{
|
||||
PrepayRfqId: quote.LoopOutRfq.PrepayRfqId,
|
||||
PrepayAssetAmt: quote.LoopOutRfq.PrepayAssetAmt,
|
||||
SwapRfqId: quote.LoopOutRfq.SwapRfqId,
|
||||
SwapAssetAmt: quote.LoopOutRfq.SwapAssetAmt,
|
||||
AssetName: quote.LoopOutRfq.AssetName,
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetLoopInTerms returns the terms that the server enforces for swaps.
|
||||
|
|
@ -2025,6 +2086,15 @@ func validateLoopOutRequest(ctx context.Context, lnd lndclient.LightningClient,
|
|||
return 0, errInvalidAddress
|
||||
}
|
||||
|
||||
// If this is an asset payment, we'll check that we have the necessary
|
||||
// outbound asset capacaity to fulfill the request.
|
||||
if req.AssetInfo != nil {
|
||||
// Todo(sputn1ck) actually check outbound capacity.
|
||||
return validateConfTarget(
|
||||
req.SweepConfTarget, loop.DefaultSweepConfTarget,
|
||||
)
|
||||
}
|
||||
|
||||
// Check that the label is valid.
|
||||
if err := labels.Validate(req.Label); err != nil {
|
||||
return 0, err
|
||||
|
|
|
|||
|
|
@ -65,6 +65,28 @@ type LoopOutContract struct {
|
|||
// PaymentTimeout is the timeout for any individual off-chain payment
|
||||
// attempt.
|
||||
PaymentTimeout time.Duration
|
||||
|
||||
// AssetSwapInfo contains information, should the loop out swpa be
|
||||
// paid via an asset channel.
|
||||
AssetSwapInfo *LoopOutAssetSwap
|
||||
}
|
||||
|
||||
type LoopOutAssetSwap struct {
|
||||
// AssetId is the optional asset id that is used to pay the swap invoice.
|
||||
AssetId []byte
|
||||
|
||||
// PrepayRfqId is the rfq id that is used to pay the prepay invoice.
|
||||
PrepayRfqId []byte
|
||||
|
||||
// SwapRfqId is the rfq id that is used to pay the swap invoice.
|
||||
SwapRfqId []byte
|
||||
|
||||
// PrepayPaidAmt is the asset amount that was paid for the prepay
|
||||
// invoice.
|
||||
PrepayPaidAmt uint64
|
||||
|
||||
// SwapPaidAmt is the asset amount that was paid for the swap invoice.
|
||||
SwapPaidAmt uint64
|
||||
}
|
||||
|
||||
// ChannelSet stores a set of channels.
|
||||
|
|
|
|||
64
loopout.go
64
loopout.go
|
|
@ -20,10 +20,13 @@ import (
|
|||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightninglabs/taproot-assets/fn"
|
||||
"github.com/lightninglabs/taproot-assets/rfqmsg"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -140,6 +143,18 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
log.Infof("Initiating swap request at height %v: amt=%v, expiry=%v",
|
||||
currentHeight, request.Amount, request.Expiry)
|
||||
|
||||
// If we have an asset id, we'll add that to the user agent.
|
||||
if request.AssetId != nil {
|
||||
if request.AssetPrepayRfqId == nil ||
|
||||
request.AssetSwapRfqId == nil {
|
||||
|
||||
return nil, errors.New("both rfq ids must be set for " +
|
||||
"asset swaps")
|
||||
}
|
||||
|
||||
request.Initiator += " asset_out"
|
||||
}
|
||||
|
||||
// The swap deadline will be given to the server for it to use as the
|
||||
// latest swap publication time.
|
||||
swapResp, err := cfg.server.NewLoopOutSwap(
|
||||
|
|
@ -208,6 +223,14 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
PaymentTimeout: request.PaymentTimeout,
|
||||
}
|
||||
|
||||
if request.AssetId != nil {
|
||||
contract.AssetSwapInfo = &loopdb.LoopOutAssetSwap{
|
||||
AssetId: request.AssetId,
|
||||
PrepayRfqId: request.AssetPrepayRfqId,
|
||||
SwapRfqId: request.AssetSwapRfqId,
|
||||
}
|
||||
}
|
||||
|
||||
swapKit := newSwapKit(
|
||||
swapHash, swap.TypeOut, cfg, &contract.SwapContract,
|
||||
)
|
||||
|
|
@ -633,20 +656,31 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) {
|
|||
}
|
||||
|
||||
// Use the recommended routing plugin.
|
||||
var assetSwapRfq []byte
|
||||
if s.isAssetSwap() {
|
||||
assetSwapRfq = s.AssetSwapInfo.SwapRfqId
|
||||
}
|
||||
s.swapPaymentChan = s.payInvoice(
|
||||
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
|
||||
s.LoopOutContract.OutgoingChanSet,
|
||||
s.LoopOutContract.PaymentTimeout, pluginType, true,
|
||||
assetSwapRfq,
|
||||
)
|
||||
|
||||
// Pay the prepay invoice. Won't use the routing plugin here as the
|
||||
// prepay is trivially small and shouldn't normally need any help. We
|
||||
// are sending it over the same channel as the loop out payment.
|
||||
s.log.Infof("Sending prepayment %v", s.PrepayInvoice)
|
||||
var assetPrepayRfq []byte
|
||||
if s.isAssetSwap() {
|
||||
assetPrepayRfq = s.AssetSwapInfo.PrepayRfqId
|
||||
}
|
||||
|
||||
s.prePaymentChan = s.payInvoice(
|
||||
ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee,
|
||||
s.LoopOutContract.OutgoingChanSet,
|
||||
s.LoopOutContract.PaymentTimeout, RoutingPluginNone, false,
|
||||
assetPrepayRfq,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -675,7 +709,7 @@ func (p paymentResult) failure() error {
|
|||
func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
|
||||
maxFee btcutil.Amount, outgoingChanIds loopdb.ChannelSet,
|
||||
paymentTimeout time.Duration, pluginType RoutingPluginType,
|
||||
reportPluginResult bool) chan paymentResult {
|
||||
reportPluginResult bool, rfqId []byte) chan paymentResult {
|
||||
|
||||
resultChan := make(chan paymentResult)
|
||||
sendResult := func(result paymentResult) {
|
||||
|
|
@ -690,7 +724,7 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
|
|||
|
||||
status, err := s.payInvoiceAsync(
|
||||
ctx, invoice, maxFee, outgoingChanIds, paymentTimeout,
|
||||
pluginType, reportPluginResult,
|
||||
pluginType, reportPluginResult, rfqId,
|
||||
)
|
||||
if err != nil {
|
||||
result.err = err
|
||||
|
|
@ -719,7 +753,7 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
|
|||
func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
|
||||
invoice string, maxFee btcutil.Amount,
|
||||
outgoingChanIds loopdb.ChannelSet, paymentTimeout time.Duration,
|
||||
pluginType RoutingPluginType, reportPluginResult bool) (
|
||||
pluginType RoutingPluginType, reportPluginResult bool, rfqId []byte) (
|
||||
*lndclient.PaymentStatus, error) {
|
||||
|
||||
// Extract hash from payment request. Unfortunately the request
|
||||
|
|
@ -782,6 +816,25 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
|
|||
MaxParts: s.executeConfig.loopOutMaxParts,
|
||||
}
|
||||
|
||||
// If we want an asset swap, we'll need to set the custom first hop
|
||||
// data to the rfq id. This will then allow LND to route the payment
|
||||
// through the asset channel, as the edge nodes tap will know about the
|
||||
// payment through the rfq id.
|
||||
if s.isAssetSwap() {
|
||||
var rfq rfqmsg.ID
|
||||
if n := copy(rfq[:], rfqId); n != 32 {
|
||||
return nil, fmt.Errorf("rfq id has wrong length: %v", n)
|
||||
}
|
||||
|
||||
htlc := rfqmsg.NewHtlc(nil, fn.Some(rfq))
|
||||
htlcMapRecords, err := tlv.RecordsToMap(htlc.Records())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.FirstHopCustomRecords = htlcMapRecords
|
||||
}
|
||||
|
||||
// Lookup state of the swap payment.
|
||||
payCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
|
@ -1404,3 +1457,8 @@ func (s *loopOutSwap) canSweep() bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
// isAssetSwap returns true if the swap is an asset swap.
|
||||
func (s *loopOutSwap) isAssetSwap() bool {
|
||||
return s.AssetSwapInfo != nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue