ChainScore Labs
All Guides

Dynamic Fee Models in Modern AMMs

LABS

Dynamic Fee Models in Modern AMMs

Chainscore © 2025

Core Concepts of Dynamic Fees

Essential mechanisms that allow Automated Market Makers to algorithmically adjust trading costs based on real-time market conditions.

Volatility Targeting

Volatility targeting adjusts fees based on the price volatility of an asset pair. When volatility is high, fees increase to compensate liquidity providers for greater impermanent loss risk. During stable periods, fees decrease to attract more volume. This mechanism is central to protocols like Uniswap V3, which uses an oracle to measure volatility. It directly aligns LP risk with reward, creating a more efficient market.

Concentrated Liquidity

Concentrated liquidity allows LPs to provide capital within specific price ranges, dramatically increasing capital efficiency. Dynamic fees are often applied to these positions, with higher fees for ranges closer to the current price where trading is most active. This model, pioneered by Uniswap V3, enables LPs to earn more fees on their deployed capital. It creates a direct link between capital efficiency and fee generation.

Fee Tier Competition

Fee tier competition involves multiple, fixed fee tiers (e.g., 0.01%, 0.05%, 0.30%) for the same asset pair, allowing the market to select the most efficient rate. LPs choose a tier based on their risk tolerance and expected volume. Over time, liquidity migrates to the optimal tier, creating a market-driven fee discovery process. This is a foundational concept for many AMMs, including Balancer V2 and Curve v2.

Oracle-Based Adjustments

Oracle-based adjustments use external price feeds to trigger fee changes. For example, if an oracle reports significant deviation between the AMM's price and the broader market, fees can be increased to arbitrage the pool back to peg. This protects LPs from adverse selection. Protocols like Curve's dynamic fees use this to maintain stablecoin peg efficiency. It introduces a reactive, data-driven layer to fee management.

Utilization Rate Pricing

Utilization rate pricing dynamically sets fees based on the proportion of a pool's liquidity that is currently being used for swaps. High utilization indicates high demand and low available liquidity, triggering a fee increase. This model, similar to money market protocols, optimizes for capital efficiency and LP returns during periods of high volume. It ensures fees reflect the immediate opportunity cost of capital.

Governance-Controlled Parameters

Governance-controlled parameters allow a DAO or token holders to vote on key variables like fee ceilings, adjustment speed, and formula coefficients. This provides a human-in-the-loop mechanism to steer the automated system based on long-term goals. For instance, a DAO might vote to lower maximum fees to boost protocol competitiveness. It balances algorithmic efficiency with community-led strategic direction.

How Dynamic Fee Models Are Implemented

Process overview for implementing dynamic fee tiers and volatility-based adjustments in an AMM.

1

Define the Fee Tier Structure and Volatility Oracle

Establish the foundational parameters and data source for fee calculation.

Detailed Instructions

First, define the fee tier boundaries based on pool liquidity and the volatility measurement period. A common structure uses three tiers: 0.05% for stable pairs, 0.30% for standard pairs, and 1.00% for exotic pairs. Simultaneously, integrate a volatility oracle. This can be an on-chain oracle like Chainlink providing a standard deviation feed, or a time-weighted average price (TWAP) calculated directly from the pool over a defined window (e.g., the last 24 hours). The contract must store a historical price array to compute this TWAP.

  • Sub-step 1: Deploy or specify the address of the volatility data source.
  • Sub-step 2: Set the baseFee for each liquidity tier in the contract constructor.
  • Sub-step 3: Implement a function to update and store periodic price observations for TWAP calculation.
solidity
// Example constructor snippet for initializing tiers struct FeeTier { uint24 tickSpacing; uint24 fee; // In basis points (e.g., 500 for 0.05%) } FeeTier[] public feeTiers; constructor() { feeTiers.push(FeeTier({tickSpacing: 10, fee: 5})); // 0.05% feeTiers.push(FeeTier({tickSpacing: 60, fee: 30})); // 0.30% feeTiers.push(FeeTier({tickSpacing: 200, fee: 100})); // 1.00% }

Tip: For TWAP, ensure the observation window is long enough to filter out short-term noise but short enough to be responsive, typically 30 minutes to 24 hours.

