Previous releases
The contracts, launch generations and SDK that came before Hookr's current contracts, and the pools that still run on them.
On this page
- Contracts
- Modular Roots and Their Stack
- Launch Generations
- Registry Identifiers
- Which Pools Use Which Contracts
- Contract Notes
- Hookr Modular Hook V6
- Hookr Modular Hook V6 WTH V5
- Hookr Swap Accounting Kernel V3
- Hookr Native Mechanics Block V2
- Hookr Module Catalog V1
- Hookr Stack Registry V2
- Hookr Market Coordinator V5
- Hookr Native Mechanics Coordinator Lib V2
- Hookr Kernel Router V3
- Hookr Kernel Quoter V1
- Hookr Treasury Forwarder V1
- Hookr Market Coordinator Token Deployer V3
- Hookr Market Coordinator Initial Buy Lib V4
- Hookr WTH Executor Adapter V1
- How Pools on the Modular Roots Work
- Fee Model
- Guard Window
- Native Mechanics
- Pool Lifecycle
- Protocol Share and Treasury
- Immutability and Ownership
- RWA and ERC20 Quotes
- Config Schema and Limits
- Hook Block Combinations
- Hook Permissions
- Events for Indexers
- Claims and Fee Collection
- Swapping, Quoting and Liquidity
- Opening a Market
- Launchers
- Security Notes
- Verification
- Arb recapture on the recapture roots
- How the Lane Works
- Where the Profit Goes
- The Partner Executor
- Opening a Market on a Recapture Root
- Routing a Recapture Pool
- Superseded Recapture Roots
- Launch Generations 3, 4 and 5
- Generations 3 and 4, Legacy
- Generation 5, V1
- Hook Blocks on Generations 3 to 5
- Market Figures and Limits
- SDK 0.2
- ABIs and Errors
- HookrModularHookV6
- HookrMarketCoordinatorV5
- HookrNativeMechanicsBlockV2
- HookrStackRegistryV2
- HookrKernelRouterV3
- HookrKernelQuoterV1
- HookrTreasuryForwarderV1
- HookrTokenV61
Hookr's current contracts are what the rest of these docs describe. This page keeps what a builder still needs about the contracts that came before them. A pool never changes its hook, so a pool opened on those contracts keeps trading on them. Hookr opens new pools on its shared hook, never on these.
Two families are covered. The modular roots, which the app calls V2, share one registry, coordinator, router and quoter, and run five native Hook Blocks on every root; one of them adds arb recapture, and three superseded ones did too. The launch generations 3, 4 and 5 are the launchers from before the modular roots: a stepped bonding curve, then instant pools and auctions. The app calls generations 3 and 4 Legacy and generation 5 V1.
Contracts
Every Hookr contract address is read from the deployment record or the manifests the app ships; none is typed into this page. A short address on a live contract links to the explorer. The superseded roots and the launch generations are text.
Select a contract on the map to see its address, role and connections. Choose a flow to follow it one step at a time.
Select a box on the map to jump to that contract's address, role and connections in the list below.
Every contract, address and role (30)
Liquidity providersCaller
Add, remove and collect liquidity through Uniswap's PositionManager. Hookr has no liquidity contract of its own.
- To PositionManager: mint, increase, decrease, collect
Traders, routers and aggregatorsCaller
Swap through the Hookr router or the Universal Router, and price a swap through either quoter.
- To Universal Router 2.1.1: swap
- To V4Quoter: quote a Universal Router swap
- To HookrKernelRouterV3: swap with payer and recipient
- To HookrKernelQuoterV1: quote
CreatorsCaller
Open new-token and existing-asset markets through the coordinator, on either root.
- To HookrMarketCoordinatorV5: open a market
PositionManagerUniswap contract
0x58daec3116aae6D93017bAAea7749052E8a04fA7
Uniswap's position manager, the way in for adding, removing and collecting liquidity on every Hookr pool.
- To Permit2: pulls ERC-20 legs
- To Uniswap v4 PoolManager: modify liquidity
- From Liquidity providers: mint, increase, decrease, collect
Universal Router 2.1.1Uniswap contract
0x8876789976dEcBfCbBbe364623C63652db8C0904
The one Universal Router Hookr pins. Aggregator trades settle through it with empty hook data, so they enter no pot and earn no trader share.
- To Permit2: pulls an ERC-20 input
- To Uniswap v4 PoolManager: unlock and swap
- From Traders, routers and aggregators: swap
V4QuoterUniswap contract
0x8Dc178eFB8111BB0973Dd9d722ebeFF267c98F94
Uniswap's v4 quoter. It prices a Universal Router swap by simulating it.
- To Uniswap v4 PoolManager: simulate
- From Traders, routers and aggregators: quote a Universal Router swap
HookrKernelRouterV3Live Hookr contract
0xf0E528c39f33F565876cbaa7e0DFaCa38Df966E9
The trusted router both roots accept, with the payer and recipient authenticated in the hook data. That is how the pot reaches a wallet rather than a router contract.
- To Uniswap v4 PoolManager: unlock and swap
- From Traders, routers and aggregators: swap with payer and recipient
HookrKernelQuoterV1Live Hookr contract
0x5Ba8FBbB4aB20Ff6Daf0ecDEBe3784f869BcB1B5
The trusted quoter both roots accept. It simulates the swap through the same code path and returns the result by reverting.
- To Uniswap v4 PoolManager: simulate and revert
- From Traders, routers and aggregators: quote
Permit2Uniswap contract
0x000000000022D473030F116dDEE9F6B43aC78BA3
The allowance layer the Universal Router and the PositionManager pull ERC-20 tokens through. The Hookr router takes a plain token approval instead.
- From Universal Router 2.1.1: pulls an ERC-20 input
- From PositionManager: pulls ERC-20 legs
Uniswap v4 PoolManagerUniswap contract
0x8366a39CC670B4001A1121B8F6A443A643e40951
Uniswap v4's singleton where every Hookr pool lives. It calls the pool's root hook on each callback.
- To HookrModularHookV6: hook callbacks
- To HookrModularHookV6WthV5: hook callbacks
- To Superseded recapture roots: hook callbacks
- From Universal Router 2.1.1: unlock and swap
- From HookrKernelRouterV3: unlock and swap
- From HookrKernelQuoterV1: simulate and revert
- From V4Quoter: simulate
- From PositionManager: modify liquidity
- From HookrMarketCoordinatorV5: initialize, founding band
HookrModularHookV6Live Hookr contract
0xb3cA29cF721380CEe8b8e4755F3865Ebc68Fe8cC
The default root hook every standard Hookr pool names in its pool key, with HookrSwapKernelV3 compiled in. It reads the pool's frozen stack and runs each callback body in the shared accounting kernel.
- To HookrStackRegistryV2: reads the frozen stack
- To HookrSwapAccountingKernelV3: callback bodies
- To HookrModularCorrectionLibV2: linked, never dispatched
- From Uniswap v4 PoolManager: hook callbacks
- From HookrReleaseCreate2FactoryV1: mined the root address
HookrModularHookV6WthV5Live Hookr contract
0xb914f955294799de4b891bd2EA8AF628Fa1c68CC
The recapture root, with HookrSwapKernelV5Wth compiled in and the same kernel, registry and module as the default root. It adds a recapture through WTH around each swap and refuses a swap that arrives inside a registered v3 pool's callback.
- To HookrStackRegistryV2: reads the frozen stack
- To HookrSwapAccountingKernelV3: callback bodies
- To HookrWthExecutorAdapterV1: reads the bound executor
- To WTH's recapture executor: re-entrancy check
- To HookrModularCorrectionLibV3: recapture dispatch
- From Uniswap v4 PoolManager: hook callbacks
Superseded recapture rootsSuperseded, pools still trade
- HookrModularHookV6WthV4
0xE5429dB8f63912E632E86733905667AaEb6ea8cC - HookrModularHookV6Wth, third
0xa99902a2922014bBe2Bf2dCF15742ac5104828Cc - HookrModularHookV6Wth, first
0xc7c516CD5546bCB2592Fe3f8aa91C2A4bA3768CC
Earlier recapture roots whose pools still trade. New recapture markets open on the recapture root.
- From Uniswap v4 PoolManager: hook callbacks
HookrSwapAccountingKernelV3Live Hookr contract
0x530523DEcC9523dDF8A87842fFBBD24a59C7E060
Runs the callback bodies of both roots by delegatecall. It walks the pool's frozen modules, enforces their caps and credits claims.
- To HookrNativeMechanicsBlockV2: module callbacks
- To HookrStatefulSettlementLibV1: linked library
- From HookrModularHookV6: callback bodies
- From HookrModularHookV6WthV5: callback bodies
HookrModularCorrectionLibV3Live Hookr contract
0x11996B4e04571718d49454fC52830dc7fA0FF99C
The recapture library linked into the recapture root. It makes the one call to the pool's frozen recapture executor, and a failed recapture never blocks the swap.
Source not verified on the explorer.
- To HookrWthExecutorAdapterV1: one recapture call
- From HookrModularHookV6WthV5: recapture dispatch
HookrWthExecutorAdapterV1Live Hookr contract
0x28AF7A3645080e926a3101461e0Ec0594D42D806
The recapture executor every pool on the recapture root names. It holds no funds and forwards one call to WTH's executor, bound once.
- To WTH's recapture executor: forwards the recapture
- From HookrModularHookV6WthV5: reads the bound executor
- From HookrModularCorrectionLibV3: one recapture call
HookrNativeMechanicsBlockV2Live Hookr contract
0xD700492b504ba5A72D7de28dDe11Cd7985a7F1ae
The module every current pool carries, which runs the five native Hook Blocks. It also keeps the claim ledger that pays the pot, the royalty and the protocol share.
- To HookrTreasuryForwarderV1: protocol recipient
- From HookrSwapAccountingKernelV3: module callbacks
- From HookrTreasuryForwarderV1: collect
- From HookrModuleCatalogV1: canonical module
HookrTreasuryForwarderV1Live Hookr contract
0x9BB7c01571ef6e59a8834A6a89A6b024822BAf6f
The address pinned as the coordinator's treasury and the module's protocol recipient. Anyone can call collect or sweep to push what it holds to the owner-set target.
- To HookrNativeMechanicsBlockV2: collect
- To Payout target: pays the target
- From HookrNativeMechanicsBlockV2: protocol recipient
WTH's recapture executorPartner contract
0xc356cf51134e0DF02BFE880115DD8c66Ead45803
The contract WTH, the recapture partner, runs. It prices each recapture against its reference venues, trades it and pays every share in the same transaction.
WTH's contract, not Hookr's. Source not verified on the explorer.
- From HookrModularHookV6WthV5: re-entrancy check
- From HookrWthExecutorAdapterV1: forwards the recapture
Payout targetOwner-set account
0xF4Ab4698554D5c95874986d5e956c62e5E6aB3eE
The address the forwarder pays today, as its target reads back on chain. The owner can rotate it without touching a pinned address.
- From HookrTreasuryForwarderV1: pays the target
HookrModuleCatalogV1Live Hookr contract
0x6a3561d5B6305E2229F30EDF66702e8Bc4fA19D1
Admits module implementations by address and code hash. A registration cannot be edited; the owner can retire a module for new pools, and pools already open keep theirs.
- To HookrNativeMechanicsBlockV2: canonical module
- From HookrStackRegistryV2: checks modules
HookrStackRegistryV2Live Hookr contract
0x5b7f1A117A83aaac15B0698Aa4eB9D53fE5f5BA3
Seals each root's profile and freezes one stack per pool when the pool opens. Roots read it on every callback.
- To HookrModuleCatalogV1: checks modules
- From HookrModularHookV6: reads the frozen stack
- From HookrModularHookV6WthV5: reads the frozen stack
- From HookrMarketCoordinatorV5: createStack
HookrMarketCoordinatorV5Live Hookr contract
0x53A192A3fCeE94Da77916B461E0cCa2Dd6402442
Opens new-token and existing-asset markets on either root. It holds a new-token market's founding position and sets the protocol share per creator.
- To Uniswap v4 PoolManager: initialize, founding band
- To HookrStackRegistryV2: createStack
- To HookrNativeMechanicsCoordinatorLibV2: admission and founding fees
- To HookrMarketCoordinatorInitialBuyLibV4: linked library
- To HookrMarketCoordinatorTokenDeployerV3: linked library
- To HookrMarketCoordinatorKernelReservationLibV1: linked library
- From Creators: open a market
HookrMarketCoordinatorKernelReservationLibV1Live Hookr contract
0x8C2E208eDFf0eF7535B20AE8F13dfBF71133521d
A library linked into the coordinator.
- From HookrMarketCoordinatorV5: linked library
HookrMarketCoordinatorTokenDeployerV3Live Hookr contract
0x7A2821F7Ea0e4d0C5a409D479cd457d5B34F9399
Linked library that deploys a new-token market's token by CREATE2, so its address is known before the launch is sent.
- From HookrMarketCoordinatorV5: linked library
HookrMarketCoordinatorInitialBuyLibV4Live Hookr contract
0xE12f58f1662293B8b4fF3f0227384bD9D2a6e382
Linked library that validates and executes the creator's buy inside the launch transaction.
- From HookrMarketCoordinatorV5: linked library
HookrNativeMechanicsCoordinatorLibV2Live Hookr contract
0xafC0a7656DF8B1aA5A173d04612a9661C4b9c7E2
The coordinator's admission library, run by delegatecall. It checks the module and the protocol share at launch and routes founding-position fees when they are collected.
- From HookrMarketCoordinatorV5: admission and founding fees
HookrReleaseCreate2FactoryV1Live Hookr contract
0xBc54e888C1A5B71744B035DfeC5611B4DC69ccDe
The deploy-time factory the default root was mined through, so its address carries the right hook flags. Only its authorized deployer can deploy through it.
- To HookrModularHookV6: mined the root address
HookrModularCorrectionLibV2Live Hookr contract
0xd2832D5F64C7116bE2c7B32D8c06d60023b479D8
Recapture library linked into the default root. The default root has no recapture lane, so nothing runs through it.
- From HookrModularHookV6: linked, never dispatched
HookrStatefulSettlementLibV1Live Hookr contract
0xde7Ae40c713D5C9EC2C8e2E28810C6C7Eb791706
Linked library the accounting kernel uses to settle module actions with the PoolManager.
- From HookrSwapAccountingKernelV3: linked library
The flows, step by step (5)
A swap on the default root
- A caller asks a router to swap on a Hookr pool key.
- The router unlocks the PoolManager and settles the swap inside that one unlock.
- The PoolManager calls beforeSwap on the root hook.
- The root reads the pool's frozen stack and checks whether the caller is the pool's trusted router or quoter.
- The root delegates to the accounting kernel, so all state stays in the root's own storage.
- The kernel calls the native block, checks what it returns against the pool's frozen caps and credits the claims.
- The PoolManager executes the swap at the overridden fee. Then afterSwap runs the same path for the take and the burn.
A swap on the recapture root
- The PoolManager calls beforeSwap on the recapture root.
- The root reads the executor the adapter is bound to.
- It asks WTH's executor whether a registered v3 pool for this pair is locked. A clean true refuses the swap, because the caller is inside that pool's callback.
- The same accounting kernel runs the five native Hook Blocks.
- The root dispatches one recapture through its linked library, before and after the swap. A failed recapture never blocks the swap.
- The library calls the pool's frozen recapture executor, the adapter.
- WTH trades the gap and pays every share in the same transaction. The creator gets 40%, a trader through the Hookr router 20%, the pool's LPs 20%, WTH 10% and Hookr 10%.
Opening a market
- A creator opens a new-token or existing-asset market on either root.
- The coordinator runs its admission library, which checks the module and the protocol share for this creator.
- On the new-token lane the token library deploys the token by CREATE2.
- The coordinator creates the pool's stack; the registry accepts stacks from no other address.
- The registry checks every module against the catalog.
- The coordinator initializes the pool and, on the new-token lane, mints the founding band.
- An optional creator buy runs in the same transaction.
Adding liquidity
- An LP mints or increases a position through Uniswap's PositionManager.
- An ERC-20 leg is pulled through Permit2, so the token is approved to Permit2 first.
- The PositionManager modifies liquidity on the PoolManager.
- A new-token pool refuses outside liquidity while its guard window is open. Removing liquidity is never blocked.
- Each opted-in add-on (surcharge, guard, burn, LP reward or pot) carves out the protocol share as a claim in the native block.
- The block's protocol recipient is the forwarder, fixed for good.
- Anyone can call collect, which claims the block's balance for one quote currency into the forwarder.
- The forwarder pays its owner-set target in the same call. If the target refuses, the funds rest on the forwarder until sweep.
Modular Roots and Their Stack
| Contract | What it is | Address |
|---|---|---|
HookrModularHookV6 | Default root hook, flags 0x28cc | 0xb3cA…e8cC |
Hookr | Arb recapture root: the default root plus the recapture lane | 0xb914…68CC |
Hookr | Superseded arb recapture root, fourth | 0xE542…a8cC |
HookrModularHookV6Wth | Superseded arb recapture root, third | 0xa999…28Cc |
HookrModularHookV6Wth | Superseded arb recapture root, first | 0xc7c5…68CC |
HookrStackRegistryV2 | Registry: sealed root profiles, one frozen stack per pool | 0x5b7f…5BA3 |
HookrModuleCatalogV1 | Catalog: admits module implementations | 0x6a35…19D1 |
Hookr | Coordinator: opens markets on both lanes | 0x53A1…2442 |
Hookr | Kernel: the callback bodies every root delegates to | 0x5305…E060 |
Hookr | The module that holds the five native Hook Blocks | 0xD700…F1ae |
HookrKernelRouterV3 | Trusted router: swaps that carry an authenticated payer | 0xf0E5…66E9 |
HookrKernelQuoterV1 | Trusted quoter: simulates a swap and reverts with the result | 0x5Ba8…B1B5 |
Hookr | Pinned protocol fee recipient with one owner-settable target | 0x9BB7…Af6f |
Hookr | Adapter bound once to What The Hook's executor | 0x28AF…D806 |
| What The Hook's executor | Trades each arb recapture and pays the legs | 0xc356…5803 |
The libraries linked into them, and the factory the default root was mined through:
| Contract | Linked into | Address |
|---|---|---|
Hookr | the coordinator | 0xafC0…c7E2 |
Hookr | the coordinator | 0xE12f…e382 |
Hookr | the coordinator | 0x7A28…9399 |
Hookr | the coordinator | 0x8C2E…521d |
Hookr | the kernel | 0xde7A…1706 |
Hookr | the default root | 0xd283…79D8 |
Hookr | the arb recapture root, source not verified | 0x1199…F99C |
Hookr | the factory the default root was mined through | 0xBc54…ccDe |
The deployer and owner of the registry, catalog, coordinator and forwarder is 0xF4Ab…B3eE. The forwarder's target was the same address when read on 2026-10-17; rotating it is the one payout change that touches no pinned address.
Launch Generations
| Generation | Launch paths | Launcher | Hook | Router | Deployed at block |
|---|---|---|---|---|---|
| 3 (Legacy) | stepped bonding curve | 0xaAed…d6C3 | 0xd000…a0CC | 0x3f6E…f21b | 27,763,919 |
| 4 (Legacy) | bonding curve, instant pool | 0x5Ce7…6e3B | 0xa0F2…28cC | 0x98F3…4fC5 | 33,485,079 |
| 5 (V1) | instant pool, auction | 0xa043…e660 | 0xe7c3…E8CC | 0x644a…6ca0 | 41,850,162 |
Generation 5 also carries a flat-fee buyback burner at 0x8Cee…FD89. Every generation sits on the same Uniswap v4 PoolManager, 0x8366…0951.
Registry Identifiers
The values registerKernel, registerModule, registerIntegration and sealRootProfile returned for the default root, which an integrator reads back rather than recomputing.
| Identifier | Value |
|---|---|
| Kernel id, default root | 0x1be0c118b1c6520d97de31ee9f0c33069f0e715ffcdcb16c87a343752bb5be14 |
| Kernel id, arb recapture root | 0xd8b6c165b82efc3b7498081f071ea4f2476e2fb9c61b2e614aba2a016f94555a |
| Module id | 0x961091565aaf4eaa4996296fda9772957e61d18d90b965111a59ded2bbb5e774 |
| Router integration id | 0xc9ddc18b51d83cdafa6da985c67b4db41895dcde37b719597bdda9db6396c455 |
| Quoter integration id | 0x6e5a5b6aa6d141ccaf3b9f25f60e87919fc7a724cc845d146581eb49c079a9a9 |
| Root profile manifest hash | 0x74f018eb9afa65dcf38eb682397a21937b8568572c9884faa12465833bbcf213 |
Which Pools Use Which Contracts
| Contracts | Pools |
|---|---|
| The default root | Every modular pool that did not ask for arb recapture. Its five native Hook Blocks run on any quote currency. It is on Uniswap's routing allowlist, approved in September 2026. |
| The arb recapture root | Pools opened with the five arb recapture fields set. It sealed on 2026-09-12, and its pools keep What The Hook's executor for good. Hookr's app no longer offers it. |
| The three superseded arb recapture roots | Six canary and rehearsal pools Hookr opened between 2026-09-10 and 2026-09-12, all HOOKR/ETH, FRONG/ETH or TEST/ETH. None was paused or withdrawn. |
| Launch generations 3, 4 and 5 | Every token launched through them, with the pool, hook and router that launch used. |
A root is superseded, not retired: nothing on chain marks one, and nothing stops a swap on a pool that names one. An indexer or router that filters on one root address misses the rest.
Contract Notes
Each note is what a builder still needs from the contract's full reference page. The ABIs are at the end of this page. In the contracts, arb recapture is still named a correction (correctionExecutor, CorrectionAttemptSucceeded); these notes say arb recapture.
Hookr Modular Hook V6
HookrModularHookV6 is the default root hook, mined so address & 0x3fff == 0x28cc, and the hooks field of every pool opened on it. It inherits HookrSwapKernelV3, handles beforeSwap and afterSwap itself and forwards every other selector to the accounting kernel by DELEGATECALL, so all state stays in the root. Four immutables pin its wiring (PoolManager, registry, coordinator, accounting kernel), and the kernel's code hash is re-checked before every delegation (AccountingKernelCodeChanged).
beforeSwapresolves the pool's frozen stack, requires thePoolKeyto match it, authenticates the caller against the pool's trusted router and quoter at their registered code hashes, and decodeshookDataonly for a trusted caller. An untrusted caller must send emptyhookData.- The root's profile names no arb recapture executor, so every pool on it freezes all-zero arb recapture fields and the lane never runs. The three recapture events are declared and never emitted here.
- Errors a caller meets:
InvalidStack,InvalidHookData,InvalidPayload,ReentrantCallback,DelegateCallFailed,AccountingKernelCodeChanged,InvalidWiring.
Hookr Modular Hook V6 WTH V5
HookrModularHookV6WthV5 is the arb recapture root. It runs the same module, over the same coordinator, registry and accounting kernel as the default root, and adds one lane step and one refusal. Its kernel id is 0xd8b6c165b82efc3b7498081f071ea4f2476e2fb9c61b2e614aba2a016f94555a, its profile read sealed on 2026-09-12 with the same module set, and the profile names the adapter as its only executor. It is mined to the same 0x28cc, because the lane needs no hook permission of its own.
- The lane runs in both phases:
beforeSwapafter the fee override and before the swap is priced, andafterSwapafter it settles. One swap can emit two arb recaptures, told apart by the indexedphase(1 for before, 2 for after). - The before-swap half only runs for a swap that carries the canonical full-fill price limit for its direction. A router that sets its own
sqrtPriceLimitX96takes the after-swap half alone, with no event for the half that did not run. - The call to the executor sits in a try and catch. A revert there is a
CorrectionAttemptFailedevent and the swap settles. This is the one fail-open path in the stack. - The lane has no gas gate. A swap sent with a limit too small for the arb recapture runs out of gas and reverts instead of skipping.
- Before the lane runs,
beforeSwapreadswthExecutor()from the adapter (30,000 gas) and asks the executor's MEV view,checkV3PoolsMev(0x6075521e) and thencheckV3PoolsMEV(0x85c30352), at 200,000 gas each. A clean true means the caller is inside a registered v3 pool's callback, closing an arbitrage, and the swap revertsMevCallbackRefused(address, address)(0xcbd9472b). A router that aggregates across venues in one transaction can meet it; drop that route. correctionMaxVolumeBpsandcorrectionMinProfitQuoteare frozen on each pool and not enforced: the executor's interface takes neither. Live pools carry5000and1.- The root and its swap kernel are verified on Blockscout as full matches, and the root's verified source carries the library it links; the library's own address is not verified, none of them is in the public contracts repository, and the executor's source is not verified.
Hookr Swap Accounting Kernel V3
HookrSwapAccountingKernelV3 holds the callback bodies the roots delegate to, so it never runs in its own storage. At 23,965 bytes it sits 611 short of the EIP-170 limit, which is why the root and the kernel are two contracts.
beforeInitializeaccepts a pool only from the coordinator, once, and only when thePoolKeymatches the frozen stack.beforeAddLiquidityruns the module's liquidity check. The other callbacks are unset and revertHookNotCalled.- It walks the pool's frozen modules, enforces the per-pool caps, computes the
BeforeSwapDeltaand theafterSwapdelta, and credits claims.syncBaseFee(key)is permissionless and pushes only the pool's own frozen base fee into the PoolManager's dynamic-fee cache. - A trusted caller (the pool's registered router or quoter, at its registered code hash) sends an authenticated
hookDataenvelope bound to the pool'sstackHash. Any other caller sends emptyhookDataor the swap revertsUntrustedHookData. This is why a Universal Router swap works but skips the pot leg. - Events, emitted from the root's address:
MarketInitialized,ModuleFeeAccrued,ModuleFeeSkipped(worth an alert),StatefulModuleActionandHookFee.
Hookr Native Mechanics Block V2
HookrNativeMechanicsBlockV2 is a stateful module and holds all five native Hook Blocks (Hookr dynamic fees, Anti-Snipe, Auto Burn, LP Rewards, Nth-buy Pot) plus the creator royalty. The kernel calls it inside the swap, and the kernel alone executes the PoolManager actions it returns.
- Constants:
MAX_TOTAL_FEE_PIPS500,000 (50%),MAX_PROTOCOL_SHARE_BPS5,000 (50%),MIN_POT_BUY_WEI0.001 ether,MAX_QUOTE_DECIMALS36,MAX_GUARD_BLOCKS100,000, module version 2. - It owns the claim ledger
claimable[quote][account]andtotalClaimLiability[quote], backed by ERC-6909 claims on the PoolManager.claim(quote)andclaimTo(quote, to)pay out and revertNothingToClaimon a zero balance orClaimTransferFailedunless the recipient's balance rises by exactly the debit.accountingInvariant(quote)checks that the module's balance covers the liability. - Per-pool counters:
potWei,potBuyCount,guardLpEarnedQuote,totalHookFeesWei,totalBurnedTokens,totalLpDonatedWei,totalPotPaidWei,totalProtocolShareWeiandprotocolShareByStream. - Events:
HookFeesAccrued,ProtocolShareAccrued,LpRewardsDonated,JackpotHit,AutoBurn,Claimed. - Errors:
InvalidConfig,NotKernel,StatefulKernelRequired,MaxBuyExceeded,ExactOutputBlockedDuringGuard,ExternalLiquidityBlockedDuringGuard,PartialFillUnsupportedWithInputCuts,ProtocolShareNotEnforced,InvalidPotRecipient,NothingToClaim,ClaimTransferFailed.
Hookr Module Catalog V1
HookrModuleCatalogV1 admits module implementations. A registration pins the implementation address, its runtime code hash, its config schema hash, its required hook flags, its phase mask, its exclusive group, its execution mode and four structural ceilings, and none of it can be edited. The owner can retire a module for new pools; open pools keep theirs.
setCanonicalStatefulModuleOncebinds the one stateful module the catalog admits, once. One catalog serves exactly one stateful module, so a new module needs a new catalog, a new registry and a new root.- The native module's registration fixes
requiredHookFlags10444 (0x28cc), all phases,STATEFUL_V1,maxLpFeeSurchargePips500,000,maxSpecifiedQuoteTakeBps4,000,maxUnspecifiedQuoteTakeBps2,500,maxSubjectTakeBps1,000 andcallbackGasLimit2,000,000. Every pool's own derived caps must fit inside them. - Retiring is
retireModule; ownership moves in two steps (proposeOwner,acceptOwnership).
Hookr Stack Registry V2
HookrStackRegistryV2 admits pools. It registers kernels (root implementations) and integrations (a router, a quoter, an arb recapture executor), seals a root profile once per kernel, and freezes one StackCore per PoolId. It keeps the per-market kernel-instance lane switched off, so every pool runs on a sealed profile.
setCoordinatorOncebinds the one coordinator allowed to callcreateStack.sealRootProfilenames the allowed modules and the router, quoter and executor integration ids, and it is irreversible. Pass the idsregisterIntegrationreturned, not the integration kinds.stack(poolId)is the read an interface wants: kernel, subject, quote, stack hash, the trusted router and quoter with their code hashes, and the frozenStackLimits.moduleAt(poolId, index)returns a module snapshot and its config,frozenModuleConfigHash(poolId, module)the hash re-derived on every callback, androotProfile(kernelId)the sealed profile.StackLimitsholdsbaseLpFeePips,maxLpFeePips, the three take caps,maxTotalModuleGas,trustedRouter,trustedQuoterand the five arb recapture fields:correctionExecutor,correctionCreator,correctionMaxVolumeBps,correctionMinProfitQuoteandcorrectionFeePolicyId.- Events:
StackConfigured(poolId, stackHash, kernelId, subject, quote, moduleCount)andStackInitializedmark a pool's birth. Registrations, retirements andRootProfileSealedare administrative and never reach an open pool. - The two reverts a launcher meets in practice are
ModuleCapsExceedStackLimits(the config asks for more than the pool's own limits) andModuleConfigCapsExceedSnapshot(more than the catalog registration).
Hookr Market Coordinator V5
HookrMarketCoordinatorV5 is the only address the registry accepts stacks from. It opens both lanes, holds the founding position for the new-token lane, resolves the protocol share for the creator and exposes the treasury the admission library checks against.
openNewTokenMarket(args, intentId, expectedToken)deploys the token by CREATE2, opens the pool, places the whole 1,000,000,000 supply in one coordinator-held band and optionally runs the creator buy.openExistingTokenMarket(args)initializes a fresh pool for a token that already exists, with no liquidity, no band and no creator buy.collectLpFees(poolId)is permissionless and pays the founding position's fees to the market'slpFeeRecipient.- Constants:
SUPPLY1,000,000,000e18,MAX_INITIAL_BUY_SUBJECT5% of supply,MAX_PROTOCOL_SHARE_BPS5,000,MAX_MODULE_DATA_LENGTH3,904,BAND_TICKS207,000,DYNAMIC_FEE_FLAG0x800000.defaultProtocolShareBpsstarts at 2,000. - The owner can pause opening (
marketOpeningPaused; the owner can always open), set the default share and set or clear a per-creator tier, all at or below the ceiling. The deployed coordinator read unpaused on 2026-10-17. - Events:
MarketCreated,ProtocolShareResolved,CreatorBuyExecuted,LpFeesCollected,CreatorTierSet,CreatorTierCleared,DefaultProtocolShareBpsSet,MarketOpeningPauseSet. - Reverts:
NotCreator,InvalidMarketArgs,IntentAlreadyUsed,TokenSaltAlreadyUsed,InvalidPayment,UnexpectedToken,TaxedTransfer,InitialBuyAboveCap,NoFoundingPosition,ProtocolShareTierMismatch.
Hookr Native Mechanics Coordinator Lib V2
HookrNativeMechanicsCoordinatorLibV2 is the coordinator's admission and fee-routing library, run by DELEGATECALL. validateOrigin finds the one canonical native module in a market's selections and proves every admission fact: module version exactly 2 and its config schema hash, execution mode and phase mask, an on-chain code hash equal to the catalog's, validateProtocolShare(config), the module's recipient equal to coordinator.treasuryBeneficiary(), the config's share equal to coordinator.protocolShareBps(creator), and a guard window only on the new-token lane. Every external read except the coordinator's own treasuryBeneficiary() is a gas-bounded staticcall (150,000 gas) that needs a full 32-byte answer, so a target that reverts, runs long or returns short fails admission. It also routes founding-position fees at collect time: 100% of both currencies to lpFeeRecipient, with no withholding.
Hookr Kernel Router V3
HookrKernelRouterV3 is the trusted router. It settles swaps against a frozen stack and carries an authenticated payer and recipient in hookData, which is what lets the hook credit the pot to a wallet instead of a router contract.
exactInput(params, moduleData)andexactOutput(params, moduleData). A native input needsmsg.valueequal to the input (amountInMaximumonexactOutput) and refunds the rest; an ERC-20 input needsmsg.value == 0and an allowance to this router.moduleDatais at most 3,904 bytes.- On any pool with input cuts, set
sqrtPriceLimitX96to the canonical full-fill limit (MIN_SQRT_PRICE_LIMITforzeroForOne,MAX_SQRT_PRICE_LIMITotherwise) or the swap revertsPartialFillUnsupportedWithInputCuts. - Integration version 3; the creator buy requires it. Constructed after
setCoordinatorOnce, with the coordinator's code hash pinned. EmitsSwapExecuted(poolId, stackHash, payer)for swaps routed through it; a Universal Router swap emits nothing here. - Errors include
InputDebitMismatchandSettlementMismatchfor a fee-on-transfer quote.
Hookr Kernel Quoter V1
HookrKernelQuoterV1 is the trusted quoter. quote(params, moduleData) unlocks the PoolManager, runs the same hook callbacks, the same module walk and the same fee override a real swap would, and reverts with the result, so it is not a view function: call it with eth_call. Being the pool's registered quoter also means the pot leg is simulated. A quote from Uniswap's V4Quoter sees the same pool without the pot cut, which is what a Universal Router swap pays. Integration version 1.
Hookr Treasury Forwarder V1
HookrTreasuryForwarderV1 is the one address every market pins as its protocol recipient, with a rotatable payout behind it. It holds no protocol authority: it cannot open markets, change tiers, pause anything or touch a pool.
collect(quote)is permissionless: it claims the module's accrual into the forwarder and forwards it totargetin the same call, and returns zero when nothing is owed. If the target rejects the payment,ForwardDeferredis emitted and the funds rest on the forwarder.sweep(token)then pushes them, and reverts if the target still refuses.- Owner functions:
setTarget,setTargetAndCollect(newTarget, quotes)(rotate and drain atomically, best sent through a private relay when the old target is believed compromised) andsetNativeBlock. The target cannot be the forwarder, the bound module or the PoolManager. - Events:
Collected,ForwardDeferred,Swept,TargetSet,NativeBlockSet, and the ownership events.
Hookr Market Coordinator Token Deployer V3
HookrMarketCoordinatorTokenDeployerV3 is a linked library that deploys a new-token market's fixed-supply HookrTokenV61 with CREATE2 and derives its founding band. It runs by DELEGATECALL, so the token is deployed from the coordinator's address and the address depends on the coordinator, not the library. initCodeHash lets anyone predict the address before the launch; the coordinator's previewNewTokenAddress(args, intentId) returns it directly.
Hookr Market Coordinator Initial Buy Lib V4
HookrMarketCoordinatorInitialBuyLibV4 validates and executes the creator's same-transaction buy. A zero quoteAmountIn disables the buy. validate reverts InvalidInitialBuy for an amount above int128 max, a zero minimum, a passed deadline, moduleData over 3,904 bytes, or a router that is not the pool's integration version 3 with the coordinator's current code hash. executeAndEmit calls the router's exactInputInitialBuy, emits CreatorBuyExecuted and reverts InitialBuyInputMismatch unless the buy consumes exactly the requested quote. The buy is available on every quote currency.
Hookr WTH Executor Adapter V1
HookrWthExecutorAdapterV1 stands between a recapture root and a partner's executor. It names one executor, once, and holds nothing. Four adapters at this name are deployed, one per recapture root, each bound to the executor What The Hook ran at the time; resolve a root's adapter from the registry (rootProfile(kernelId).correctionExecutorIntegrationId, then integration(id).implementation) rather than from the name.
setExecutorOnce(executor)(0x76e5397a) is owner-only and already spent: a second call revertsExecutorAlreadySet()(0xa1002dd1), and any other caller revertsNotOwner()(0x30cd7471) first.wthExecutor()(0x477a9d7c) returns0xc356…5803on the current adapter.- Views:
feePolicyId()(0xc893dd97),routeAdmissionOpen()(0xc93b3f0f, true),executionBlockNumber(),integrationKind(),integrationFamilyId()andintegrationVersion()(1).executionBlockNumber()returns this chain's own height through the ArbSys precompile, notblock.number. - It forwards three arguments to the executor (the triggering pool, the rebate recipient and the profit split). The swap's direction and size, the pool's frozen volume and profit fields and the plan are dropped at this boundary.
- The adapter and the root are verified on Blockscout as full matches; the executor is not.
How Pools on the Modular Roots Work
The notes below cover what the contract notes do not: how the pieces behave together.
Fee Model
The protocol never takes a slice of the base LP fee. It takes a share from inside each opted-in add-on, 20% at the deployed default and never more than 50%, and the trader pays exactly the amounts the pool was configured with. A pool that opts into nothing but a base fee generates no protocol revenue.
| Config field | Stream | Who pays it | Where it goes at the 20% default |
|---|---|---|---|
baseFeePips | Base LP fee | every swap | 100% to in-range LPs |
maxFeePips above base | Hookr dynamic fee surcharge | swaps that move price | 80% to in-range LPs, 20% to the protocol |
snipeTaxPips | Guard snipe tax | buys during the guard window | 80% to the creator, 20% to the protocol |
lpBps | LP Rewards cut | exact-input buys | 80% donated to in-range LPs, 20% to the protocol |
potBps | Nth-buy Pot cut | exact-input buys through a trusted caller | 80% into the pot, 20% to the protocol |
burnBps | Auto Burn | exact-input buys | 80% of the withheld subject burned, 20% taken in quote by the protocol |
royaltyBps | Creator royalty | share of the LP Rewards and pot cuts | to royaltyTo, computed after the protocol's share |
The share s is resolved once when the market opens and frozen into the pool's config; in general the split is 1-s to the LP side and s to the protocol. The trader's total fee rate is base + dyn + snipe in every quadrant, so the split moves value between LPs and the protocol and never adds to the price.
- Exact-input buy. Every leg lands on the quote input in
beforeSwap. The protocol's pips leg ispDyn + pSnipe + burnP * 100withpDyn = dyn * s / 10_000, and the cutB = x * (lpBps + activePot) / 10_000is split by post-share weights into LP donation, pot and royalty, with the protocol taking the residual. - Exact-output buy. No LP Rewards, pot or burn leg. The dynamic fee uses its ceiling
maxFeePips - base, because the input is unknown when the fee is set, andafterSwaptakes the protocol's slice in quote. - Exact-input sell. The dynamic fee applies and the protocol's slice is taken from the quote output in
afterSwap. The value is carried frombeforeSwaptoafterSwapin transient storage. - Exact-output sell. Exempt. The unspecified currency is the subject, which the protocol never holds, so LPs keep the whole surcharge.
- Burn slice. The protocol never holds subject tokens, so its slice of a burn is taken in quote:
burnP = burnBps * s / 10_000, charged as that many bps of the gross quote input. - Rounding. Every slice floors the protocol's rate, so it is never above
s. The protocol absorbs the shared rounding dust. - Partial fills. A pool with a specified-leg take (a dynamic fee, guard, burn, LP reward, or a pot through a trusted caller) requires the canonical full-fill price limit and reverts
PartialFillUnsupportedWithInputCutsotherwise. A pool with only a base fee accepts partial fills. - Creator buy. At most 5% of the 1,000,000,000 supply measured after any burn (
InitialBuyAboveCap), on any quote. It pays the snipe tax and respects the per-block cap. An ERC-20 quote needs the creator's approval to the router first.
Guard Window
Anti-Snipe's launch window, available only on the new-token lane. Requesting it on an existing-asset market reverts GuardRequiresLockedFoundingPosition. It is open while block.number < guardEndBlock, and four limits apply:
| Limit | Behavior |
|---|---|
| Per-block buy cap | maxBuyQuoteAmount bounds the quote spent on buys in one block for the pool, not per address (MaxBuyExceeded). Zero disables the cap. |
| Snipe tax | snipeTaxPips is added to the LP fee on buys, split 80% creator and 20% protocol at the default share. |
| Exact-output buys | Blocked (Exact). |
| Outside liquidity | Blocked for any sender but the coordinator (External). |
The founding position is the only liquidity while the window is open, so the creator's lpFeeRecipient receives the LP part of every guard-window fee, and a creator who buys their own launch recovers most of the tax; the 5% creator-buy cap bounds it. Bounds: guardEndBlock after the opening block and at most 100,000 blocks ahead, baseFeePips + snipeTaxPips <= 500_000, lockedLiquidityProvider the coordinator while the guard is on, and zero fields when it is off. Every limit stops at guardEndBlock. guardLpEarnedQuote(poolId) is an informational counter that separates guard-window earnings from later ones.
Native Mechanics
The five blocks live in one module because splitting them would change the fee rounding: one hook cut is computed across LP Rewards and the pot, the royalty is removed once, and the protocol absorbs the shared residue. Each block is switched off by setting its rate to zero.
- Hookr dynamic fees.
reserveInis the in-range reserve on the input side,ratio = min(amountIn * surgeSens * 1e6 / reserveIn, 1e6)andsurge = (maxFeePips - baseFeePips) * ratio / 1e6.surgeSensis capped at 10 and is zero exactly whenmaxFeePips == baseFeePips. Sells pay it too. - Auto Burn. On an exact-input buy,
burnBpsof the subject output is withheld and the pool's share is sent to0x000000000000000000000000000000000000dEaD. The subject must permit that transfer, or every buy reverts and the pool fails closed. Burning a tokenized stock or a stablecoin destroys real value. - LP Rewards.
lpBpsof the gross quote input is donated to in-range liquidity inside the same swap, so a position that leaves range stops receiving it at once. - Nth-buy Pot.
potBpsof the gross quote input fills the pot, and the buy that makes the counter divisible bypotEveryNBuystakes all of it. A buy counts only at or abovepotMinBuyWei(0.001 ETH natively, at least10 ** (decimals - 3)for an ERC-20 quote), and the counter advances at most once per pool per block. The pot needs an authenticated recipient, which only the registered router or quoter can supply; a swap from any other caller pays no pot cut and cannot win. The pot is not random:potBuyCountis public, so expect it to be raced. - Royalty.
royaltyBpsis a share of the LP Rewards and pot cuts, computed after the protocol's share and credited toroyaltyTo. If it is zero,royaltyTomust be zero.
Pool Lifecycle
- Configure. The caller assembles
MarketParams(quote, tick spacing, opening price,kernelId, module selections,StackLimits) with exactly one native module config.limits.baseLpFeePipsmust equal the config'sbaseFeePips. - Admit. The coordinator calls the admission library, which proves the facts listed under the coordinator library above.
- Freeze.
createStackderives per-pool caps, checks them against the catalog registration and stores theStackCore. From here nothing in the registry, catalog or coordinator can change the pool. - Initialize. The coordinator initializes the pool with
fee = 0x800000and the root ashooks.beforeInitializeaccepts only the coordinator, only once, only against the frozen stack, and emitsMarketInitialized. - Liquidity. The new-token lane places the whole supply in one token-only band whose quote-side edge sits on the opening price, owned by the coordinator with no removal path. The existing-asset lane adds nothing; LPs use the ordinary v4
PositionManager. - Creator buy. New-token lane only, once, after the band is seeded.
- Trade and collect. Founding-position fees wait for
collectLpFees, protocol shares forcollect(quote)on the forwarder, and pot wins and royalties forclaimon the module.
A pool has no expiry, migration or shutdown. The two lanes:
| New-token lane | Existing-asset lane | |
|---|---|---|
| Token | deployed by the coordinator, fixed supply | any deployed token |
| Founding position | created, never removable | none; lpFeeRecipient must be zero |
| Guard window | allowed, up to 100,000 blocks | rejected |
| Creator buy | allowed, at most 5% of supply | rejected |
| Who may open | the creator, expectedCreator == msg.sender | anyone while opening is not paused |
openExistingTokenMarket performs no ownership check on the subject, and market.creator records only who opened the pool. Never present it as token ownership or as the token team's endorsement.
Protocol Share and Treasury
protocolShareBps(creator) returns the creator's tier if the owner set one and defaultProtocolShareBps otherwise. Both are bounded by 5,000 on write (ProtocolShareAboveCeiling). Tiers are keyed on the address that calls the open function, so a tier granted to a launcher contract applies to every market it opens, and a tier on a wallet covers only that wallet.
Admission reverts ProtocolShareTierMismatch(expected, actual) unless the config carries exactly that value, and the hook re-derives the config hash on every swap and liquidity add, so a later change to the default or to a tier cannot reach an open pool. The module's protocolRecipient is immutable and the coordinator's treasury is constructor-only, and they must be equal. The forwarder is the pinned address, so rotating the payout destination never de-admits a market.
The share is never taken from the base LP fee, never in subject tokens and never on top of what the trader agreed to pay, and it is zero on a pool that runs on its base fee alone.
Immutability and Ownership
Frozen per pool at creation: the base LP fee, the dynamic fee ceiling and sensitivity, the guard window and its parameters, the burn, LP reward, pot and royalty rates, the royalty recipient, the pot floor and interval, the protocol share and recipient, and the trusted router and quoter. The founding-position fee recipient is written once into the coordinator's market record with no setter.
Four owner slots exist, each under a two-step transfer (proposeOwner, acceptOwnership): the coordinator, registry, catalog and forwarder. One address holds all four.
| Contract | Owner function | Effect | Reaches a live pool? |
|---|---|---|---|
| Coordinator | set | While paused only the owner may open markets | No |
| Coordinator | set, setCreatorTier, clearCreatorTier | Changes the share future markets resolve to | No |
| Registry | registerIntegration, retireIntegration, registerKernel, retireKernel | Admits or retires a router, quoter, executor or root for future stacks | No |
| Registry | setCoordinatorOnce, sealRootProfile | One-shot binds | No |
| Catalog | registerModule, retireModule, set | Admits or retires a module for future stacks | No |
| Forwarder | setTarget, setTargetAndCollect, setNativeBlock | Rotates where collected shares are paid | No |
Nobody can change a pool that is open, pause, freeze or blacklist a pool, a swap or a trader, remove the founding position, take a share of the base LP fee, raise MAX_PROTOCOL_SHARE_BPS (a module constant with no setter), upgrade anything (there are no proxies), or move funds owed to somebody else. Anyone can call openExistingTokenMarket (while opening is not paused), collectLpFees, collect, sweep, claim and claimTo; none of them lets the caller redirect value.
RWA and ERC20 Quotes
Every Hook Block works on every quote currency, and the claim ledger is keyed by quote, so an ETH-quoted and a USDG-quoted pool share the module without their balances crossing.
- Payment. Opening a market with an ERC-20 quote needs
msg.value == 0(InvalidPayment). The coordinator pulls no quote itself; only the creator buy moves quote at launch, and the router pulls it from the creator's approval. - Pot floor. An ERC-20 quote needs
potMinBuyWei >= 10 ** (decimals - 3), checked with a boundeddecimals()read that rejects fewer than 3 or more than 36 decimals. USDG has six, so its floor is 1,000 base units. - Transfers. The coordinator and the module compare balances before and after every ERC-20 movement (
TaxedTransfer,ClaimTransferFailed), so a fee-on-transfer or rebasing quote does not work. - Robinhood tokenized stocks. A stock token on chain 4663 is a
BeaconProxyover a sharedStockimplementation. Pause and blocklist state lives in one chain-wide contract at0xe10b6f6B275de231345c20D14Ab812db62151b00, so a pause halts every pool holding the token, and a blocked hook or router stops its pools. A privileged role can burn a holder's balance (adminBurn), and the ERC-8056uiMultiplierchanges what an interface shows, not the pool math.
Combinations that need a warning in any interface: Auto Burn with a stock, a stablecoin or any subject the creator does not control; any pool holding a stock on either side; pot or LP rewards with a stock as the quote. The contract rejects only a subject equal to its quote and a guard window on the existing-asset lane.
Config Schema and Limits
One HookrNativeMechanicsBlockV2.Config is frozen per pool: twenty fields, 640 ABI-encoded bytes, rejected if it does not re-encode to itself. In order: poolId, kernel, subject, quote, lockedLiquidityProvider, guardEndBlock (uint40), baseFeePips, maxFeePips, snipeTaxPips (uint24), surgeSens, burnBps, lpBps, potBps, royaltyBps (uint16), potEveryNBuys (uint32), maxBuyQuoteAmount, potMinBuyWei (uint96), royaltyTo, protocolRecipient and protocolShareBps (uint24). protocolShareBps is basis points of each add-on, never a flat rate on the quote leg, which is why the schema hash is part of admission.
| Check | Bound |
|---|---|
| Fees | baseFeePips <= maxFeePips <= 500_000 and baseFeePips + snipeTaxPips <= 500_000 |
| Dynamic fee | surgeSens <= 10, zero exactly when maxFeePips == baseFeePips |
| Cuts | burnBps + lpBps + potBps <= 1_000 and royaltyBps <= 1_000 |
| Royalty | royaltyBps != 0 needs royaltyTo != 0 and lpBps + potBps != 0; royaltyBps == 0 needs royaltyTo == 0 |
| Pot | potBps != 0 needs 2 <= potEveryNBuys <= 100_000 and a pot floor; potBps == 0 needs both zero |
| Guard | guardEndBlock == 0 needs locked, snipeTaxPips and maxBuyQuoteAmount zero; otherwise locked is the coordinator |
| Share | protocolShareBps <= 5_000 and equal to coordinator.protocol |
| Quote | subject != quote; tick spacing positive and at most type(int16).max |
Other constants: DYNAMIC_FEE_FLAG 0x800000 (the required PoolKey.fee), MAX_MODULE_DATA_LENGTH 3,904 and MAX_CONFIG_BYTES 4,096.
Hook Block Combinations
On the new-token lane every Hook Block is allowed: the fee is the launcher's standard 0.30% and the guard, creator buy, burn, LP Rewards, pot and royalty all apply. On the existing-asset lane the creator picks the base fee, and the guard and the creator buy are rejected (see the lane table under Pool Lifecycle). Every block runs on every quote currency, because the module keys its cut ledger by quote. What differs from pair to pair is the warning it earns:
| Subject \ Quote | ETH | HOOKR | USDG | Tokenized stock | Other ERC-20 |
|---|---|---|---|---|---|
| A new token | OK | OK | OK | OK, warn pause registry | OK |
| An existing ERC-20 | OK, warn Auto Burn if the token is not yours | same | same | same, plus warn pause registry | same |
| HOOKR | OK, burn deflates HOOKR | Block, subject equals quote | OK | OK, warn pause registry | OK |
| A tokenized stock | OK, warn Auto Burn destroys shares and warn pause registry | same | same | OK, both warnings | same |
| USDG or another stablecoin | OK, warn Auto Burn destroys dollars | same | Block, subject equals quote | OK, warn pause registry | same |
The recommended settings were: for existing pairs, the 0.30% tier with dynamic fees on and no burn on a stock or stablecoin subject; for new tokens, the guard on with a 25% to 45% snipe tax and a per-block max buy, burn 2% to 3%, LP Rewards 1% to 2%, royalty 5% to 10% of the cuts and a creator buy at or under 5% of supply; for stock or stable pairs, the base fee plus the dynamic fee only. Only swaps through the Hookr router pay into the pot.
Hook Permissions
Every modular root answers REQUIRED_FLAGS 10444, 0x28cc: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeSwapReturnDelta and afterSwapReturnDelta. Removing liquidity is never hooked, so no Hook Block can trap an LP position. The arb recapture lane needs no flag of its own, so the recapture roots carry the same word. The registry re-derives a root's flags from its address and refuses a registration whose declared flags disagree (InvalidHookFlags, InvalidHookFlagDependencies).
beforeInitializeaccepts initialization only from the coordinator, once perPoolId, against the frozen stack.beforeAddLiquidityruns the guard-window lock.beforeSwapsets the LP fee override and takes the quote-side cuts as aBeforeSwapDelta;afterSwaptakes the unspecified-leg protocol share and the burn.Hooks.Permissionsspells the delta flagsbeforeSwapReturnDeltaandafterSwapReturnDelta; Uniswap's hooklist schema spells thembeforeSwapReturnsDeltaandafterSwapReturnsDelta.- Every pool sets
PoolKey.fee = 0x800000, the dynamic-fee flag, sofeesays nothing about the real cost. Read the frozen base fee withregistry.stack(poolId).limits.baseLpFeePips, or push it into the PoolManager's cache withsyncBaseFee(key)at the root's address.MAX_TOTAL_FEE_PIPSis 500,000 (50%).
Allowlisting is per hook address and carries over to nothing.
Events for Indexers
Six value streams run through a pool on the default root and must not be summed. A recapture pool has a seventh.
| Stream | Source |
|---|---|
| Base LP fee | PoolManager fee growth |
| Hookr dynamic fee and snipe surcharge, LP part | PoolManager fee growth, indistinguishable from the base fee on chain |
| Protocol share | ProtocolShareAccrued(poolId, stream, amount), with stream one of Surcharge, Guard, LpReward, Pot, Burn |
| LP Rewards donation | LpRewardsDonated, which also lands as fee growth |
| Pot | HookFeesAccrued for inflow, JackpotHit for the payout; balance in potWei(poolId) |
| Burn | AutoBurn, in subject units |
| Arb recapture profit | Correction, gross, on a recapture root only |
Counters are cheaper than replaying events: potWei, potBuyCount, guardLpEarnedQuote, totalHookFeesWei, totalBurnedTokens, totalLpDonatedWei, totalPotPaidWei, totalProtocolShareWei and protocolShareByStream. Protocol revenue received is Collected plus Swept on the forwarder, not ProtocolShareAccrued.
- Coordinator:
MarketCreated(the origin tells the two lanes apart),ProtocolShareResolved,CreatorBuyExecuted,LpFeesCollectedand the owner events. - Accounting kernel, emitted from the root's address:
MarketInitialized,ModuleFeeAccrued,ModuleFeeSkipped,StatefulModuleAction,HookFee. - Router:
SwapExecuted, only for swaps routed through it. - Registry:
StackConfiguredandStackInitialized. - Recapture roots, from the root a pool names:
CorrectionAttemptSucceeded(0x062af9b1a8b2f073cef7ee04bab7c582cf3aca90852ec46c3481a07b715531dd),CorrectionAttemptFailed(0xcbe0d1d2d8c523a1845d8ff0549db2778776b3a7e4932ac1dcc30354199fea99) andCorrectionAttemptSkipped(0xddf7f687f86d810a6d78924e7e77035c06caa1feb864c0f674fb55bb893c3425). Only the first carries value. De-duplicate by the indexedphase, never by transaction hash, which halves the measurement.
Do not parse a pool's configuration out of events. Read registry.stack(poolId) and registry.moduleAt(poolId, 0), then decode the config; it is frozen, so one read at any block holds for the pool's life.
Claims and Fee Collection
Four balances build up around a pool, and each is collected differently. A pool on a recapture root adds a fifth stream, paid inside the swap.
| Balance | How it is collected | Who can call |
|---|---|---|
| Founding-position fees | coordinator.collect pays both currencies to lpFeeRecipient; reverts NoFoundingPosition on an existing-asset market | anyone |
| Pot wins and royalties | claimable(quote, account) to read, claim(quote) or claimTo(quote, to) to pay; one balance per quote across every pool, zero reverts NothingToClaim | the account owed |
| Protocol share | forwarder.collect(quote), then sweep(token) if the target refused | anyone |
| LP Rewards donations | nothing to collect; they are fee growth, collected with a position's fees through PositionManager | the position owner |
| Arb recapture payouts | paid by the executor inside the swap that realised the profit; the LP part arrives as fee growth | nothing to claim |
Claimed covers a pot win, a royalty and the forwarder's own collection; tell them apart by account. Keep accrued and collected as separate numbers: accrued is what the pool owes, collected is what reached an address. A read that fails is not a zero balance.
Swapping, Quoting and Liquidity
- Universal Router. Send empty
hookData. Every Hook Block applies except the pot leg, which needs the authenticated recipient only the registered router or quoter can supply. An ERC-20 input is pulled through Permit2. - Hookr router.
exactInputandexactOutputcarry the payer and recipient. An ERC-20 input needs a plain approval to the router. - Price limit. A pool with any input cut on an exact-input buy needs the canonical full-fill limit. Aggregators used to setting their own limit must use
amountOutMinimuminstead. - Quote. The registered quoter runs the real callbacks and reverts with the result.
- Liquidity. Mint through the ordinary
PositionManagerwith emptyhookData(an oversized payload revertsHookDataTooLarge). A new-token pool refuses any sender but the coordinator while its guard window is open. Removing liquidity is never hooked. A narrow position earns more of the dynamic fee and leaves range faster.
Opening a Market
New-token market. Build the Config (see Config Schema and Limits), ask the coordinator for the token address with previewNewTokenAddress(args, intentId) so the config and PoolKey can name it, derive the key with poolKeyFor(subject, market) (it sorts the currencies, sets fee to 0x800000 and names the root), assemble MarketParams (subjectAmount equal to SUPPLY, quoteAmount zero, lpFeeRecipient neither zero nor the coordinator, sqrtPriceX96 exactly on a usable tick, the registered router and quoter as trustedRouter and trustedQuoter), add an optional creator buy, and send openNewTokenMarket{value: quoteAmountIn}(args, intentId, subject). msg.value equals the creator buy for a native quote and zero for an ERC-20 quote. In one transaction the coordinator deploys the token, freezes the stack, initializes the pool, seeds the band, runs the buy and emits MarketCreated, ProtocolShareResolved and CreatorBuyExecuted.
Existing-asset market. The creator chooses the base fee (500, 3000 and 10000 pips are the usual tiers), with baseLpFeePips plus the dynamic fee headroom plus the snipe tax inside 500,000. The guard must be off, so lockedLiquidityProvider, snipeTaxPips and maxBuyQuoteAmount are zero. Send openExistingTokenMarket(ExistingTokenArgs({ subject, market })) with msg.value zero; the pool is tradeable the moment someone mints a position. Before opening one, check that the subject permits a transfer to 0x…dEaD if Auto Burn is on, what a tokenized stock's pause registry does to the pool, and that market.creator proves nothing about ownership.
Launchers
A launcher is a contract that opens modular markets for its own users, and it is the creator of every market it opens: openNewTokenMarket requires args.expectedCreator == msg.sender, and openExistingTokenMarket records msg.sender.
- Its tier applies to every market it opens.
protocolShareBps(creator)resolves on the calling address, so a tier granted to a launcher applies to every one of its users until it is cleared. Tiers are owner-set on the coordinator, with no application flow in the contracts. Read your rate in the same transaction you launch in, because the config must match it exactly or admission revertsProtocolShareTierMismatch. A tier can be zero. lpFeeRecipientis where the founding fees go. Set it to a user's address or to a splitter you deploy per launch. It is frozen at creation, and the coordinator only ever pays the one address named there.intentIdis scoped to the creator.launchedByIntent[creator][intentId]guards replay for one creator address, which for a contract launcher is one namespace shared by all its users, so derive the id from something user-specific.- Predict the token.
previewNewTokenAddress(args, intentId)is a view. Pass the same address asexpectedTokento assert it; a mismatch revertsUnexpectedToken(expected, actual). - Size a creator buy. The 5% cap,
maxInitialBuySubject(), is on the subject the creator receives, not on the quote spent. Compute the quote amount from the opening price and the band, quote it through the Hookr quoter, then check it againstmaxBuyQuoteAmountfor its block. The buy must consume its whole input. A buy that breaks either bound reverts the entire launch, token deployment included. - The opening price is not free. The app's launcher opens every new-token market at a fixed valuation of 2.5 ETH. Against native ETH the usable tick nearest it is 198,060 for an 18-decimal subject at tick spacing 60. For USDG or HOOKR the launcher converts 2.5 ETH into raw units from a reference pool's spot price (the WETH/USDG 0.01% Uniswap v3 pool, or the hookless HOOKR/ETH v4 pool) and lands on the nearest usable tick. The coordinator itself only takes a
sqrtPriceX96.
| Revert | Meaning |
|---|---|
Protocol | The tier changed between reading it and launching. Re-read it and rebuild the config. |
MarketOpeningPaused(caller) | Opening is owner-restricted right now. |
IntentAlreadyUsed(creator, intentId, subject) | The intent namespace collided. |
TokenSaltAlreadyUsed(create2Salt, subject) | The same launch arguments were already used. Vary deploymentSalt. |
InvalidMarketArgs | Usually the opening price is not exactly on a usable tick. |
InitialBuyAboveCap(subjectOut, cap) | The creator buy would deliver more than 5% of supply. |
MaxBuyExceeded(attempted, max) | The creator buy breaks the guard's per-block cap. |
Native | The selections contain no native mechanics module. |
TaxedTransfer | A token moved by a different amount than the coordinator asked for. A fee-on-transfer ERC-20 quote fails in the router instead (InputDebitMismatch, SettlementMismatch) or at claim time (ClaimTransferFailed). |
Before mainnet: read your own protocolShareBps in the launch transaction, set limits.baseLpFeePips equal to the config's baseFeePips, set limits.trustedRouter and limits.trustedQuoter to the registered router and quoter (or the registry rejects the stack with IntegrationOutsideRootProfile), put the opening price exactly on a usable tick, approve the router for an ERC-20 quote if the launch includes a creator buy, set potMinBuyWei to at least 10 ** (decimals - 3) for an ERC-20 quote with a pot, and test one buy against a burn-enabled pool on a fork if the subject is not a token you deployed.
Security Notes
Invariants the contracts hold, each enforced at a named place:
- Admission: one canonical module version (exactly 2); the protocol share is proved at admission, not declared; the recipient equals the coordinator's treasury; the pair is re-checked on every swap and liquidity add (
ProtocolShareNotEnforced); every admission read fails closed; a guard window implies a locked founding position. - Configuration: the config is byte-exact; its hash is re-derived on every callback and compared with the registry's frozen hash; per-pool caps sit inside the catalog registration; the base fee agrees across both structures.
- Runtime: only the pool's own kernel may call the module; a stateful module never runs as a stateless one; input cuts require the canonical full-fill limit; the pot needs the authenticated router path and its counter advances once per pool per block; a trusted integration's code cannot change under a pool (
TrustedIntegrationCodeChanged); the protocol never holds subject tokens; the base LP fee carries no protocol share. - Accounting: claims are quote-isolated and independently solvent; a claim pays exactly what the pool delivers; ERC-20 movements are balance-checked on both sides; the trader pays exactly the configured amount.
- Lifecycle: a pool is initialized once, by the coordinator, against a stack that already exists; owner changes never reach an open pool; one-shot binds stay burned; removing liquidity is never hooked.
Trust assumptions and failure direction. The system relies on the PoolManager, on the quote and subject tokens (a token that pauses, blocklists or admin-burns can halt or distort a pool, and Hookr fails closed instead of routing around it), on the owner to set tiers sensibly and to keep the forwarder's target safe, and on the deployer's release sequence, where a wrong one-shot bind is unrecoverable. On the recapture roots it also relies on the partner executor. Every failure direction is a revert except one: the arb recapture lane is fail-open on purpose, so a failing or declining executor is a skipped arb recapture and an event. On the arb recapture root a swap with too little gas for the lane reverts; the recapture roots before it skipped the lane below a gas floor. Ordinary MEV is untouched on the default root; the recapture root refuses only a swap closing an arbitrage inside a registered v3 pool's callback.
Known limitations.
- The protocol share ceiling can only go down, and only by deploying a new module, catalog, registry, coordinator and root, because the binds are one-shot.
- Registration caps are permanent; sizing them too low forecloses configurations, too high widens the reviewed surface.
- There is no per-pool admin: no kill switch, fee override, parameter change or founding-position removal, even in an emergency. A misconfigured pool stays misconfigured.
- A creator who buys their own launch through the guard recovers most of the tax; the 5% cap bounds it.
- Exact-output sells are free of protocol take, and a base-fee-only pool earns the protocol nothing.
- The burn slice is computed from gross quote input, not by converting the withheld subject at the execution price.
- An exact-output swap pays the full dynamic fee ceiling, not a size-scaled fee.
- The pot leg depends on the router: two traders doing the same swap through different routers pay different amounts.
- Partial fills are unavailable on most pools, and the guard is per block, not per address.
- Tokenized stocks bring external control: a pause, a blocklist and
adminBurn. - The contracts are deployed and unaudited. The evidence is source review, a full run of the deployment and trading path against a fork, and reads against the live contracts, plus two canary pools the deployer opened and traded.
Verification
The 16 contracts of the default root's deployment record hold verified source on Blockscout as full matches (the explorer recompiles the source and gets the deployed bytes back, CBOR trailer included), and Sourcify holds the same 16 addresses on chain 4663 as exact matches. The build is Solidity v0.8.26+commit.8a97fa7a through via-IR, the optimizer at 200 runs, evm_version = cancun, bytecode_hash = ipfs and CBOR metadata appended, with the contract repository's ten remappings written out because the metadata records them. The source commit behind the deployed code is 8db7fc940938f811f508ba9cb0c8f2d3f24c9a25, published with the deployment record as deployments/robinhood-4663.v2.json in the public contracts repository.
To rebuild one, run git submodule update --init --recursive, forge build and cast code <address>, and compare out/<file>.sol/<Contract>.json. For a contract that links libraries, write the library addresses into the placeholders deployedBytecode.linkReferences locates, and zero the spans in deployedBytecode.immutableReferences on both sides. The kernel is 23,965 bytes of runtime, the registry 23,307 and the coordinator 22,865. Four of them, the kernel reservation, stateful settlement and correction V2 libraries and the CREATE2 factory, predate the rest and carry the metadata of a build that embeds source text, so their trailers need use_literal_content = true; their code is identical either way. The single immutable in each of the three libraries is its own address, which is how a library refuses a direct call.
Arb recapture on the recapture roots
Arb recapture keeps captured price-gap value with pool participants instead of external MEV bots. The arb recapture root runs it, and so did the three superseded roots. A partner contract does the trading; Hookr's part is an adapter, a set of frozen fields on the pool, and a kernel that calls the partner and cannot be broken by it. Powered by What The Hook, or WTH (whatthehook.io): the executor that prices each arb recapture, trades it and pays the proceeds belongs to WTH, not to Hookr.
How the Lane Works
An arb recapture is one bounded call from the swap kernel to the partner executor while the swap is in flight. The executor reads this pool's price and a venue it has registered for the pair, trades the difference if it is worth trading, and reports a gross realizedProfitQuote. The executor decides whether and how much to trade; Hookr decides nothing about the trade.
- Two phases.
beforeSwapcatches a gap that was open when the transaction arrived,afterSwapone the trader's own order opened. The executor is not told which phase it is in. - The reference venue. The executor keeps a registry of venues per pair, read through
getTokenPools(address,address)(0xeb1af5d2) andregisteredPools(bytes32)(0xea509a64). For HOOKR against ETH it holds one hookless v4 pool at fee 2,500 and tick spacing 25. A pair with no venue has nothing to trade against, and WTH's keeper registers a pair's counter-venues after a pool opens, so a new token starts with the lane on and nothing for it to do. - One quote. The registry admits an executor only on a native-ETH pool with
currency0the quote andcurrency1the subject. A pool quoted in USDG, HOOKR or a tokenized stock cannot carry the lane on any root. - Fail-open. A revert or a decline is a
CorrectionAttemptFailedorCorrectionAttemptSkippedevent and the swap settles.reasonHashis thekeccak256of the revert data. Neither is a failed swap, and neither should be shown to a trader as one. - Refusal. The arb recapture root alone refuses a swap closing an arbitrage inside a registered v3 pool's callback; see the contract note above. The refusal is in the hook and not the adapter because the adapter sits inside the kernel's try and catch, where a revert would be swallowed.
Where the Profit Goes
Every pool on the arb recapture root freezes the same waterfall, correctionFeePolicyId 0xd2653e091cb7002585fd8b1192b58f11b9c951061dc797123e37d5e2ccb45cef. The route only decides whether a trader is paid.
| Recipient | Through the Hookr router | Any other route |
|---|---|---|
The pool's correctionCreator | 40% | 40% |
| The authenticated trader | 20% | nothing |
| The triggering pool's in-range LPs | 20% | 40% |
| WTH | 10% | 10% |
| Hookr | 10% | 10% |
Only a swap through the Hookr router carries an authenticated trader; on any other route the trader's share goes to the triggering pool's LPs. The executor pays every leg inside the swap that realised the profit, and the LP leg arrives as fee growth.
The root's CorrectionAttemptSucceeded carries only the gross. The executor emits the split in the same transaction:
event ArbitrageProfitSplit(
address indexed profitCurrency,
address indexed trader,
address indexed creator,
uint256 totalProfit,
uint256 traderAmount,
uint256 creatorAmount,
uint256 triggerPoolAmount,
uint256 wthProtocolAmount,
uint256 hookrProtocolAmount
);It carries no pool id, so attribute one split to one pool by pairing it with the root's CorrectionAttemptSucceeded from the same transaction. A transaction where the two lists do not pair one for one belongs to no pool that can be named. A leg recomputed from the gross is what the hook requested, not what was paid, and should be labelled derived.
The Partner Executor
What The Hook's executor runs the arb recapture, and if it cannot, the trade goes through without it. This is the procedure for putting an executor behind a root.
- Interface.
IHookrArbExecutorV3hasrouteAdmissionOpen(),executionBlockNumber()andexecuteArbitrage(ExecutionRequest). The adapter narrows the request to three arguments,executeArbitrage(PoolKey triggeringPool, address rebateRecipient, ProfitSplit split), and the executor must also answerfeePolicyId(). - One lawful waterfall. The registry reverts
InvalidStackLimits()unless a pool'scorrectionFeePolicyIdequalsHookrWthFeePolicyV2.FEE_POLICY_ID, and every root shares one registry. A different waterfall needs a registry change, not a new root. - Order. The partner deploys the executor and Hookr deploys the adapter, the executor is configured to accept calls from it, the adapter is bound with
setExecutorOnce, the owner registers it withregisterIntegration, then seals a root profile naming it on a newly mined root.sealRootProfilesucceeds once per kernel, so a new executor is a new root with its own kernel id and pools. There is no self-service path. - Admission checks the executor live.
routeAdmissionOpen()must be true (InvalidIntegrationWiring()otherwise) andfeePolicyId()must match the pool's frozen id (IntegrationMetadataMismatchotherwise). - Verify a binding from outside.
wthExecutor()on the adapter, a secondsetExecutorOncerevertingExecutorAlreadySet(),feePolicyId()against the pool'scorrectionFeePolicyId,stack(poolId)on the registry showinglimits.correctionExecutorand a non-zerocorrectionExecutorIntegrationId, andGET /api/wth/poolson hookr.fun, which returns the root, its kernel id, the coordinator and the topic of each event.
Opening a Market on a Recapture Root
A market on the arb recapture root opens through the coordinator like any other market of its release. Two things differ: the kernelId names the recapture root, and the five StackLimits fields that are zero everywhere else are filled in.
| Field | Value | The registry requires |
|---|---|---|
correctionExecutor | 0x28AF…D806 | non-zero, with code, and the executor the root's profile admits |
correctionCreator | the address the creator share pays to | non-zero |
correction | 5000 | above zero and at most MAX_CORRECTION_VOLUME_BPS, 5000 |
correction | 1 | above zero |
correctionFeePolicyId | 0xd2653e091cb7002585fd8b1192b58f11b9c951061dc797123e37d5e2ccb45cef | equal to Hookr |
- Gains.
40%of each arb recapture goes tocorrectionCreator, on top of everything the five Hook Blocks pay a creator, and the pool's LPs take20%through Hookr's router and40%through any other route. - Gives up. Every quote but native ETH; swaps arriving from inside a registered v3 pool's callback; the routing allowlist, which is per hook address, so nothing the default root carries reaches this root; and any swap sent with too little gas for the lane.
- Getting it wrong is permanent. Leaving the five fields at zero opens a pool that can never recapture the arb, because the arb recapture path returns on its first line when
correctionExecutoris zero. The fields are part of the stack hash and the root is named in thePoolKey. There is no repair. - Check the pair first. The lane does nothing unless the executor has registered a reference venue for the pair, and nothing enforces that at launch. A pair with no venue is admitted, opens normally and never recaptures the arb.
- Admission rejects a zero executor with any other field set, a non-zero executor with a zero creator, volume or profit field, a non-native quote, and a wrong policy id (
InvalidStackLimits()), an executor with no code or whoserouteAdmissionOpen()is false or reverts (InvalidIntegrationWiring()), and one whosefeePolicyId()differs (IntegrationMetadataMismatch).
Routing a Recapture Pool
On the arb recapture root the arb recapture is priced into the call and not gated by a floor, so a swap sent with a limit too small for it reverts instead of settling without one. An interface routing to these pools sizes from the ordinary estimate with headroom, and carries a measured floor on top for the case where the lane found nothing to do at estimate time. hookr-sdk 0.2.0 gives a pool on a superseded root no floor, so the caller sets one; 0.2.1 adds the correcting option.
Superseded Recapture Roots
| Root | What it does not do |
|---|---|
Fourth, 0xE542…a8cC | Does not refuse a swap arriving from inside a registered v3 pool's callback. Recaptures in both phases. |
Third, 0xa999…28Cc | Does not recapture in beforeSwap: its kernel refused a quote-denominated swap, so every arb recapture it ran was in afterSwap. |
First, 0xc7c5…68CC | Recaptures the arb on neither phase in practice: an attempt needs more gas than an ordinary swap carries. |
Every one carries the same 0x28cc flag word and shares the coordinator, registry and accounting kernel, and each has its own kernel id and adapter bound to the executor WTH ran at the time.
Launch Generations 3, 4 and 5
Every launch mints a fixed supply of 1,000,000,000 tokens with no owner, mint function, pause, blacklist or transfer tax, and the creator receives no token allocation. A pool's Hook Blocks are fixed when it opens, and the hook cannot be edited or replaced afterwards. Tokens launched under these generations remain readable and tradable under their own contracts, and the app reads them under the retained manifests. The contract addresses are listed under Contracts above.
Generations 3 and 4, Legacy
- Bonding curve. 80% of supply sells across ten tranches, each priced 70% above the one before. Buys and sells work while the curve is live, and each pays a 1% curve fee split evenly between creator and protocol. The pool opens only after the last tranche sells out.
- Graduation. The launch graduates in the same transaction as the last sale. The ETH raised and the remaining 20% of supply become a full-range Uniswap v4 position at exactly the price the curve ended on, with the creator's hook attached and a dynamic fee. The launcher owns it and has no function that removes liquidity. Supply that did not fit is burned by default, or placed as up to four token-only bands at creator-chosen price multiples above the graduation price.
- Instant pool (generation 4). The creator commits ETH and chooses 20 to 100% of supply as the tradeable float, which together set the opening price and an opening valuation of 1 to 5 times the committed ETH. The pool opens with locked, launcher-owned liquidity in the launch transaction, with no curve phase. ETH accepted into the position cannot be withdrawn.
- Fees. The locked position earns trading fees that anyone can collect. The ETH side splits by a share the creator chose at launch (50% by default, 80% at most), and the token side is burned. That share can be divided across up to four addresses, each credited on chain so a recipient can claim without depending on the creator.
Generation 5, V1
- Instant pool. Zero-seed: the launch takes no ETH from the creator and the whole supply is one locked, launcher-owned sell position. The pool opens at a fixed platform-wide valuation of 2.5 ETH fully diluted, the same for every launch, and buyers supply all the ETH. LP Rewards is unavailable on this lane, because a zero-seed pool has no in-range LP at open to receive its donation.
- Auction. The creator reserves a disclosed 20 to 50% of supply to seed the pool, and the rest is sold through Uniswap's Continuous Clearing Auction. Bidders set a budget and a maximum price, and each block clears at one price, and that price can rise as demand arrives. The floor valuation (default 0.22 ETH) and the graduation threshold, the minimum successful raise (default 1.1 ETH), are public before anyone bids.
- Settlement. Ending at or above the threshold lets anyone trigger migration, and the pool opens at the final clearing price, with a dynamic fee, as a full-range position the launcher owns and cannot remove. At the 50% reserve the reserved tokens are worth the whole raise at the clearing price, so all of it locks and the creator is paid nothing; at a smaller reserve the un-locked balance is paid to the creator. Supply the position cannot absorb is burned. Ending below the threshold fails the launch: every bid is refunded, the supply is burned and no pool opens.
- Fees. There is no curve fee. The launcher submits
creatorFeeBps = 0andfeeRecipients = [], and zero activates the contract default: on an ETH pair, the collected quote-side position fees remaining after any guard withholding split 50% to the creator and 50% to the protocol, the creator side credits the launching wallet, and the token side is burned. This is launcher policy, not a restriction in the contract's ABI. On generations 3 and 4, token pages continue to show the fee routing stored for that launch. Every eligible ETH-pair swap also pays a flat0.3%protocol fee. Anyone can collect it into the burner. Only its owner can spend it, on a buyback of $HOOKR through the canonical ETH/$HOOKR pool, capped per call, once per block, with a reviewed minimum output, burning every token bought in the same transaction. - $HOOKR pairs. Both lanes quote in ETH by default, and a launch can pair with $HOOKR instead: an instant open at a fixed 2,500,000 $HOOKR valuation, or an auction with a 600,000 $HOOKR floor and a 3,000,000 $HOOKR threshold. A $HOOKR pair supports Anti-Snipe and Hookr dynamic fees only, because Auto Burn, LP Rewards and the Nth-buy Pot account in native ETH. It pays no protocol fee, and 100% of the quote-side fees it collects go to the creator.
Hook Blocks on Generations 3 to 5
These are the earlier launcher's five blocks; which of them a launch could use depended on its lane and its quote, as above.
- Anti-Snipe. For a set number of blocks after the pool opens, buys are capped and pay an extra LP fee. The blocks are counted on the parent chain, about 12 seconds each, not this chain's. Exact-output buys are blocked in the window so the cap cannot be routed around.
- Hookr dynamic fees. The LP fee scales with how much of the pool's in-range depth a trade consumes, from the base fee up to the ceiling.
- Auto Burn. A share of exact-input buy output goes to the dead address.
- LP Rewards. An ETH-side share of an exact-input buy is donated to in-range LPs inside the swap.
- Nth-buy Pot. An ETH-side share fills a pot and the Nth qualifying buy wins it. A public counter advances at most once per pool per block, a minimum buy applies, and the winner's payout can be redirected when claimed. The pot is not random.
Hook fee cuts apply to exact-input buys only. Sells and exact-output buys pay only the LP fee, plus the flat protocol fee on an ETH pair on generation 5, where an exact-output sell is exempt.
Market Figures and Limits
Hookr-indexed launch history uses settled trades. Pooled charts, volume and value deposited use live third-party market data, so read each figure with its source and window; an unavailable figure is not zero.
Nothing here carries a fairness, anti-bot or anti-MEV guarantee: the Anti-Snipe cap limits swap size for a window, identifies nobody and cannot stop someone using many wallets. The contracts were exercised end to end by a canary launch before writes opened, which is evidence and not an audit. Live-pool trades route only through the bounded Hookr router pinned in the manifest by address and runtime hash.
SDK 0.2
hookr-sdk 0.2 is the package for pools opened on the modular roots. It runs on viem and ships the addresses, the ABIs, the Hook Block catalog with its bounds, and builders for the calls an integration makes.
npm install hookr-sdk@0.2 viem| What it gives you | Exports |
|---|---|
| Addresses and chain facts for Robinhood Chain 4663, both roots and their kernel ids | ADDRESSES, ROOTS, KERNEL_ID, RECAPTURE_KERNEL_ID, CHAIN_ID, RPC_URL |
| The Hook Blocks a pool may compose, their parameters and bounds, and a validator | RULES, PARAMS, defaultBlock, validateStack |
| The fee model, so a UI can show the exact split before signing | fees helpers |
| Market creation for both lanes | build, build |
| Quotes through the trusted quoter and swaps through the trusted router | swap and quote builders |
| Reads of a pool's frozen configuration and market state | reads helpers |
| Claims of pot payouts and creator fees | claims builders |
| Deep links into hookr.fun | links helpers |
ADDRESSES.root is the default root and ADDRESSES.recaptureRoot the arb recapture root, and ROOTS pairs each with its kernel id. Pool keys, quotes, swaps and reads work on either root; pass the pool's own hooks to poolKeyFor. The market builders open on the default root unless root: ROOTS.recapture is passed; on the recapture root they freeze the five arb recapture fields from RECAPTURE_CORRECTION plus the creator, refuse any quote but native ETH and need the creator wallet. The swap builders put a 1,100,000 gas floor on a call against the arb recapture root; a pool on a superseded root gets none from 0.2.0, and 0.2.1 adds a correcting option with poolCorrects to read it from the pool's frozen blocks. On such a pool send the larger of the swap's own estimate plus a quarter and that floor, never less than the estimate. listRoots reads every kernel the registry has registered. listMarkets in 0.2.1 scans in 3,000,000-block windows, which a provider can cut short without an error, so scan logs on your own provider in windows of at most 5,000 blocks. RPC_URL is the public node and is for light reads only. A parity test checks the package against the app's encoders on each release.
ABIs and Errors
The ABI of each deployed contract is served as JSON from /api/v2-abi/<Contract>. Each file is the ABI from the release evidence, pinned to it by a test that compares the two byte for byte, and the route serves only the names in the table. HookrTokenV61 is the fixed-supply ERC-20 the token-deployer library creates for a new-token market; it has no address of its own, because one is deployed per launch. Every custom error in these ABIs is listed below by contract, generated from the same JSON, so it cannot fall behind the deployed code. An interface should surface these errors rather than retry: most are structural, and re-sending the transaction unchanged fails the same way.
| Contract | Functions | Events | Errors | JSON |
|---|---|---|---|---|
HookrModularHookV6 | 18 | 3 | 7 | /api/v2-abi/HookrModularHookV6 |
HookrMarketCoordinatorV5 | 38 | 10 | 32 | /api/v2-abi/HookrMarketCoordinatorV5 |
HookrNativeMechanicsBlockV2 | 55 | 6 | 15 | /api/v2-abi/HookrNativeMechanicsBlockV2 |
HookrStackRegistryV2 | 65 | 14 | 70 | /api/v2-abi/HookrStackRegistryV2 |
HookrKernelRouterV3 | 19 | 1 | 28 | /api/v2-abi/HookrKernelRouterV3 |
HookrKernelQuoterV1 | 15 | 0 | 18 | /api/v2-abi/HookrKernelQuoterV1 |
HookrTreasuryForwarderV1 | 14 | 8 | 11 | /api/v2-abi/HookrTreasuryForwarderV1 |
HookrTokenV61 | 20 | 2 | 2 | /api/v2-abi/HookrTokenV61 |
HookrModularHookV6
7 custom errors in its ABI.
- AccountingKernelCodeChanged(bytes32, bytes32)
- DelegateCallFailed()
- InvalidHookData()
- InvalidPayload()
- InvalidStack()
- InvalidWiring()
- ReentrantCallback()
HookrMarketCoordinatorV5
32 custom errors in its ABI.
- BadCallback()
- CreatorTierUnchanged(address)
- DefaultProtocolShareBpsUnchanged()
- DuplicateMarket(bytes32)
- ExcessiveSettlement()
- InitialBuyAboveCap(uint256, uint256)
- InitialBuyInputMismatch(uint256, uint256)
- IntentAlreadyUsed(address, bytes32, address)
- InvalidInitialBuy()
- InvalidMarketArgs()
- InvalidPayment(uint256, uint256)
- InvalidStack()
- InvalidToken()
- InvalidWiring()
- MarketOpeningPaused(address)
- MarketOpeningPauseUnchanged()
- NativeMechanicsModuleRequired()
- NativeTransferFailed()
- NoFoundingPosition(bytes32)
- NotCreator(address, address)
- NotOwner()
- NotPendingOwner()
- NotPoolManager()
- ProtocolShareAboveCeiling(uint24)
- ReentrantCall()
- TaxedTransfer()
- TierNotSet(address)
- TokenSaltAlreadyUsed(bytes32, address)
- TransferFailed()
- UnexpectedToken(address, address)
- ZeroAddress()
- ZeroLiquidity()
HookrNativeMechanicsBlockV2
15 custom errors in its ABI.
- ClaimTransferFailed()
- ExactOutputBlockedDuringGuard()
- ExternalLiquidityBlockedDuringGuard()
- HookNotCalled()
- InvalidConfig()
- InvalidPotRecipient()
- InvalidRuntimeContext()
- MaxBuyExceeded(uint256, uint256)
- NothingToClaim()
- NotKernel()
- NotPoolManager()
- PartialFillUnsupportedWithInputCuts()
- ProtocolShareNotEnforced()
- StatefulKernelRequired()
- ZeroAddress()
HookrStackRegistryV2
70 custom errors in its ABI.
- ConfigTooLarge(bytes32)
- CoordinatorAlreadySet()
- CoordinatorNotSet()
- DuplicateModule(bytes32)
- DuplicateModuleImplementation(address)
- ExceptionalKernelInstancesDisabled(bytes32)
- ExclusiveGroupConflict(bytes32)
- IntegrationAlreadyExists(bytes32)
- IntegrationAlreadyRetired(bytes32)
- IntegrationCodeChanged(address, bytes32, bytes32)
- IntegrationFamilyMismatch(address, bytes32, bytes32)
- IntegrationImplementationAlreadyRegistered(address, bytes32)
- IntegrationKindMismatch(address, bytes32, bytes32)
- IntegrationMetadataMismatch(address)
- IntegrationNotActive(bytes32)
- IntegrationOutsideRootProfile(bytes32, bytes32)
- IntegrationPoolManagerMismatch(address)
- IntegrationStackRegistryMismatch(address)
- InvalidCurrencyPair()
- InvalidHookFlagDependencies()
- InvalidHookFlags(uint160, uint160)
- InvalidIntegration()
- InvalidIntegrationWiring()
- InvalidKernel()
- InvalidKernelInstance(address)
- InvalidKernelInstanceFactory()
- InvalidModuleConfig(bytes32)
- InvalidModuleStackBinding(bytes32)
- InvalidPoolKey()
- InvalidRootProfile()
- InvalidStackLimits()
- KernelAlreadyExists(bytes32)
- KernelAlreadyRetired(bytes32)
- KernelCodeChanged(bytes32, bytes32)
- KernelCoordinatorMismatch()
- KernelInstanceFactoryAlreadyExists(address)
- KernelInstanceFactoryAlreadyRetired(address)
- KernelInstanceFactoryCodeChanged(address, bytes32, bytes32)
- KernelInstanceFactoryNotActive(address)
- KernelNotActive(bytes32)
- KernelPoolManagerMismatch()
- KernelStackRegistryMismatch()
- MissingDependency(bytes32, bytes32)
- ModuleCapsExceedStackLimits()
- ModuleCodeChanged(bytes32, bytes32, bytes32)
- ModuleConfigCapsExceedSnapshot(bytes32)
- ModuleConflict(bytes32, bytes32)
- ModuleGasExceedsStackLimit()
- ModuleMetadataMismatch(bytes32)
- ModuleOutsideRootProfile(bytes32, bytes32)
- NonCanonicalProfileModuleOrder(bytes32, bytes32)
- NotCoordinator()
- NotOwner()
- NotPendingOwner()
- NotStackKernel(address, address)
- RootImplementationAlreadyRegistered(address, bytes32)
- RootProfileAlreadySealed(bytes32)
- RootProfileIdentityAlreadyUsed(bytes32, uint32, bytes32)
- RootProfileNotSealed(bytes32)
- StableRootProfilesRequired()
- StackAlreadyConfigured(bytes32)
- StackAlreadyInitialized(bytes32)
- TooManyModules()
- UnknownIntegration(bytes32)
- UnknownKernel(bytes32)
- UnknownKernelInstanceFactory(address)
- UnknownStack(bytes32)
- UnregisteredIntegration(address)
- UnsupportedHookFlags(bytes32, uint160, uint160)
- ZeroAddress()
HookrKernelRouterV3
28 custom errors in its ABI.
- BalanceQueryFailed()
- CallbackNotActive()
- CallbackNotCompleted()
- CoordinatorCodeHashMismatch(bytes32, bytes32)
- DeadlineExpired(uint256, uint256)
- InputDebitMismatch(uint256, uint256)
- InvalidAmount()
- InvalidNativeValue(uint256, uint256)
- InvalidRecipient()
- InvalidSqrtPriceLimit()
- InvalidStack()
- InvalidWiring()
- ModuleDataTooLarge()
- NotCoordinator(address, address)
- NotPoolManager()
- OutputCustodyMismatch(uint256, uint256)
- OutputDeliveryMismatch(uint256, uint256)
- PartialFill(uint256, uint256)
- Reentrancy()
- RetainedBalance(address, uint256, uint256)
- SettlementMismatch(uint256, uint256)
- StackNotInitialized()
- TooLittleReceived(uint256, uint256)
- TooMuchRequested(uint256, uint256)
- TransferFailed()
- UnexpectedDelta()
- UnexpectedNativeSender()
- UntrustedStackRouter(address, address)
HookrKernelQuoterV1
18 custom errors in its ABI.
- CallbackNotActive()
- InvalidAmount()
- InvalidAmountBound()
- InvalidIdentity()
- InvalidQuoteResult()
- InvalidSqrtPriceLimit()
- InvalidStack()
- InvalidWiring()
- ModuleDataTooLarge()
- NotPoolManager()
- QuoteResult(bytes32, uint128, uint128)
- Reentrancy()
- SimulationDidNotRevert()
- StackNotInitialized()
- TooLittleReceived(uint256, uint256)
- TooMuchRequested(uint256, uint256)
- UnexpectedDelta()
- UntrustedStackQuoter(address, address)
HookrTreasuryForwarderV1
11 custom errors in its ABI.
- InvalidNativeBlock()
- InvalidPoolManager()
- InvalidTarget()
- NativeBlockUnchanged()
- NativeTransferFailed()
- NotBound()
- NotOwner()
- NotPendingOwner()
- TargetUnchanged()
- TokenCallFailed()
- ZeroAddress()
HookrTokenV61
2 custom errors in its ABI.
- MathOverflowedMulDiv()
- ZeroAddress()