Skip to content

Commit 530cd56

Browse files
committed
liquidity: add easy asset autoloop out
1 parent 2161438 commit 530cd56

File tree

1 file changed

+130
-18
lines changed

1 file changed

+130
-18
lines changed

liquidity/liquidity.go

Lines changed: 130 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ package liquidity
3434

3535
import (
3636
"context"
37+
"encoding/json"
3738
"errors"
3839
"fmt"
3940
"math"
@@ -48,6 +49,7 @@ import (
4849
"github.com/lightninglabs/loop/loopdb"
4950
clientrpc "github.com/lightninglabs/loop/looprpc"
5051
"github.com/lightninglabs/loop/swap"
52+
"github.com/lightninglabs/taproot-assets/rfqmsg"
5153
"github.com/lightningnetwork/lnd/clock"
5254
"github.com/lightningnetwork/lnd/funding"
5355
"github.com/lightningnetwork/lnd/lntypes"
@@ -218,6 +220,10 @@ type Config struct {
218220
LoopOutTerms func(ctx context.Context,
219221
initiator string) (*loop.LoopOutTerms, error)
220222

223+
// GetAssetPrice returns the price of an asset in satoshis.
224+
GetAssetPrice func(ctx context.Context, assetId string,
225+
assetAmt, minSatAmt uint64) (btcutil.Amount, error)
226+
221227
// Clock allows easy mocking of time in unit tests.
222228
Clock clock.Clock
223229

@@ -305,6 +311,15 @@ func (m *Manager) Run(ctx context.Context) error {
305311
}
306312
}
307313

314+
for assetID := range m.params.AssetAutoloopParams {
315+
err = m.easyAutoLoopAsset(ctx, assetID)
316+
if err != nil {
317+
log.Errorf("easy autoloop asset "+
318+
"failed: id: %v, err: %v",
319+
assetID, err)
320+
}
321+
}
322+
308323
case <-ctx.Done():
309324
return ctx.Err()
310325
}
@@ -501,7 +516,41 @@ func (m *Manager) easyAutoLoop(ctx context.Context) error {
501516
m.refreshAutoloopBudget(ctx)
502517

503518
// Dispatch the best easy autoloop swap.
504-
err := m.dispatchBestEasyAutoloopSwap(ctx)
519+
err := m.dispatchBestEasyAutoloopSwap(
520+
ctx, "", m.params.EasyAutoloopTarget,
521+
)
522+
if err != nil {
523+
return err
524+
}
525+
526+
return nil
527+
}
528+
529+
// easyAutoLoopAsset is the main entry point for the easy auto loop functionality
530+
// for assets. This function will try to dispatch a swap in order to meet the
531+
// easy autoloop requirements for the given asset. For easyAutoloop to work
532+
// there needs to be an EasyAutoloopTarget defined in the parameters. Easy
533+
// autoloop also uses the configured max inflight swaps and budget rules defined
534+
// in the parameters.
535+
func (m *Manager) easyAutoLoopAsset(ctx context.Context, assetID string) error {
536+
assetParams, ok := m.params.AssetAutoloopParams[assetID]
537+
if !ok {
538+
return nil
539+
}
540+
541+
if !assetParams.EnableEasyOut {
542+
return nil
543+
}
544+
545+
// First check if we should refresh our budget before calculating any
546+
// swaps for autoloop.
547+
m.refreshAutoloopBudget(ctx)
548+
549+
// Dispatch the best easy autoloop swap.
550+
err := m.dispatchBestEasyAutoloopSwap(
551+
ctx, assetID,
552+
btcutil.Amount(assetParams.EasyAutoloopTargetAmount),
553+
)
505554
if err != nil {
506555
return err
507556
}
@@ -522,7 +571,9 @@ func (m *Manager) ForceAutoLoop(ctx context.Context) error {
522571

523572
// dispatchBestEasyAutoloopSwap tries to dispatch a swap to bring the total
524573
// local balance back to the target.
525-
func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
574+
func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context,
575+
assetID string, localTarget btcutil.Amount) error {
576+
526577
// Retrieve existing swaps.
527578
loopOut, err := m.cfg.ListLoopOut(ctx)
528579
if err != nil {
@@ -557,16 +608,30 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
557608
localTotal := btcutil.Amount(0)
558609
for _, channel := range channels {
559610
if channelIsCustom(channel) {
560-
continue
611+
if assetID == "" {
612+
continue
613+
}
614+
assetData := getCustomAssetData(channel, assetID)
615+
616+
if assetData == nil {
617+
continue
618+
}
619+
// We'll overwrite the channel local balance to be
620+
// the custom asset balance. This allows us to make
621+
// use of existing logic.
622+
channel.LocalBalance = btcutil.Amount(
623+
assetData.LocalBalance,
624+
)
561625
}
626+
562627
localTotal += channel.LocalBalance
563628
}
564629

565630
// Since we're only autolooping-out we need to check if we are below
566631
// the target, meaning that we already meet the requirements.
567-
if localTotal <= m.params.EasyAutoloopTarget {
632+
if localTotal <= localTarget {
568633
log.Debugf("total local balance %v below target %v",
569-
localTotal, m.params.EasyAutoloopTarget)
634+
localTotal, localTarget)
570635
return nil
571636
}
572637

@@ -579,23 +644,37 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
579644

580645
// Calculate the amount that we want to loop out. If it exceeds the max
581646
// allowed clamp it to max.
582-
amount := localTotal - m.params.EasyAutoloopTarget
583-
if amount > restrictions.Maximum {
584-
amount = restrictions.Maximum
647+
amount := localTotal - localTarget
648+
satAmount := amount
649+
650+
// If we run a custom asset, we'll need to convert the asset amount
651+
// we want to swap to the satoshi amount.
652+
if assetID != "" {
653+
satAmount, err = m.cfg.GetAssetPrice(
654+
ctx, assetID, uint64(amount),
655+
uint64(restrictions.Minimum),
656+
)
657+
if err != nil {
658+
return err
659+
}
660+
}
661+
662+
if satAmount > restrictions.Maximum {
663+
satAmount = restrictions.Maximum
585664
}
586665

587666
// If the amount we want to loop out is less than the minimum we can't
588667
// proceed with a swap, so we return early.
589-
if amount < restrictions.Minimum {
668+
if satAmount < restrictions.Minimum {
590669
log.Debugf("easy autoloop: swap amount is below minimum swap "+
591670
"size, minimum=%v, need to swap %v",
592-
restrictions.Minimum, amount)
671+
restrictions.Minimum, satAmount)
593672
return nil
594673
}
595674

596675
log.Debugf("easy autoloop: local_total=%v, target=%v, "+
597676
"attempting to loop out %v", localTotal,
598-
m.params.EasyAutoloopTarget, amount)
677+
localTarget, amount)
599678

600679
// Start building that swap.
601680
builder := newLoopOutBuilder(m.cfg)
@@ -611,7 +690,7 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
611690
channel.ChannelID, channel.LocalBalance)
612691

613692
swapAmt, err := btcutil.NewAmount(
614-
math.Min(channel.LocalBalance.ToBTC(), amount.ToBTC()),
693+
math.Min(channel.LocalBalance.ToBTC(), satAmount.ToBTC()),
615694
)
616695
if err != nil {
617696
return err
@@ -639,9 +718,17 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
639718
lnwire.NewShortChanIDFromInt(channel.ChannelID),
640719
}
641720

721+
var assetSwap *assetSwapInfo
722+
if assetID != "" {
723+
assetSwap = &assetSwapInfo{
724+
assetID: assetID,
725+
peerPubkey: channel.PubKeyBytes[:],
726+
}
727+
}
728+
642729
suggestion, err := builder.buildSwap(
643730
ctx, channel.PubKeyBytes, outgoing, swapAmt, easyParams,
644-
nil,
731+
assetSwap,
645732
)
646733
if err != nil {
647734
return err
@@ -1441,10 +1528,6 @@ func (m *Manager) pickEasyAutoloopChannel(channels []lndclient.ChannelInfo,
14411528
// Check each channel, since channels are already sorted we return the
14421529
// first channel that passes all checks.
14431530
for _, channel := range channels {
1444-
if channelIsCustom(channel) {
1445-
continue
1446-
}
1447-
14481531
shortChanID := lnwire.NewShortChanIDFromInt(channel.ChannelID)
14491532

14501533
if !channel.Active {
@@ -1467,7 +1550,12 @@ func (m *Manager) pickEasyAutoloopChannel(channels []lndclient.ChannelInfo,
14671550
continue
14681551
}
14691552

1470-
if channel.LocalBalance < restrictions.Minimum {
1553+
if channel.LocalBalance < restrictions.Minimum &&
1554+
// If we use a custom channel, the local balance is
1555+
// denominated in the asset's unit, so we don't need to
1556+
// check the minimum.
1557+
!channelIsCustom(channel) {
1558+
14711559
log.Debugf("Channel %v cannot be used for easy "+
14721560
"autoloop: insufficient local balance %v,"+
14731561
"minimum is %v, skipping remaining channels",
@@ -1578,3 +1666,27 @@ func channelIsCustom(channel lndclient.ChannelInfo) bool {
15781666
// don't want to consider it for swaps.
15791667
return channel.CustomChannelData != nil
15801668
}
1669+
1670+
// getCustomAssetData returns the asset data for a custom channel.
1671+
func getCustomAssetData(channel lndclient.ChannelInfo, assetID string,
1672+
) *rfqmsg.JsonAssetChanInfo {
1673+
1674+
if channel.CustomChannelData == nil {
1675+
return nil
1676+
}
1677+
1678+
var assetData rfqmsg.JsonAssetChannel
1679+
err := json.Unmarshal(channel.CustomChannelData, &assetData)
1680+
if err != nil {
1681+
log.Errorf("Error unmarshalling custom channel data: %v", err)
1682+
return nil
1683+
}
1684+
1685+
for _, asset := range assetData.Assets {
1686+
if asset.AssetInfo.AssetGenesis.AssetID == assetID {
1687+
return &asset
1688+
}
1689+
}
1690+
1691+
return nil
1692+
}

0 commit comments

Comments
 (0)