Skip to content

Commit 0c3ea4e

Browse files
committed
liquidity: add easy asset autoloop out
1 parent 67af997 commit 0c3ea4e

File tree

1 file changed

+126
-18
lines changed

1 file changed

+126
-18
lines changed

liquidity/liquidity.go

Lines changed: 126 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,11 @@ 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 uint64, minSatAmt btcutil.Amount) (btcutil.Amount,
226+
error)
227+
221228
// Clock allows easy mocking of time in unit tests.
222229
Clock clock.Clock
223230

@@ -305,6 +312,14 @@ func (m *Manager) Run(ctx context.Context) error {
305312
}
306313
}
307314

315+
for assetId := range m.params.AssetAutoloopParams {
316+
err := m.easyAutoLoopAsset(ctx, assetId)
317+
if err != nil {
318+
log.Errorf("easy autoloop asset %v"+
319+
" failed: %v", assetId, err)
320+
}
321+
}
322+
308323
case <-ctx.Done():
309324
return ctx.Err()
310325
}
@@ -501,7 +516,38 @@ 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 := m.params.AssetAutoloopParams[assetId]
537+
538+
if !assetParams.EnableEasyOut {
539+
return nil
540+
}
541+
542+
// First check if we should refresh our budget before calculating any
543+
// swaps for autoloop.
544+
m.refreshAutoloopBudget(ctx)
545+
546+
// Dispatch the best easy autoloop swap.
547+
err := m.dispatchBestEasyAutoloopSwap(
548+
ctx, assetId,
549+
btcutil.Amount(assetParams.EasyAutoloopTargetAmount),
550+
)
505551
if err != nil {
506552
return err
507553
}
@@ -522,7 +568,9 @@ func (m *Manager) ForceAutoLoop(ctx context.Context) error {
522568

523569
// dispatchBestEasyAutoloopSwap tries to dispatch a swap to bring the total
524570
// local balance back to the target.
525-
func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
571+
func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context,
572+
assetId string, localTarget btcutil.Amount) error {
573+
526574
// Retrieve existing swaps.
527575
loopOut, err := m.cfg.ListLoopOut(ctx)
528576
if err != nil {
@@ -557,16 +605,30 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
557605
localTotal := btcutil.Amount(0)
558606
for _, channel := range channels {
559607
if channelIsCustom(channel) {
560-
continue
608+
if assetId == "" {
609+
continue
610+
}
611+
assetData := getCustomAssetData(channel, assetId)
612+
613+
if assetData == nil {
614+
continue
615+
}
616+
// We'll overwrite the channel local balance to be
617+
// the custom asset balance. This allows us to make
618+
// use of existing logic.
619+
channel.LocalBalance = btcutil.Amount(
620+
assetData.LocalBalance,
621+
)
561622
}
623+
562624
localTotal += channel.LocalBalance
563625
}
564626

565627
// Since we're only autolooping-out we need to check if we are below
566628
// the target, meaning that we already meet the requirements.
567-
if localTotal <= m.params.EasyAutoloopTarget {
629+
if localTotal <= localTarget {
568630
log.Debugf("total local balance %v below target %v",
569-
localTotal, m.params.EasyAutoloopTarget)
631+
localTotal, localTarget)
570632
return nil
571633
}
572634

@@ -579,23 +641,36 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
579641

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

587662
// If the amount we want to loop out is less than the minimum we can't
588663
// proceed with a swap, so we return early.
589-
if amount < restrictions.Minimum {
664+
if satAmount < restrictions.Minimum {
590665
log.Debugf("easy autoloop: swap amount is below minimum swap "+
591666
"size, minimum=%v, need to swap %v",
592-
restrictions.Minimum, amount)
667+
restrictions.Minimum, satAmount)
593668
return nil
594669
}
595670

596671
log.Debugf("easy autoloop: local_total=%v, target=%v, "+
597672
"attempting to loop out %v", localTotal,
598-
m.params.EasyAutoloopTarget, amount)
673+
localTarget, amount)
599674

600675
// Start building that swap.
601676
builder := newLoopOutBuilder(m.cfg)
@@ -611,7 +686,7 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
611686
channel.ChannelID, channel.LocalBalance)
612687

613688
swapAmt, err := btcutil.NewAmount(
614-
math.Min(channel.LocalBalance.ToBTC(), amount.ToBTC()),
689+
math.Min(channel.LocalBalance.ToBTC(), satAmount.ToBTC()),
615690
)
616691
if err != nil {
617692
return err
@@ -639,9 +714,17 @@ func (m *Manager) dispatchBestEasyAutoloopSwap(ctx context.Context) error {
639714
lnwire.NewShortChanIDFromInt(channel.ChannelID),
640715
}
641716

717+
var assetSwap *assetSwapInfo
718+
if assetId != "" {
719+
assetSwap = &assetSwapInfo{
720+
assetId: assetId,
721+
peerPubkey: channel.PubKeyBytes[:],
722+
}
723+
}
724+
642725
suggestion, err := builder.buildSwap(
643726
ctx, channel.PubKeyBytes, outgoing, swapAmt, easyParams,
644-
nil,
727+
assetSwap,
645728
)
646729
if err != nil {
647730
return err
@@ -1441,10 +1524,6 @@ func (m *Manager) pickEasyAutoloopChannel(channels []lndclient.ChannelInfo,
14411524
// Check each channel, since channels are already sorted we return the
14421525
// first channel that passes all checks.
14431526
for _, channel := range channels {
1444-
if channelIsCustom(channel) {
1445-
continue
1446-
}
1447-
14481527
shortChanID := lnwire.NewShortChanIDFromInt(channel.ChannelID)
14491528

14501529
if !channel.Active {
@@ -1467,7 +1546,12 @@ func (m *Manager) pickEasyAutoloopChannel(channels []lndclient.ChannelInfo,
14671546
continue
14681547
}
14691548

1470-
if channel.LocalBalance < restrictions.Minimum {
1549+
if channel.LocalBalance < restrictions.Minimum &&
1550+
// If we use a custom channel, the local balance is
1551+
// denominated in the asset's unit, so we don't need to
1552+
// check the minimum.
1553+
!channelIsCustom(channel) {
1554+
14711555
log.Debugf("Channel %v cannot be used for easy "+
14721556
"autoloop: insufficient local balance %v,"+
14731557
"minimum is %v, skipping remaining channels",
@@ -1578,3 +1662,27 @@ func channelIsCustom(channel lndclient.ChannelInfo) bool {
15781662
// don't want to consider it for swaps.
15791663
return channel.CustomChannelData != nil
15801664
}
1665+
1666+
// getCustomAssetData returns the asset data for a custom channel.
1667+
func getCustomAssetData(channel lndclient.ChannelInfo, assetId string,
1668+
) *rfqmsg.JsonAssetChanInfo {
1669+
1670+
if channel.CustomChannelData == nil {
1671+
return nil
1672+
}
1673+
1674+
var assetData rfqmsg.JsonAssetChannel
1675+
err := json.Unmarshal(channel.CustomChannelData, &assetData)
1676+
if err != nil {
1677+
log.Errorf("Error unmarshalling custom channel data: %v", err)
1678+
return nil
1679+
}
1680+
1681+
for _, asset := range assetData.Assets {
1682+
if asset.AssetInfo.AssetGenesis.AssetID == assetId {
1683+
return &asset
1684+
}
1685+
}
1686+
1687+
return nil
1688+
}

0 commit comments

Comments
 (0)