Overview
Hook Prog is one Uniswap v4 hook that serves many pools. When a pool is opened through it, the pool's rules are written to storage once; the hook reads that record on every swap. There is no function that edits the record, so nothing about a live pool can change afterwards, the owner included.
The hook's address carries the permission bits 0x28cc: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap and the two swap-delta bits. Removing liquidity is not hooked: LPs can always leave.
Two lanes
New token. launch deploys a fixed-supply ERC-20 of 1,000,000,000 units, opens its pool, and places the whole supply in one token-only band held by the hook. The contract has no function that removes that band. The creator may buy in the same transaction, before anyone else can, up to 5% of supply; that buy is a call from the hook to the pool, which v4 runs without hook callbacks, so it pays no rule.
Existing token. openExisting initializes a fresh hooked pool for a token that already exists. It adds no liquidity, holds no position, and records only who opened it. Launch Guard is refused on this lane, because a guard only means something when the hook holds the only liquidity.
Pools on the hook can only be opened through those two functions. Anyone calling the PoolManager directly with this hook is refused in beforeInitialize.
Fee model
The base LP fee goes to in-range liquidity in full. The protocol takes a share s from inside each opted-in stream, 20% by default and never above 50%, frozen per pool at opening. A pool that opts into nothing but a base fee pays the protocol nothing.
| Stream | Charged on | 1 − s goes to |
|---|---|---|
| Base LP fee | every swap | LPs, 100 % |
| Dynamic fee above base | every swap, by size | LPs |
| Launch Guard tax | buys while the guard is open | creator |
| Directional fee | buys and sells | creator |
| LP Rewards | every swap | LPs, by donation |
| Nth-buy Pot | buys | the pot |
| Founding position fees | collected on demand | creator |
In integer pips (1,000,000 = 100 %), with surge the dynamic component and tax the directional fee plus the guard tax:
pSurge = surge * s / 10000 lpFee = base + surge - pSurge // the v4 LP fee for this swap protocol = pSurge + tax*s/10000 + lp*s/10000 + pot*s/10000 creator = tax - tax*s/10000 lpReward = lp - lp*s/10000 potAdd = pot - pot*s/10000 // buys only cut = quoteAmount * (protocol + creator + lpReward + potAdd) / 1e6
previewRates returns these five numbers for any swap before it is sent. The protocol's part accrues per currency in protocolAccrued; sweep, callable by anyone, sends all of it to the treasury address. There is no burn, no buyback and no staking path in the contract. Creators and pot winners withdraw with claim.
The dynamic component is span × min(1, size × sens / depth), where span is ceiling minus base, size the swap's specified amount and depth the in-range virtual reserve of that same currency. The guard tax at time t is snipeTax × (guardEnd − t) / (guardEnd − guardStart).
The four swap directions
Every stream is taken in the quote currency, whichever way the swap is specified, so neither the creator nor the protocol is ever paid in the token being traded.
| Swap | Where the cut is taken | Trader sees |
|---|---|---|
| Exact-input buy | beforeSwap, from the quote input | less quote reaches the pool |
| Exact-output buy | afterSwap, on top of the quote input | pays the swap plus the cut |
| Exact-input sell | afterSwap, from the quote output | receives the output minus the cut |
| Exact-output sell | beforeSwap, added to the quote output | receives the exact amount, sells more token |
Rule fields and limits
| Field | Bound |
|---|---|
| baseFeePips ≤ maxFeePips | at most 200,000 (20 %) |
| surgeSens | 0 when the fee is flat, otherwise 1 to 10 |
| snipeTax + max(buyTax, sellTax) + lp + pot | at most 300,000 (30 %) |
| guardSeconds | new-token lane only, at most 7 days |
| maxBuyPerBlock | needs a guard; zero for no cap |
| potEveryN | 2 to 100,000 when the pot is on |
| potMinBuy | above zero when the pot is on |
| protocolShareBps | at most 5,000, frozen per pool |
A rule set outside these bounds reverts with InvalidRules. The builder runs the same checks before you sign.
What the owner can do
- Move the treasury address. Revenue already accrued follows the new address at the next sweep.
- Change the default protocol share for pools opened afterwards, within the cap.
- Pause and resume the opening of new pools.
- Hand ownership over, in two steps.
The owner cannot change a live pool's rules or share, cannot move a founding position, cannot touch a creator's or a winner's claimable balance, and cannot stop swaps.
Integrating
- The pool key's fee is the v4 dynamic-fee flag
0x800000; the hook sets the LP fee per swap. - Every block works with empty hook data through any router. To name a pot recipient, pass
abi.encode(address)as hook data; otherwise the transaction's sender is used. - On an exact-input swap the cut is computed on the specified amount. Use the full-fill price limit so a partial fill is not charged on the unfilled part.
config(poolId)returns the frozen record;pools(i)andpoolCount()enumerate every pool on the hook.- Events:
PoolOpened,Cut,PotWon,Claimed,Swept,FoundingFeesCollected.
Contract status
The root hook is not deployed on Robinhood Chain yet. Until it is, the builder is a simulator and nothing on this site opens a real market.
HookProg.sol compiles with solc 0.8.26 (via-IR, optimizer 200, cancun) to 23,254 bytes of runtime code. Its end-to-end suite runs against Uniswap's own PoolManager on a local chain: 44 checks covering the launch, the on-chain logo and bio, the creator buy, the guard, all four swap directions, the pot, the payouts, the existing-asset lane and the owner functions, with the hook's balance checked against everything it owes after each step.
The contract has not been audited. A passing test suite is not an audit.
Addresses
| Hook Prog root hook | not deployed |
| Uniswap v4 PoolManager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
| Uniswap v4 PositionManager | 0x58daec3116aae6D93017bAAea7749052E8a04fA7 |
| Universal Router | 0x8876789976dEcBfCbBbe364623C63652db8C0904 |
| CREATE2 deployer | 0x4e59b44847b379578588920cA78FbF26c0B4956C |
| USDG | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 |
Robinhood Chain, chain id 4663. The Uniswap addresses are Uniswap's own deployments.
Known limitations
- Unaudited.
- The per-block buy cap and the pot counter use the chain's
block.number, which on Robinhood Chain follows the Ethereum L1 block, about twelve seconds, not the L2 block. - A per-block cap is shared by everyone buying in that block: one buyer can fill it.
- When no recipient is named, the pot falls back to the transaction's sender. A smart-account user whose transaction is sent by a bundler should name the recipient in the hook data.
- A creator who buys through their own guard pays the tax to themselves, less the protocol share.
- The Discover index is rebuilt from logs and can trail the chain; every page that uses it says to which block it has read.