ClankerHookDynamicFee
Documentation for Clanker's Dynamic Fee Hook
The ClankerHookDynamicFee hook inherits from the ClankerHook and is used to set a dynamic fee for both the deployed token and the paired token. The math governing the dynamic fee took inspiration from Meteora's Dynamic Liquidity Market Maker's fee model.
The goal of the dynamic fee is to charge higher fees when there is volatility is present on the pool. We measure the volatility by simulating the price impact of a swap (measured in the difference between the beforeTick
and afterTick
) and applying a fee based on the magnitude. The resulting fee we apply is a mixture of the tick difference, recent swaps on the pool, and safeguards to prevent bot manipulation.
User Controlled Configuration Variables
The following configuration is used to control the range and sensitivity of the dynamic fees.
struct PoolDynamicConfigVars {
uint24 baseFee; // the minimum LP fee to be taken on a swap
uint24 maxLpFee; // the maximum LP fee to be taken on a swap
uint256 referenceTickFilterPeriod;
uint256 resetPeriod;
int24 resetTickFilter;
uint256 feeControlNumerator; // the denominator is set to 10_000_000_000
uint24 decayFilterBps;
}
Users are able to tune the dynamic fees as they choose, but we do provide a set of recommended configuration values on deployments through our website and API. We also have a spreadsheet that replicates this process and can provide it upon request.
Dynamic Fee Calculation
The dynamic fee is calculated by the following equation:
uint24 dynamicFee =
MIN(
(baseFee + (feeControlNumerator * (volatilityAccumulator ** 2)) / FEE_CONTROL_DENOMINATOR),
maxLpFee
);
The volatilityAccumulator
is a value that is updated on each swap and is calculated by the process described below.
Volatility Accumulator
The volatility accumulator is our way of measuring the amount of volatility on the pool. It is a value that is updated on each swap and is affected by recent swaps on the pool under different time frames. We use two lagging tick values, two time periods, and a decayed version of a previous volatility accumulator to calculate a swap's volatility accumulator.
The equation for the volatility accumulator is:
volatilityAccumulator = |afterTick - referenceTick| + prevVR;
The values of referenceTick
and prevVR
are updated based on snapshots of pool activity captured by the lagging ticks and time periods.
The two lagging tick values are:
referenceTick
: When thereferenceTickFilterPeriod
has passed or if aresetPeriod
has been triggered with a failing reset condition, this tick value is updated to thebeforeTick
of a swap. This tick value is kept the same until hitting one of these update conditions.resetTick
: This tick is updated to thebeforeTick
of a swap when thereferenceTick
is updated, and is additionally updated when theresetPeriod
has passed and a swap'sbeforeTick
is at leastresetTickFilter
ticks away from theresetTick
(a passing reset condition).
The two time periods and their uses are:
referenceTickFilterPeriod
: If the previous swap was less thanreferenceTickFilterPeriod
seconds away, we use the recordedreferenceTick
for the dynamic fee calculation instead of thebeforeTick
of the current swap. This prevents bots from avoiding the dynamic fee by cutting their swap into smaller swaps.resetPeriod
: If areferenceTick
has not been updated for overresetPeriod
seconds, we want to ensure that there is true volatility happening on the pool. The goal is to prevent bots from keeping the pool's fee high by sending in micro swaps to avoid updating thereferenceTick
. If theresetPeriod
has passed and thebeforeTick
's difference from the recordedresetTick
is smaller than theresetTickFilter
(a failing reset condition), we update both thereferenceTick
andresetTick
to thebeforeTick
of the current swap to clear out the stored volatility. If theresetPeriod
has passed and thebeforeTick
's difference from the recordedresetTick
is larger than theresetTickFilter
(a passing reset condition), we only update theresetTick
to thebeforeTick
of the current swap and keep thereferenceTick
the same.
Additionally, the two time periods are used to determine the prevVR
value. The prevVR
is used to smooth volatility over a period of time and is set to a value when the referenceTickFilterPeriod
has passed but the resetPeriod
has not. The value is set to the last swap's volatility accumulator multiplied by the decayFilterBps
value. The prevVR
is set to zero when the resetPeriod
passes with a failed condition or if a resetPeriod
amount of time has passed without a swap. The same prevVR
is used as long as the referenceTick
has not been updated.
Note: there is a slight drift between the simulated afterTick
and the resulting afterTick
. This is because the applied fee for the simulated swap is an estimate either reusing the previous swap fee or just the prevVR
under some reset conditions. We deemed that this drift is acceptable for our intended outcomes.
Last updated