UniHook
BaseUniHook is a Uniswap v4 hook on Base. Runs code on every swap. Can take tokens from swaps. Gates who can add liquidity. Runs at pool creation. Handles donations to LPs.
- Contract name
- UniHook
How this hook works
Trading taxA trading tax hook. It takes a cut of every trade, charges more right after launch and trades on its own.
Takes a cut of every trade
On top of the normal pool fee, it keeps a share of each trade.
The code that does this (beforeSwap):function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external view onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { uint256 fee = _beforeSwapFee(PoolId.unwrap(key.toId()), sender, params.zeroForOne, params.amountSpecified); if (fee == 0) return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0); }Higher at launch
The fee starts higher right after launch and comes down over time, which makes it costly for bots to buy first.
The code that does this (currentFee):function currentFee(address coin) external view returns (uint24) { PoolConfig storage cfg = _pools[poolIdOf[coin]]; if (cfg.launchTime == 0) return 0; return block.timestamp < uint256(cfg.launchTime) + cfg.sniperWindow ? cfg.sniperPips : cfg.feePips; }Trades on its own
It makes trades itself, for example to convert the fees it collected.
The code that does this (swapIntoParent):function _swapIntoParent(PoolConfig storage p, uint256 amountIn) internal override returns (uint256 spent, uint256 out) { BalanceDelta d = poolManager.swap( _key(p.coin, p.host, p.hostIsToken0), IPoolManager.SwapParams({ zeroForOne: !p.hostIsToken0, amountSpecified: -int256(amountIn), sqrtPriceLimitX96: p.startSqrtPriceX96 }), "" ); (int128 coinD, int128 hostD) = p.hostIsToken0 ? (d.amount1(), d.amount0()) : (d.amount0(), d.amount1()); spent = uint256(uint128(-coinD)); out = uint256(uint128(hostD)); }Limits on trading
It limits trading, for example a max buy or wallet size or a wait between trades, to stop bots grabbing the supply before real traders.
The code that does this (afterSwap):function _afterSwap( bytes32 id, address sender, bool zeroForOne, int256 amountSpecified, int128 delta0, int128 delta1 ) internal returns (int128 hookDelta) { PoolConfig storage cfg = _pools[id]; _checkPrice(cfg, id); (int128 hostDelta, int128 coinDelta) = cfg.hostIsToken0 ? (delta0, delta1) : (delta1, delta0); bool isBuy = _isBuy(cfg, zeroForOne); uint24 rate = _rate(cfg, sender); if ( isBuy && cfg.sniperMaxBuy != 0 && sender != locker && block.timestamp < uint256(cfg.launchTime) + cfg.sniperWindow && coinDelta > 0 && uint256(uint128(coinDelta)) > cfg.sniperMaxBuy ) revert SniperMaxBuy(); bool exactInput = amountSpecified < 0; uint256 hostSwapped = uint256(uint128(hostDelta < 0 ? -hostDelta : hostDelta)); uint256 fee; if (exactInput == isBuy) { fee = _specifiedFee(amountSpecified, rate); // A price limit that stopped the swap early would leave the beforeSwap fee too big for what traded. The // locker's first buy stops at the 5% cap by design and sizes its input so only rounding wei are cut. if (sender != locker) { uint256 requested = exactInput ? uint256(-amountSpecified) : uint256(amountSpecified); if (hostSwapped != (exactInput ? requested - fee : requested + fee)) revert PartialFill(); } // …Rules for adding liquidity
It decides who can add liquidity, or when.
The code that does this (beforeAddLiquidity):function beforeAddLiquidity(address sender, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external view onlyPoolManager 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 sqrtPriceX96) external view onlyPoolManager returns (bytes4) { _checkInitialize( sender, PoolId.unwrap(key.toId()), sqrtPriceX96, key.fee == C.POOL_FEE && key.tickSpacing == C.TICK_SPACING ); return IHooks.beforeInitialize.selector; }
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.
It runs when someone donates tokens to the pool’s LPs.
Hook history
- Registered a new pool 1 time
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.