SapHook
EthereumSapHook is a Uniswap v4 hook on Ethereum. Runs code on every swap. Can take tokens from swaps. Gates who can add liquidity. Runs at pool creation.
- Contract name
- SapHook
How this hook works
Trading taxA trading tax hook. It takes a tax on trades, charges more right after launch and controls who adds liquidity.
Takes a tax on trades
On top of the normal pool fee, it keeps part of each trade as a tax. It can never be above 5%. The tax builds up in the hook until it is collected.
The code that does this (beforeSwap):function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata) external onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { PoolId id = key.toId(); Market memory m = _markets[id]; (bool pairIs0, bool buy, bool pairSpecified) = _shape(key, m, params); if (!pairSpecified) return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); uint256 rate = buy ? _buyRate(m, sender) : m.feeBps; uint256 gross; uint256 fee; if (params.amountSpecified < 0) { gross = uint256(-params.amountSpecified); fee = (gross * rate) / BPS; } else { uint256 net = uint256(params.amountSpecified); gross = (net * BPS) / (BPS - rate); fee = gross - net; } _book(id, m, key, pairIs0, sender, buy, gross, fee); return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0); }Higher at launch
The tax starts higher right after launch and comes down over time, which makes it costly for bots to buy first.
The code that does this (buyRate):function _buyRate(Market memory m, address sender) private view returns (uint256 rate) { rate = m.feeBps; if (sender == launchpad || m.guardStartBps <= rate) return rate; uint256 elapsed = block.timestamp - m.openedAt; if (elapsed >= m.guardSeconds) return rate; rate = m.guardStartBps - ((uint256(m.guardStartBps) - rate) * elapsed) / m.guardSeconds; }Rules for adding liquidity
It decides who can add liquidity, or when.
The code that does this (beforeAddLiquidity):function beforeAddLiquidity(address sender, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) external view returns (bytes4) { if (sender != locker) revert NotLocker(); return IHooks.beforeAddLiquidity.selector; }Only approved callers create pools
New pools with this hook can only be created by approved callers, for example its own factory or launcher.
The code that does this (beforeInitialize):function beforeInitialize(address sender, PoolKey calldata key, uint160) external view returns (bytes4) { if (sender != launchpad) revert NotLaunchpad(); if (_markets[key.toId()].token == address(0)) revert UnknownMarket(); return IHooks.beforeInitialize.selector; }Anyone can trigger payouts
Anyone can collect the fees it has built up and send the collected fees on to where they go, so nobody has to wait on the hook developer.
The code that does this (collect):function collect(PoolId id) external returns (uint256 amount) { amount = _purse[id].creator; if (amount == 0) return 0; address to = beneficiaryOf[id]; _purse[id].creator = 0; _paid[id].creator += uint128(amount); poolManager.unlock(abi.encode(pairOf[id], to, amount)); emit Collected(id, to, amount); }
Who controls it: Nobody. The hook has no admin functions, and the code cannot be changed.
Based on the hook’s published code.
What it can do
Its code executes inside each trade in pools that use it.
It can take or add tokens during a swap. Fees, burns and custom curves work this way, and so do scams.
It checks every deposit before it goes in.
It runs when a pool is created, often to lock in that pool’s settings.
Hook history
- Handled a trade 4 times
Latest transactions
- 0xb2b0d2e2…8a68bc block 26,391,657
- 0x18b90c74…b2785e block 26,391,618
- 0xbd561784…d7868f block 26,391,609
- 0x7f3cddc4…88a5f5 block 26,391,609
- Opened the market 2 times
Latest transactions
- 0x56d70d11…fe1ff5 block 26,391,624
- 0x369546dc…ac2649 block 26,391,607
Counted from what the hook recorded on-chain across all its pools, for every action in its published code, plus token transfers in and out of it.