2

Calculate Real-Time Volatility and Determine Dynamic Adjustment

Compute the current market volatility and map it to a fee multiplier.

Detailed Instructions

Using the integrated oracle, calculate the annualized volatility. For a TWAP, this involves taking the standard deviation of logarithmic price returns over the observation period and scaling it to a yearly basis. Map this volatility value to a fee multiplier using a piecewise function. For instance, if base volatility (e.g., 20% annualized) corresponds to a 1.0x multiplier, a volatility of 60% might trigger a 1.5x multiplier. This logic is typically implemented in a separate library or contract function that returns a uint16 multiplier (e.g., 1500 for 1.5x).

  • Sub-step 1: Call the oracle to get the latest volatility metric or calculate it from stored price observations.
  • Sub-step 2: Apply the scaling formula: volatility = (stdDev(logReturns) * sqrt(PeriodsPerYear)).
  • Sub-step 3: Pass the calculated volatility through a pre-defined function to get the multiplier, often using if/else statements or a lookup table.
solidity
// Example function to get a dynamic fee multiplier function getFeeMultiplier(uint256 volatilityBps) public pure returns (uint16) { // volatilityBps is volatility * 100 (e.g., 2000 for 20%) if (volatilityBps < 1500) return 1000; // 1.0x for low vol if (volatilityBps < 5000) return 1200; // 1.2x for medium vol if (volatilityBps < 10000) return 1500; // 1.5x for high vol return 2000; // 2.0x for extreme vol }

Tip: Use basis points (bps) for all calculations to avoid floating-point numbers. 1% = 100 bps.

3

Integrate Dynamic Logic into Swap Function

Modify the core swap function to apply the calculated dynamic fee.

Detailed Instructions

The core swap function must be updated to fetch the current dynamic fee before calculating the amount out. This involves calling the helper functions from the previous steps. The final fee is calculated as dynamicFee = (baseFee * multiplier) / 1000. This fee is then applied within the constant product formula x * y = k. The fee is typically deducted from the input amount before the swap proceeds, and the protocol's share is tracked for later withdrawal by liquidity providers.

  • Sub-step 1: At the start of the swap, call getBaseFee(tickSpacing) and getFeeMultiplier(currentVolatility).
  • Sub-step 2: Compute the total fee: totalFee = (baseFee * multiplier) / 1000. Ensure it does not exceed a maximum cap (e.g., 2%).
  • Sub-step 3: Apply the fee within the swap math: amountInAfterFee = amountIn * (1 - totalFee).
solidity
// Simplified snippet within a swap function function swap(...) external returns (int256 amountOut) { uint24 baseFee = getBaseFeeForPool(tickSpacing); uint16 multiplier = volatilityOracle.getFeeMultiplier(); uint24 totalFee = uint24((uint256(baseFee) * multiplier) / 1000); // Enforce a maximum fee cap of 200 bps (2%) if (totalFee > 200) totalFee = 200; // Apply fee to input amount uint256 amountInAfterFee = amountIn * (1e6 - totalFee) / 1e6; // ... proceed with swap logic using amountInAfterFee }

Tip: To save gas, consider caching the fee multiplier and only updating it after a significant volatility change or a time delay.

4

Implement Fee Distribution and Incentive Mechanisms

Route collected fees to LPs and optionally to a protocol treasury.

Detailed Instructions

Accumulated fees must be distributed to liquidity providers (LPs) proportionally to their share of the pool. This is typically done by minting new LP tokens representing the fee share and adding them to the pool's total liquidity, thereby increasing the value of each existing LP token. Some protocols implement a split, directing a portion (e.g., 10-25%) to a protocol treasury for development. This requires tracking feeGrowthGlobal variables for each token in the pool, which accumulate fees per unit of liquidity over time.

  • Sub-step 1: On each swap, calculate the fee amount in terms of both tokens and update the global feeGrowthGlobal0X128 and feeGrowthGlobal1X128 accumulators.
  • Sub-step 2: When an LP mints or burns positions, calculate their entitled fees based on the difference in these global accumulators since their last interaction.
  • Sub-step 3: Transfer the treasury's share, if any, to a designated address during the fee accrual step.
