Swap Fee Module

The Swap Fee Module is responsible for calculating the fees applied at swap time. It is an optional module. When Swap Fee Module is not set, the pool will use the default constant swap fee set at construction.

At the beginning of each swap, the following function gets called:

struct SwapFeeModuleData {
    uint256 feeInBips;
    bytes internalContext;
}

function getSwapFeeInBips(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn,
        address _user,
        bytes memory _swapFeeModuleContext
    ) external returns (SwapFeeModuleData memory swapFeeModuleData);

It takes the following inputs:

  • _tokenIn Input token for the swap.

  • _tokenOut Output token for the swap.

  • _amountIn Input token amount which _user wants to trade.

  • _user address of msg.sender in the pool’s swap function.

  • _swapFeeModuleContext Optional bytes encoded calldata for the case where the Swap Fee Module leverages off-chain or external data to function. For example, this can be used to provide signed price feeds or volatility estimates.

The module returns the fee amount to be charged in basis points (swapFeeModuleData.feeInBips) and arbitrary internal context as bytes for the case where the Swap Fee Module needs to be called once again at the end of the swap (swapFeeModuleData.internalContext). IMPORTANT: Since the Swap Fee Module gets queried before the Liquidity Module, there is no way to know in advance how much liquidity will be filled when quoting fees. Hence the swapFeeModuleData.feeInBips should be understood as follows:

First, we calculate amountInMinusFee as follows:

uint256 amountInMinusFee = Math.mulDiv(_amountIn, 1e4, 1e4 + swapFeeModuleData.feeInBips);

Note that swapFeeModuleData.feeInBips must not be greater than 1e4, meaning that the maximum possible swap fee is 50%.

Then, once the Liquidity Module gets queried and returns liquidityQuote.amountInFilled, the effectiveFee is calculated as follows:

uint256 effectiveFee = Math.mulDiv(liquidityQuote.amountInFilled, swapFeeModuleData.feeInBips, 1e4, Math.Rounding.Up);

We round up to ensure that fee calculation goes in favor of the pool.

So the total amount that the user gets changed in tokenIn will be liquidityQuote.amountInFilled + effectiveFee

swapFeeModuleData.feeInBips should be interpreted as the fee multiplier in basis points which gets applied to the tokenIn amount filled by the liquidity module, not the user’s initial _amountIn

If swapFeeModuleData.internalContext is a non-empty byte encoded payload, the following function gets called after execution of the swap:

function callbackOnSwapEnd(
        uint256 _effectiveFee,
        uint256 _amountInUsed,
        uint256 _amountOut,
        SwapFeeModuleData memory _swapFeeModuleData
    ) external;

This allows the Swap Fee Module to make any required internal state updates.

Swap Fee Modules can be reusable across both Universal and Sovereign pools without requiring developers to re-write nor replace Liquidity Modules. They can implement any kind of fee calculation logic, as long as they do not quote a fee above 50% of the input token amount on each swap.

Alternatively, in the case where a Swap Fee Module is not added to the pool (swapFeeModule = address(0)), the pool will use an immutable fee multiplier to calculate swap fees, lowering gas costs. Even after whitelisting a swap fee module, poolManager can always reset it to address(0), in which case a default constant swap fee would be applied.

Alternatively, both the Swap Fee Module and the immutable default fee can be ignored and the Liquidity Module can consider fee accounting logic when providing price quotes. Though this removes the ability for a Pool Manager to collect fees.

NOTE: The Swap Fee Module can only be changed by the Pool Manager once every 3 days. This prevents unexpected changes in Swap Fee logic.

Last updated