
Deterministic V4 discovery: finding every hookless pool without a factory, a list, or a deployer to trust
Uniswap V4 has no factories to scan and no registry to read — every pool of every deployer is keccak256(PoolKey) inside one singleton. BlazePhoenix discovers hookless V4 pools by DERIVING those ids over the canonical fee grid and probing them in one gas-free extsload — 99 calls become one, deployer-blind. And the honest limit: hooked pools are unenumerable on-chain by construction, and why that is physics, not a gap.
Known formally as Singleton Derivation in the BlazePhoenix whitepaper.
BlazePhoenix Engineering · updated 2026-08-18 · 9 min · written from the deployed bytecode
By Mitra (@Sigmacrit) — anonymous developer of the BlazePhoenix protocol. The code is the résumé.
To discover a Uniswap V2 or V3 pool you derive an address: CREATE2 makes the pool of a token pair a pure function of factory, salt and init code, so you can compute where it MUST live and check (that is the sibling technique — see create2-discovery). V4 deletes the thing that derivation stood on. There are no per-pool contracts and no factories: every pool of every deployer lives as a storage entry inside ONE PoolManager singleton per chain. There is nothing to scan.
That sounds like it makes discovery harder. It makes it cleaner. Because there is exactly one creation path — PoolManager.initialize(PoolKey, sqrtPrice) — every pool that has ever existed, from any deployer, is identified the same way: PoolId = keccak256(abi.encode(PoolKey)). The pool is not something you look up in a directory; it is the hash of its own definition. The deployer is erased from the trust model — you never ask who made a pool, only whether the id you derived reads back a live price.
The meta-pattern: you cannot enumerate, but you can derive
A PoolKey is five fields — currency0, currency1, fee, tickSpacing, hooks. The singleton keys its state by the keccak of those five, and — this is the load-bearing fact — the PoolKeys themselves are never stored on-chain; they exist only in the Initialize event log. A contract cannot read logs. So there is no on-chain function that returns "all pools for USDC/WETH": the set is genuinely unenumerable from inside the EVM.
But the same asymmetry that forbids enumeration grants derivation. Serious hookless liquidity clusters on a tiny, industry-standard grid of (fee, tickSpacing) tiers. For a pair, we can DERIVE the four canonical PoolIds — hooks fixed to address(0) — and ask the singleton whether each exists. That is not a lookup against a list we maintain; it is arithmetic anyone reproduces, and it works for a pool deployed one block ago by someone we have never heard of.
The whole discovery, as one equation and one read
The id is one hash over the five-field key; existence is one storage read on the singleton at the slot that key hashes to. Both are pure functions — no registry, no admin, no gas:
One call, zero gas, deployer-blind
The Hub's V4 derive-scan sorts the pair, derives the canonical hookless ids, and reads them all through one batched extsload on the singleton, returning the tiers that hold a live price and liquidity. It is a view function — an eth_call — so it costs no gas, and it is deterministic: the same inputs give the same ids at every block. What it replaces is worth naming: not a 99-call factory sweep, and not an admin allowlist of "approved" pools. There is nothing to approve, because a pool cannot lie about its own id.
The discovered pools merge into the router's candidate set alongside V2/V3 venues; the best output wins, and the contract-enforced iron floor makes admission safe without trusting the pool — the worst a bad pool can do is fail to fill or deliver less, which the floor catches. Identity-free discovery, execution-time safety.
The honest limit: hooked pools are physics, not a gap
The derive-scan finds HOOKLESS pools. Hooked pools carry an arbitrary hook contract, and a hook address is 160 bits with only its low 14 bits constrained (those encode the hook's permission flags, mined into the address via CREATE2). The other ~146 bits are free. You cannot iterate 2^146 addresses, and the only record of which hooks exist for a pair is the Initialize log a contract cannot read. So hooked-pool enumeration on-chain is impossible — for anyone, not just us. It is a property of V4's design, and pretending otherwise would be the kind of unverifiable claim this protocol exists to avoid.
Measured on Base: for WETH/USDC, all four canonical hookless tiers exist. For memecoins the picture is exactly what the design predicts — BRETT/WETH and DEGEN/WETH each expose one hookless canonical pool that the derive-scan finds; a token whose V4 liquidity lives behind a hook (dynamic fees, anti-snipe) exposes none to this call. Those are reachable only through an off-chain feed of the Initialize logs — which stays allowlist-free, because a submitted key still self-proves against the singleton.
The bits that classify what you cannot enumerate
Enumeration is impossible, but classification is free and objective — three small bit-patterns handle every V4 pool without trusting a soul. The hook's low 14 bits say what it may do (beforeSwap, afterSwap, return-delta) straight from its address. The pool's fee field says whether the fee is dynamic: bit 23, the flag 0x800000, distinguishes a dynamic-fee pool (its live fee set by the hook) from a static one, and static fees never reach that bit. And the current fee of a dynamic pool is not a mystery either — it lives in slot0's lpFee field, read in the same extsload that reads the price. Derive the id, read the slot, and the pool's price, liquidity and live fee are all in hand — no oracle, no registry, no permission.
Do not trust this page — reproduce it
Every claim above is checkable against the chain. Start here:
reproduce a derivation end to end (Base WETH/USDC, 0.05% tier): PID=$(cast keccak $(cast abi-encode "f(address,address,uint24,int24,address)" 0x4200000000000000000000000000000000000006 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 500 10 0x0000000000000000000000000000000000000000)); cast call 0xa3c0c9b65bad0b08107aa264b0f3db444b867a71 "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $PID --rpc-url https://mainnet.base.org — a non-zero sqrtPriceX96 is the pool, derived not looked upCite this article
Licensed CC BY 4.0 — quote, translate and reuse freely, including commercially, with attribution and a link. Copy a ready-made citation:
BlazePhoenix (2026). Deterministic V4 discovery: finding every hookless pool without a factory, a list, or a deployer to trust. BlazePhoenix Engineering. https://blazephoenix.xyz/learn/deterministic-v4-discovery@misc{blazephoenix_deterministic_v4_discovery,
title = {Deterministic V4 discovery: finding every hookless pool without a factory, a list, or a deployer to trust},
author = {BlazePhoenix},
year = {2026},
url = {https://blazephoenix.xyz/learn/deterministic-v4-discovery},
note = {Accessed: reproduce the claim with the command above}
}Writing an answer, a wiki entry or a paper? The claim above is reproducible against the chain before you quote it — which is the only sound basis for citing a technical source at all.
Contracts are verified on every chain we deploy to — addresses in the protocol manifest. Deeper formal treatment: the whitepaper (PDF). Standards cited: UNISWAP V4-CORE: POOLMANAGER.SOL (SINGLETON, INITIALIZE, INITIALIZE EVENT), SRC/TYPES/POOLID.SOL (KECCAK OF THE KEY), SRC/LIBRARIES/STATELIBRARY.SOL (POOLS_SLOT = 6, SLOT0 PACKING), SRC/LIBRARIES/HOOKS.SOL (14 PERMISSION-FLAG BITS). · UNISWAP V4-CORE LPFEELIBRARY: DYNAMIC_FEE_FLAG = 0X800000. · EIP-1014 (CREATE2) — THE V2/V3 SIBLING DERIVATION.
Share this article · join the discussion
Related engineering
- CREATE2 discovery: verifying a pool is who it claims to be ›
- Uniswap V4 end to end: four broken assumptions, answered structurally ›
- On-chain quoting: why the price you see is the price the contract computed ›
- Phantom liquidity: when pools advertise depth they do not hold ›
- Stateless by design: a contract that holds nothing is a contract that cannot lose it ›
- Measured, not nominal: why a protocol must never trust the number it passed to a token ›