solidity
// Example of updating fee accumulators (conceptual) function _updateFeeGrowthGlobals(uint128 feeAmount0, uint128 feeAmount1) internal { uint128 liquidity = poolLiquidity; if (liquidity > 0) { // Accumulate fees per unit of liquidity (Q128.128 fixed point) feeGrowthGlobal0X128 += (feeAmount0 << 128) / liquidity; feeGrowthGlobal1X128 += (feeAmount1 << 128) / liquidity; // Deduct and send protocol fee share (e.g., 1/4 of the fee) uint128 protocolFee0 = feeAmount0 / 4; feeAmount0 -= protocolFee0; safeTransfer(token0, protocolTreasury, protocolFee0); } }

Tip: Use fixed-point arithmetic with sufficient precision (e.g., Q128.128) to avoid rounding errors in fee distribution.

5

Test and Simulate Under Market Conditions

Validate the model's behavior and economic security.

Detailed Instructions

Deploy the contract to a testnet and conduct rigorous simulations. Use historical price data for volatile and stable assets to backtest the fee model. Key metrics to monitor include: fee revenue for LPs compared to static models, arbitrageur profitability to ensure the pool remains efficient, and gas cost of the additional calculations. Write comprehensive tests using Foundry or Hardhat that simulate high volatility events, flash crashes, and periods of low liquidity to ensure the contract logic is robust and does not revert unexpectedly.

  • Sub-step 1: Create a fork of mainnet at a historical date with high volatility (e.g., March 12, 2020) using Foundry's cheatcodes.
  • Sub-step 2: Deploy the dynamic fee contract and seed it with liquidity, then simulate a series of swaps.
  • Sub-step 3: Assert that the collected fees align with the expected baseFee * multiplier formula and that LP token values increase accordingly.
solidity
// Example Foundry test snippet function testHighVolatilityFee() public { vm.createSelectFork("mainnet", 12_000_000); DynamicFeePool pool = new DynamicFeePool(...); // ... provide liquidity // Simulate a large price swing mockOracle.setVolatility(8000); // 80% annualized vol (uint256 feeBefore, ) = getPoolFees(); pool.swap(...); // Perform a swap (uint256 feeAfter, ) = getPoolFees(); // Assert fee collected matches 1.5x multiplier on base fee uint256 expectedFeeIncrease = ... ; assertEq(feeAfter - feeBefore, expectedFeeIncrease); }

Tip: Include fuzz tests for the fee multiplier function with random volatility inputs to ensure it never reverts or returns an invalid value.

Protocol Comparison: Dynamic Fee Implementations

Comparison of dynamic fee mechanisms across leading AMM protocols.

FeatureUniswap V4 (Hook)Curve v2Trader Joe v2.1

Core Mechanism

Custom hooks for on-chain logic

Internal oracle-based EMA

Volatility Oracle (VO) & LVR capture

Fee Adjustment Trigger

Programmable (e.g., time, volatility, volume)

Recent price movement vs. internal oracle

Real-time volatility & arbitrage profit estimation

Typical Fee Range

0.01% to 1%+ (hook-defined)

0.01% to 0.04% (amplified pools)

0.01% to 0.3% (dynamic base + variable)

Update Frequency

Per-transaction (hook execution)

Continuous, recalculated per trade

Every block (VO update), fee adjusted per trade

Gas Overhead

High (custom hook execution)

Moderate (oracle maintenance)

Moderate (oracle queries, fee logic)

Primary Goal

Maximum customization for LP strategies

Minimize impermanent loss in volatile assets

Maximize LP returns from arbitrageurs (LVR)

Example Implementation

Time-weighted average price (TWAP) hook

USDC-ETH pool (2pool-ng)

USDC.e/WAVAX pool on Avalanche

Strategic Implications for Different Participants

Optimizing Yield and Risk Management

Dynamic fee models fundamentally change the LP's calculus from a static to an active strategy. The primary goal is to allocate capital to pools where the fee tier algorithmically adjusts to maximize revenue during periods of high volatility and network congestion, without proportionally increasing impermanent loss risk.

