@ -65,73 +65,99 @@ func (r *ThresholdRule) validate() error {
// swapAmount suggests a swap based on the liquidity thresholds configured,
// swapAmount suggests a swap based on the liquidity thresholds configured,
// returning zero if no swap is recommended.
// returning zero if no swap is recommended.
func ( r * ThresholdRule ) swapAmount ( channel * balances ,
func ( r * ThresholdRule ) swapAmount ( channel * balances ,
outRestrictions * Restrictions ) btcutil . Amount {
restrictions * Restrictions ) btcutil . Amount {
var (
// For loop out swaps, we want to adjust our incoming liquidity
// so the channel's incoming balance is our target.
targetBalance = channel . incoming
// For loop out swaps, we target a minimum amount of incoming
// liquidity, so the minimum incoming threshold is our target
// percentage.
targetPercentage = uint64 ( r . MinimumIncoming )
// For loop out swaps, we may want to preserve some of our
// outgoing balance, so the channel's outgoing balance is our
// reserve.
reserveBalance = channel . outgoing
// For loop out swaps, we may want to preserve some percentage
// of our outgoing balance, so the minimum outgoing threshold
// is our reserve percentage.
reservePercentage = uint64 ( r . MinimumOutgoing )
)
// Examine our total balance and required ratios to decide whether we
// Examine our total balance and required ratios to decide whether we
// need to swap.
// need to swap.
amount := loopOutSwapAmount (
amount := calculateSwapAmount (
channel , r . MinimumIncoming , r . MinimumOutgoing ,
targetBalance , reserveBalance , channel . capacity ,
targetPercentage , reservePercentage ,
)
)
// Limit our swap amount by the minimum/maximum thresholds set.
// Limit our swap amount by the minimum/maximum thresholds set.
switch {
switch {
case amount < outRestrictions . Minimum :
case amount < r estrictions. Minimum :
return 0
return 0
case amount > outRestrictions . Maximum :
case amount > r estrictions. Maximum :
return outRestrictions . Maximum
return r estrictions. Maximum
default :
default :
return amount
return amount
}
}
}
}
// loopOutSwapAmount determines whether we can perform a loop out swap, and
// calculateSwapAmount calculates amount for a swap based on thresholds.
// returns the amount we need to swap to reach the desired liquidity balance
// This function can be used for loop out or loop in, but the concept is the
// specified by the incoming and outgoing thresholds.
// same - we want liquidity in one (target) direction, while preserving some
func loopOutSwapAmount ( balances * balances , incomingThresholdPercent ,
// minimum in the other (reserve) direction.
outgoingThresholdPercent int ) btcutil . Amount {
// * target: this is the side of the channel(s) where we want to acquire some
// liquidity. We aim for this liquidity to reach the threshold amount set.
minimumIncoming := btcutil . Amount ( uint64 (
// * reserve: this is the side of the channel(s) that we will move liquidity
balances . capacity ) *
// away from. This may not drop below a certain reserve threshold.
uint64 ( incomingThresholdPercent ) / 100 ,
func calculateSwapAmount ( targetAmount , reserveAmount ,
capacity btcutil . Amount , targetThresholdPercentage ,
reserveThresholdPercentage uint64 ) btcutil . Amount {
targetGoal := btcutil . Amount (
uint64 ( capacity ) * targetThresholdPercentage / 100 ,
)
)
minimumOutgoing := btcutil . Amount (
reserveMinimum := btcutil . Amount (
uint64 ( balances . capacity ) *
uint64 ( capacity ) * reserveThresholdPercentage / 100 ,
uint64 ( outgoingThresholdPercent ) / 100 ,
)
)
switch {
switch {
// If we have sufficient incoming capacity, we do not need to loop out .
// If we have sufficient target capacity, we do not need to swap .
case balances. incoming >= minimumIncoming :
case targetAmount >= targetGoal :
return 0
return 0
// If we are already below the threshold set for outgoing capacity, we
// If we are already below the threshold set for reserve capacity, we
// cannot take any further action.
// cannot take any further action.
case balances. outgoing <= minimumOutgoing :
case reserveAmount <= reserveMinimum :
return 0
return 0
}
}
// Express our minimum outgoing amount as a maximum incoming amount.
// Express our minimum reserve amount as a maximum target amount.
// We will use this value to limit the amount that we swap, so that we
// We will use this value to limit the amount that we swap, so that we
// do not dip below our outgoing threshold.
// do not dip below our reserve threshold.
maximum Incoming := balances . capacity - minimumOutgoing
maximum Target := capacity - reserveMinimum
// Calculate the midpoint between our minimum and maximum incoming
// Calculate the midpoint between our minimum and maximum target values.
// values. We will aim to swap this amount so that we do not tip our
// We will aim to swap this amount so that we do not tip our reserve
// outgoing balance beneath the desired level.
// balance beneath the desired level.
midpoint := ( minimumIncoming + maximumIncoming ) / 2
midpoint := ( targetGoal + maximumTarget ) / 2
// Calculate the amount of incoming balance we need to shift to reach
// Calculate the amount of target balance we need to shift to reach
// this desired midpoint.
// this desired midpoint.
required := midpoint - balances. incoming
required := midpoint - targetAmount
// Since we can have pending htlcs on our channel, we check the amount
// Since we can have pending htlcs on our channel, we check the amount
// of outbound capacity that we can shift before we fall below our
// of reserve capacity that we can shift before we fall below our
// threshold.
// threshold.
available := balances. outgoing - minimumOutgoing
available := reserveAmount - reserveMinimum
// If we do not have enough balance available to reach our midpoint, we
// If we do not have enough balance available to reach our midpoint, we
// take no action. This is the case when we have a large portion of
// take no action. This is the case when we have a large portion of