@@ -34,6 +34,7 @@ package liquidity
3434
3535import (
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