Key Considerations

  • Fee Sensitivity Analysis: LPs must monitor the correlation between trading volume, price volatility, and the protocol's fee adjustment mechanism. For example, in a pool like Uniswap V3 with multiple static tiers, you choose based on historical data. A dynamic model, as proposed by protocols like Ambient Finance, automatically shifts the fee based on real-time volatility, requiring LPs to assess the long-term efficiency of this automation.
  • Capital Efficiency vs. Predictability: Higher, variable fees can compensate for increased IL during market swings, but they also may deter volume. LPs need to model whether the dynamic fee's upside during "fee spike" events outweighs the loss of consistent volume from traders seeking stable costs.
  • Protocol Selection Strategy: Commitment shifts from choosing a static fee tier (e.g., 0.05% on Uniswap) to evaluating the governance and parameters of the dynamic fee algorithm itself. Is the fee update frequency (e.g., every block vs. hourly) too slow or too fast for your market?

Practical Example

An LP providing ETH/USDC liquidity in a dynamic fee AMM might see the fee rate adjust from 0.05% to 0.30% during a major news-driven price swing. While this generates higher fee revenue per trade, the LP must be comfortable with the associated increase in arbitrage activity and impermanent loss magnitude during that period.

Optimizing Liquidity Provision with Dynamic Fees

A technical process for liquidity providers to analyze, select, and manage positions in AMMs with dynamic fee tiers.

1

Analyze Historical Fee Performance by Pool

Evaluate on-chain data to identify pools where dynamic fees have effectively captured volatility.

Detailed Instructions

Begin by querying historical data for target pools. Use subgraph APIs (e.g., Uniswap V3, Curve) or blockchain explorers to extract time-series data on fee tier utilization, volume, and implied volatility. Calculate the annualized fee yield for different fee tiers over rolling 7-day and 30-day windows. Compare this to the impermanent loss (divergence loss) experienced during the same period to assess risk-adjusted returns.

  • Sub-step 1: Use a subgraph query to fetch daily volume and fees collected for a specific pool address and fee tier.
  • Sub-step 2: Calculate the fee APR: (Fees Collected * 365) / (Total Liquidity * Days in Period).
  • Sub-step 3: Plot this APR against the price change of the pool's assets to visualize the fee-volatility correlation.
graphql
# Example subgraph query for Uniswap V3 pool data query { pool(id: "0x...") { feeGrowthGlobal0X128 feeGrowthGlobal1X128 volumeUSD liquidity } }

Tip: Focus on pools with assets that have high realized volatility but low correlation, as dynamic fees are designed to monetize price movement.

2

Select Optimal Fee Tier Based on Market Regime

Choose a fee tier strategy aligned with current and expected market conditions.

Detailed Instructions

Dynamic fee AMMs like Uniswap V4 hooks or Trader Joe v2.1 offer multiple fee tiers (e.g., 1 bps, 5 bps, 30 bps, 100 bps). Your selection should be a function of expected future volatility and target asset pair. For stablecoin or correlated asset pairs, lower fees (1-5 bps) are typically optimal to attract high volume. For volatile or exotic pairs, higher fees (30-100 bps) are necessary to compensate for increased inventory risk and impermanent loss.

  • Sub-step 1: Monitor volatility indicators like Bollinger Band width, historical volatility from oracles, or funding rates in perpetual markets.
  • Sub-step 2: If volatility is rising, consider provisioning liquidity in a higher fee tier to capture the increased swap demand.
  • Sub-step 3: Use a fee tier laddering strategy by splitting capital across multiple tiers to hedge against regime misprediction.
solidity
// Conceptual logic for a dynamic fee selection hook (Uniswap V4) function getFee(PoolKey memory key) external view returns (uint24 fee) { // Access oracle for volatility data uint256 volatility = volatilityOracle.getVolatility(key.currency0, key.currency1); if (volatility > THRESHOLD_HIGH) { return 3000; // 30 bps } else if (volatility > THRESHOLD_MED) { return 500; // 5 bps } else { return 100; // 1 bps } }

Tip: In sideways markets, lower fees may generate more volume; in trending markets, higher fees protect LP capital.

3

Implement Active Liquidity Management via Ranges

Concentrate capital within high-probability price ranges to maximize fee accumulation.

Detailed Instructions

