Skip to main content

A fee on every swap

The most common hook on Robinhood Chain: every swap in your pool sends a cut to a wallet you choose.

Ask for it​

I'm launching a token paired with ETH. Build a hook that sends 1% of every swap to my wallet.

Useful details to add:

  • Who receives the fee, and whether you can change that later.
  • Whether the rate is fixed or adjustable, and the highest it can ever be.
  • Whether only you can create pools with this hook.

How it works​

The hook uses two permissions: afterSwap and afterSwapReturnDelta.

After each swap, it looks at the token the trader did not fix the amount of, takes the fee from it, and sends it to the recipient:

  • On an exact-input swap ("sell exactly 1 ETH"), the fee comes out of what the trader receives.
  • On an exact-output swap ("buy exactly 1000 tokens"), the fee is added to what the trader pays.

Because of this, the recipient is paid in both tokens of the pair over time.

This fee is on top of the pool's liquidity provider fee.

Reference contract​

This version was tested with real swaps: on a 1% setting the recipient received 1% on both exact-input and exact-output swaps, and no tokens were left in the hook.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";

contract CreatorFeeHook is BaseHook {
address public feeRecipient;
uint256 public feeBips = 100; // 1%

constructor(IPoolManager _poolManager, address _owner) BaseHook(_poolManager) {
feeRecipient = _owner;
}

function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}

function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata)
internal
override
returns (bytes4, int128)
{
bool specifiedIsZero = (params.amountSpecified < 0) == params.zeroForOne;
(Currency feeCurrency, int128 amount) =
specifiedIsZero ? (key.currency1, delta.amount1()) : (key.currency0, delta.amount0());
if (amount < 0) amount = -amount;
uint256 fee = (uint256(uint128(amount)) * feeBips) / 10_000;
if (fee == 0) return (BaseHook.afterSwap.selector, 0);
poolManager.take(feeCurrency, feeRecipient, fee);
return (BaseHook.afterSwap.selector, int128(int256(fee)));
}
}

The hook SuperHooks writes for you will differ in the details you ask for. Tested here means this exact contract; it is not an audit. See Security.

The pool​

New-token pools on Robinhood Chain are usually created with a 1% fee and tick spacing 200, paired with ETH. Say otherwise if you want something else.