CanonicalHook
RobinhoodCanonicalHook is a Uniswap v4 hook on Robinhood Chain. Brand new: its first pool went live today. Still in use: 50 trades in the last week, and 1 new pool this week.
- Contract name
- CanonicalHook
How this hook works
Trading taxA trading tax hook. It takes a cut of every trade, charges more right after launch and keeps rewards to claim.
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, SwapParams calldata params, bytes calldata) internal override returns (bytes4, BeforeSwapDelta, uint24) { if (!params.zeroForOne || params.amountSpecified >= 0) { return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } int256 amountIn = -params.amountSpecified; // positive (exact-in) // Out of the pool's encodable range: pass through, the pool reverts downstream. if (uint256(amountIn) > type(uint128).max) { return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } PoolId id = key.toId(); uint128 ethIn = uint128(uint256(amountIn)); uint24 feeBps = currentFeeBps(id); uint128 fee = _ceilMulBps(ethIn, feeBps); // Keep at least 1 wei of input for the pool (prevents amountToSwap == 0 on // 1-wei swaps, which the PoolManager rejects with SwapAmountCannotBeZero). if (fee >= ethIn) { fee = ethIn - 1; } if (fee > 0) { // Take the fee out of the specified native ETH input, in raw native ETH. poolManager.take(CurrencyLibrary.ADDRESS_ZERO, treasury, fee); emit FeeTaken(key.toId(), sender, true, fee, feeBps); // …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 (currentFeeBps):function currentFeeBps(PoolId id) public view returns (uint24) { uint256 launch = poolLaunchTime[id]; if (launch == 0 || block.timestamp < launch + RAMP_DURATION) { return PHASE1_FEE_BPS; } uint256 elapsedSinceRamp = block.timestamp - (launch + RAMP_DURATION); if (elapsedSinceRamp >= LINEAR_RAMP_DURATION) { return PHASE2_FEE_BPS; } return uint24( uint256(PHASE1_FEE_BPS) + ((uint256(PHASE2_FEE_BPS - PHASE1_FEE_BPS) * elapsedSinceRamp) / LINEAR_RAMP_DURATION) ); }Rewards to claim
It keeps track of what people have earned, and they claim it themselves.
The code that does this (claimJackpot):function claimJackpot() public { uint256 amount = pendingJackpot[msg.sender]; if (amount == 0) revert NothingToClaim(); pendingJackpot[msg.sender] = 0; emit JackpotClaimed(msg.sender, amount); (bool ok, ) = msg.sender.call{value: amount}(""); if (!ok) revert EthTransferFailed(); }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, PoolKey calldata key, uint160) internal override returns (bytes4) { if (!key.currency0.isAddressZero()) revert NativeETHRequired(); PoolId id = key.toId(); if (poolLaunchTime[id] == 0) { uint256 timestamp = block.timestamp; poolLaunchTime[id] = timestamp; emit PoolInitialized(id, timestamp); } return BaseHook.beforeInitialize.selector; }Simple trades only
Trades must state the amount going in, not the amount coming out.
The code that does this (afterSwap):function _afterSwap( address sender, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata hookData ) internal override returns (bytes4, int128) { PoolId id = key.toId(); _settleExpiredKing(id); if (params.zeroForOne || params.amountSpecified == 0) { if (params.zeroForOne && delta.amount0() < 0 && sender != flashArb) { uint256 ethSpent = uint256(int256(-delta.amount0())); if (ethSpent >= MIN_KING_BUY_WEI) { address buyer = _resolveBuyer(hookData); qualifiedBuyEth[id][buyer] += ethSpent; lastKing[id] = buyer; uint256 deadline = block.timestamp + KING_TIMER_DURATION; kingDeadline[id] = deadline; emit NewKing(id, buyer, ethSpent, deadline, jackpotPot[id]); } } return (BaseHook.afterSwap.selector, 0); } if (sender == flashArb && sender != address(0)) { return (BaseHook.afterSwap.selector, 0); } if (params.amountSpecified > 0) { revert ExactOutputNotAllowed(); } // …
Who controls it: Nobody. The hook has no admin functions, and the code cannot be changed.
Based on the hook’s published code.What do these labels mean?
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.
Pools using it let the hook choose the swap fee instead of a fixed tier.
It runs when a pool is created, often to lock in that pool’s settings.
Hook history
- Took its fee from a trade 22 times
Latest transactions
- 0xe3e209eb…8e92de block 114,399,969
- 0x56c84b7e…4b33ff block 114,378,018
- 0xe064bfa8…240af1 block 114,375,135
- 0xd147cf6d…9a37c3 block 114,374,997
- 0x25c4b126…fa9a16 block 114,373,367
- 0xcbb699c8…4fccea block 114,373,157
- 0xeba0a48f…ce6dca block 114,372,951
- 0x0a0ceac6…3db9f0 block 114,369,083
- 0x5af4600a…fe3c6d block 114,368,997
- 0x51a4be78…fda47a block 114,368,903
- Funded the jackpot 22 times
Latest transactions
- 0xe3e209eb…8e92de block 114,399,969
- 0x56c84b7e…4b33ff block 114,378,018
- 0xe064bfa8…240af1 block 114,375,135
- 0xd147cf6d…9a37c3 block 114,374,997
- 0x25c4b126…fa9a16 block 114,373,367
- 0xcbb699c8…4fccea block 114,373,157
- 0xeba0a48f…ce6dca block 114,372,951
- 0x0a0ceac6…3db9f0 block 114,369,083
- 0x5af4600a…fe3c6d block 114,368,997
- 0x51a4be78…fda47a block 114,368,903
- Added to the pool’s liquidity 3 times
Latest transactions
- 0xb577b8c0…3c9716 block 114,360,434 · 3 times in this transaction
Settings changes: set up a 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.