docs
docs copied to clipboard
Improve Documentation on Hook Fees in Uniswap V4
Description
The current Hook Fees documentation primarily focuses on implementing fees through the beforeSwap hook. However, this approach can potentially lead to overcharging fees in certain scenarios. I suggest enhancing the documentation to cover implementation through the afterSwap hook, which offers several advantages, particularly for handling partial swaps.
Suggested Improvements
1. Add a Section on afterSwap Hook Fee Implementation
Implementing Hook Fees with afterSwap
While the standard approach uses beforeSwap to calculate fees based on the requested swap amount, an alternative approach using afterSwap allows for charging fees based on the actual output amount. This can be more accurate in cases of partial swaps due to swaps hitting the price limit(sqrtPriceLimitX96) or due to insufficient liquidity.
To implement hook fees with afterSwap, you'll need to enable both afterSwap and afterSwapReturnDelta permissions:
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
// Other permissions set to false
beforeSwap: false,
afterSwap: true,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: true,
// Other permissions set to false
});
}
The implementation requires both collecting the fee tokens and returning the appropriate delta:
function afterSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
BalanceDelta delta,
bytes calldata
) external override returns (bytes4, int128) {
// Determine which token is the output token
bool outputIsToken0 = params.zeroForOne ? false : true;
// Extract output amount (will be positive in delta)
int256 outputAmount = outputIsToken0 ? delta.amount0() : delta.amount1();
// Only charge fees on positive output amount
if (outputAmount > 0) {
// Calculate fee amount
uint256 feeAmount = (uint256(outputAmount) * HOOK_FEE_PERCENTAGE) / FEE_DENOMINATOR;
// Take the fee tokens
Currency feeCurrency = outputIsToken0 ? key.currency0 : key.currency1;
poolManager.take(feeCurrency, address(this), feeAmount);
// Return the fee amount as unspecified delta
// The Hooks library will map this to the correct token automatically
return (BaseHook.afterSwap.selector, int128(int256(feeAmount)));
}
return (BaseHook.afterSwap.selector, 0);
}
2. Explain How afterSwap Delta Works
Understanding afterSwap Delta
The int128 value returned from afterSwap represents the delta for the "unspecified currency" - which is always the output currency in a swap(regardless of whether it's an exact in/out swap):
- In a swap from token0 to token1 (
zeroForOne = true), token1 is the unspecified currency - In a swap from token1 to token0 (
zeroForOne = false), token0 is the unspecified currency
A positive delta means "the hook is keeping this amount of the unspecified currency," effectively reducing what the user receives. The Hooks library automatically maps this delta to the correct token (token0 or token1) based on the swap parameters.
This approach ensures you're only charging fees on tokens that were actually transferred, not on the requested amount that might not be fully fulfilled in certain swap scenarios.
3. Include a Comparison Between Approaches
Comparing beforeSwap and afterSwap Fee Implementations
| Aspect | beforeSwap | afterSwap |
|---|---|---|
| Fee calculated on | Requested swap amount | Actual output amount |
| Handles partial swaps | May overcharge | Charges only on actual output |
| When fee is applied | Before swap execution | After swap execution |
| Required permissions | beforeSwap & beforeSwapReturnDelta | afterSwap & afterSwapReturnDelta |
| Implementation complexity | Higher (uses BeforeSwapDelta) | Lower (single int128 return) |
Each approach has its advantages:
- beforeSwap: More flexible (can modify both currencies) and follows the execution flow of the swap
- afterSwap: More accurate for actual tokens transferred and simpler to implement in many cases