In concentrated liquidity AMMs, your provided capital only earns fees when the price is within your set price range. Use volatility forecasts to set ranges that are wide enough to avoid frequent range exits but narrow enough to achieve high capital efficiency. A common strategy is to set ranges around ±2x the daily standard deviation of the price. Rebalance these ranges periodically or use limit orders to adjust positions automatically.

  • Sub-step 1: Calculate the current price and its standard deviation over a recent period (e.g., 24 hours).
  • Sub-step 2: Set your liquidity range: [Current Price / (1 + 2*σ), Current Price * (1 + 2*σ)].
  • Sub-step 3: Monitor the pool's price and your position's in-range status using event logs or position manager contracts.
typescript
// Example: Calculating a dynamic price range in JavaScript const currentPrice = 2000; // ETH/USD price const dailyVolatility = 0.05; // 5% daily std dev const rangeMultiplier = 2; const lowerBound = currentPrice * (1 - rangeMultiplier * dailyVolatility); const upperBound = currentPrice * (1 + rangeMultiplier * dailyVolatility); // Result: Range ~[1800, 2200]

Tip: Use Gamma Strategies or keeper networks to automate range rebalancing, especially for volatile pairs.

4

Monitor and Rebalance Based on Fee Performance

Continuously track fee accrual and adjust capital allocation to maintain optimal returns.

Detailed Instructions

Liquidity provision is not a set-and-forget activity. Establish a dashboard to track key metrics: fee APR, capital efficiency ratio (fees earned / TVL), and impermanent loss. Compare the performance of your active positions against benchmarks like simple holding or providing in a static fee pool. If a position's risk-adjusted return falls below a threshold (e.g., 20% lower than benchmark), consider reallocating capital.

  • Sub-step 1: Use a portfolio tracker (e.g., DeFi Llama, Zapper) or custom script to pull real-time fee data for your LP positions.
  • Sub-step 2: Calculate your position's Net LP Return: Fee Yield - Impermanent Loss - Gas Costs.
  • Sub-step 3: Execute a rebalance by withdrawing liquidity and redeploying to a better-performing pool or adjusting fee/range parameters.
bash
# Example CLI command to check a Uniswap V3 position's fees (conceptual) cast call <POSITION_MANAGER> \ "positions(uint256)(uint96,address,address,address,uint24,int24,int24,uint128,uint256,uint256,uint128,uint128)" \ <TOKEN_ID> # Decode output to get `tokensOwed0` and `tokensOwed1` for accrued fees.

Tip: Automate monitoring with alerts for when the pool price approaches your range boundary or when fee APR drops significantly.

5

Mitigate Risks from MEV and Gas Costs

Employ strategies to protect profits from maximal extractable value and transaction fees.

Detailed Instructions

Dynamic fee pools can be targets for Maximal Extractable Value (MEV), such as just-in-time (JIT) liquidity attacks, where bots provide liquidity for a single block to capture fees without long-term risk. As an LP, you must account for gas costs from frequent rebalancing. Use private transaction relays (e.g., Flashbots Protect) for rebalance transactions to avoid front-running. Consider batching operations or using Layer 2 networks where gas fees are lower.

  • Sub-step 1: For mainnet Ethereum, submit all rebalance and harvest transactions via a private RPC endpoint or mev-blocks to prevent sandwich attacks.
  • Sub-step 2: Calculate the break-even gas cost for a rebalance: (Expected Fee Increase * Position Value) > Gas Cost in USD.
  • Sub-step 3: For pools with high-frequency arbitrage, consider using dynamic fee hooks that implement a fee decay mechanism to discourage JIT liquidity.
solidity
// Simplified check for a hook to prevent low-commitment liquidity function beforeModifyPosition(...) external { require( block.timestamp - position.lastInteraction > MIN_COMMITMENT_TIME, "Commitment period not met" ); }

Tip: Liquidity provision on Arbitrum or Optimism can significantly reduce gas overhead for active management strategies.

SECTION-TECHNICAL_FAQ

Technical and Economic FAQ

Ready to Start Building?

Let's bring your Web3 vision to life.

From concept to deployment, ChainScore helps you architect, build, and scale secure blockchain solutions.