CRIUS
Uniswap v4Real-timeSecurityTutorial

How to Detect New Uniswap v4 Pools on Robinhood Chain

Detect every new Uniswap v4 pool on Robinhood Chain as it is created: the pools:new stream, raw PoolManager Initialize logs, and hook checks before you trade.

Crius 9 min read

Short answer: subscribe to the pools:new channel on the Crius WebSocket stream and you receive every new Uniswap v2, v3 and v4 pool on Robinhood Chain within about a block, with the v4 pool id, both currencies, the fee, the tick spacing and the hook’s decoded permissions. Without Crius, read Initialize logs from the v4 PoolManager (0x8366a39cc670b4001a1121b8f6a443a643e40951) over JSON-RPC, then check each hook before you trust the pool.

Uniswap v4 is where most new markets on Robinhood Chain are born, and many of them come with a hook that can change how swaps behave. This guide shows both ways to detect new v4 pools, what a new pool actually tells you, and how to screen hooks automatically. It is the practical companion to Uniswap v4 hooks on Robinhood Chain, explained, which covers how hook permissions work. Every output below comes from Robinhood Chain mainnet in October 2026.

How many new pools to expect

From the chain stats endpoint, the 24 hours before writing saw 8,890 new pools and curves:

Venue New in 24 h 24 h volume
Uniswap v4 4,537 $286.5 million
Uniswap v2 1,595 $6.4 million
Pons bonding curves 1,340 $6.0 million
Other launch curves 1,275 $11,000
Uniswap v3 143 $237.6 million

That is about three new v4 pools a minute. Most are launchpad pools or one-off memecoin pairs, but v4 also carries more than half of the chain’s DEX volume, so the few that matter matter a lot. Any detector therefore needs a cheap first pass (what was created, with which hook) and a second, more expensive look only at the pools worth it.

What defines a v4 pool

Uniswap v4 keeps every pool inside one contract, the PoolManager. A pool is identified by its PoolKey:

Field Meaning
currency0, currency1 The two assets, sorted by address. Native ETH is 0x0000…0000, not WETH.
fee The LP fee in hundredths of a basis point (3000 = 0.30%). 0x800000 (8,388,608) is the dynamic-fee flag: the hook sets the fee per swap.
tickSpacing Price granularity of positions.
hooks The hook contract, or 0x0 for none.

The pool id is keccak256(abi.encode(PoolKey)), a 32-byte value rather than a contract address. You cannot reverse it into the key, which is why the creation event matters: it is the only place the key is published.

Option 1: the pools:new stream

The pools:new channel pushes one message per new pool on every DEX Crius indexes. A real v4 message, as received:

{
"ch": "pools:new",
"seq": 168,
"block": 96729348,
"ts": 1792841269000,
"data": {
"dex": "uniswap_v4",
"pool": "0xe821c1b762c480e8f4a93dd47b9c9e0c6b339b1b6c3890111de12a4f9e0c058c",
"token0": "0x0000000000000000000000000000000000000000",
"token1": "0xcb20c41d6ae4bdba0a87400c8e64634386e306d7",
"fee": 0,
"tick_spacing": 200,
"hook": {
"address": "0x745d717620052a97a22deee2e5eba59583f3e0cc",
"permissions": ["beforeInitialize", "beforeSwap", "afterSwap", "beforeSwapReturnDelta", "afterSwapReturnDelta"]
}
}
}

Note what is already decoded for you: the hook’s permissions, read from the low bits of the hook address, are in the message. A pool with beforeSwap plus the ReturnDelta permissions can reprice or tax swaps; a pool without a hook has "hook": null.

This detector listens for v4 pools and, for hooked ones, looks the hook up with /hook/{address} to get its name, verification status, risk flags and how many pools use it (Node.js 22+):

// detect-v4.mjs: node detect-v4.mjs
const API = "https://api.crius.sh/v1/robinhood";
const headers = { "x-api-key": process.env.CRIUS_API_KEY };
const ws = new WebSocket(`wss://api.crius.sh/v1/robinhood/stream?api_key=${process.env.CRIUS_API_KEY}`);
ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", channels: ["pools:new"] }));
ws.onmessage = async (e) => {
const m = JSON.parse(e.data);
if (m.ch !== "pools:new" || m.data.dex !== "uniswap_v4") return;
const { pool, fee, tick_spacing, hook } = m.data;
if (!hook) {
console.log(`block ${m.block} v4 pool ${pool.slice(0, 10)}… fee ${fee} spacing ${tick_spacing} (no hook)`);
return;
}
// Hooked pool: name, verification, risk flags and adoption of the hook.
const r = await fetch(`${API}/hook/${hook.address}`, { headers });
const h = (await r.json()).data ?? {};
console.log(`block ${m.block} v4 pool ${pool.slice(0, 10)}… hook ${h.name ?? hook.address}`,
`verified=${h.verified} risk=${(h.risk ?? []).join(",") || "none"} pools_using_hook=${h.pools}`);
};

Two minutes of real output:

block 96717627 v4 pool 0xc6fef029… hook 0x2c668601193e4a081dd82604785e436aff6a2acc verified=false risk=can_block_or_reprice_swaps,can_modify_swap_amounts,can_gate_liquidity_removal,can_gate_liquidity_add,unverified pools_using_hook=2219
block 96717800 v4 pool 0x61e82d84… hook DopplerHookInitializer verified=true risk=can_modify_swap_amounts pools_using_hook=219074
block 96718360 v4 pool 0xd03b7207… fee 10000 spacing 200 (no hook)

The first line is exactly the kind of pool worth flagging: an unverified hook that can block swaps, change swap amounts and gate liquidity in both directions. The second is a widely used, verified hook behind more than 219,000 pools.

Option 2: raw Initialize logs from the PoolManager

If you run your own node or want to verify Crius against the chain, read the PoolManager’s Initialize event directly. Its signature in v4-core is:

event Initialize(
PoolId indexed id, Currency indexed currency0, Currency indexed currency1,
uint24 fee, int24 tickSpacing, IHooks hooks, uint160 sqrtPriceX96, int24 tick
);

The topic is keccak256("Initialize(bytes32,address,address,uint24,int24,address,uint160,int24)") = 0xdd466e67…6438. The pool id and both currencies are indexed topics; fee, tick spacing, hooks, starting price and tick are in the data. This Python script reads the last 3,000 blocks from the public RPC:

"""Find new Uniswap v4 pools straight from the chain: PoolManager `Initialize` logs over the last N blocks."""
import requests
RPC = "https://rpc.mainnet.chain.robinhood.com"
POOL_MANAGER = "0x8366a39cc670b4001a1121b8f6a443a643e40951"
INITIALIZE = "0xdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d6438"
def rpc(method, params):
r = requests.post(RPC, json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params}, timeout=20).json()
if "error" in r:
raise RuntimeError(r["error"])
return r["result"]
def word(data: str, i: int) -> int:
return int(data[2 + 64 * i: 2 + 64 * (i + 1)], 16)
head = int(rpc("eth_blockNumber", []), 16)
logs = rpc("eth_getLogs", [{"address": POOL_MANAGER, "topics": [INITIALIZE], "fromBlock": hex(head - 3000), "toBlock": hex(head)}])
for log in logs:
t, d = log["topics"], log["data"]
fee, tick_spacing, hooks = word(d, 0), word(d, 1), "0x" + d[2 + 64 * 2 + 24: 2 + 64 * 3]
tick_spacing = tick_spacing - (1 << 256) if tick_spacing >= 1 << 255 else tick_spacing
print(int(log["blockNumber"], 16), "pool", t[1][:12] + "…", "currency0", "0x" + t[2][-40:], "currency1", "0x" + t[3][-40:],
"fee", fee, "spacing", tick_spacing, "hooks", hooks if int(hooks, 16) else "none")
print(f"{len(logs)} new v4 pools in the last 3,000 blocks (~5 minutes)")

Real output (trimmed):

96720270 pool 0x8bcebf89f8… currency0 0x5fc5360d0400a0fd4f2af552add042d716f1d168 currency1 0xc0d6457c16cc70d6790dd43521c899c87ce02f35 fee 8388608 spacing 10 hooks 0xfd72eea606df1050b110503b4953c10fd4c870c0
96720372 pool 0xf78f24593b… currency0 0x51418def50e62a103989b0760a2e504765679aac currency1 0xd917b029c761d264c6a312bbbcda868658ef86a6 fee 8388608 spacing 200 hooks 0x4e3468951d49f2eea976ed0d6e75ffcb44a9a544
96720935 pool 0x8ac8300c4e… currency0 0x1fbc318342701e9797a229fa3f1c8d621a411e18 currency1 0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec fee 8388608 spacing 8 hooks 0x4e3468951d49f2eea976ed0d6e75ffcb44a9a544
16 new v4 pools in the last 3,000 blocks (~5 minutes)

Every pool here uses the dynamic-fee flag (8388608), so the fee you pay is whatever the hook decides at swap time. The first line pairs USDG with the Meta stock token through a hook (0xfd72…70c0) that, when we looked it up, was unverified, used by 8 pools and held beforeSwap: worth knowing before routing a stock-token trade through it.

The raw route is what an indexer does under the hood. To run it continuously you also need to handle reorgs, decode hook permissions from the address bits, look up token metadata, and backfill after restarts. The RPC vs indexed API comparison covers where that work pays off and where it doesn’t.

Look at the pool before you trade it

A new pool is a promise, not a market. Two things to check:

1. Is there liquidity? In v4, initializing a pool and adding liquidity are separate steps; Initialize can be followed by nothing at all. Call /pool/{pool} for live reserves, liquidity and lock status, or subscribe to lp:<token> to hear when liquidity is added. A graduated Pons pool, for example, comes back with:

{
"dex": "uniswap_v4",
"hook": {
"address": "0xe5e702641ea86f4ae6cc3cdaed2b886f976be044",
"name": "PonsV2MemeHook",
"permissions": ["beforeInitialize", "afterSwap", "afterSwapReturnDelta"],
"risk": ["can_modify_swap_amounts"]
},
"lp": { "locked_pct": 100, "locker": "pons_v2_launch_locker", "permanent": true, "status": "locked" },
"state": { "reserves": { "currency0": 4.70785, "currency1": 182066584, "kind": "real" }, "tick": 174715 }
}

2. What can the hook do? The hooks list ranks all 29,474 hooks Crius has seen on Robinhood Chain. The most used at time of writing:

Hook Pools Verified Risk flags
DopplerHookInitializer 219,070 yes can_modify_swap_amounts
ClankerHookStaticFeeV2 15,354 yes can_block_or_reprice_swaps, can_modify_swap_amounts, can_gate_liquidity_add
PonsV2MemeHook 10,428 yes can_modify_swap_amounts
CashCatHookV2 9,184 yes can_block_or_reprice_swaps, can_modify_swap_amounts, can_gate_liquidity_removal, …
LunchTaxHookPair (proxy) 7,323 yes can_block_or_reprice_swaps, can_modify_swap_amounts
UniversalKlikHook 6,929 yes can_block_or_reprice_swaps, can_modify_swap_amounts

Risk flags describe capability, not intent: launchpad hooks need afterSwapReturnDelta to take their fee. What should raise your guard is an unverified hook with blocking or delta permissions and few pools. Filter for exactly that (at time of writing this returned 20,322 hooks, the busiest used by 5,343 pools):

Terminal window
curl -s "https://api.crius.sh/v1/robinhood/hooks?verified=false&risk=can_block_or_reprice_swaps&sort=pools&limit=20" \
-H "x-api-key: $CRIUS_API_KEY"

For a token you are about to trade, /security goes further and measures the tax actually paid on real buys and sells through the hook, which catches behaviour a permission check cannot see. See the token security API.

A practical screening pipeline

Putting it together, a production detector usually looks like this:

  1. Stream pools:new and keep dex == "uniswap_v4" (or more venues if you route anywhere; the channel also carries v2, v3 and launchpad curves).
  2. Classify the hook from the message: no hook, a known launchpad hook (Pons, Doppler and so on), or something else.
  3. Enrich the rest with /hook/{address} and cache the result per hook; with 29,000 hooks and 219,000 pools on the largest one, most lookups hit the cache.
  4. Wait for liquidity via lp:<token> or /pool/{pool} before treating the pool as a market.
  5. Score the token with security, holders and bundle detection.

Step 1 costs no credits. Steps 3–5 cost a few credits per pool, and you only take them for pools that pass the earlier steps.

FAQ

How fast does pools:new deliver a new v4 pool? As soon as the block containing the Initialize is indexed, typically within a second or two on Robinhood Chain’s ~100 ms blocks.

Does pools:new include Uniswap v2 and v3 pools too? Yes. It carries every new market Crius indexes, with dex naming the venue: uniswap_v2, uniswap_v3, uniswap_v4, and launchpad bonding curves such as pons_curve and launch_curve. In a three-minute sample we saw 4 Pons curves, 3 v4 pools, 2 v2 pools and 2 other launch curves, so filter on dex if you only want v4.

What does fee 8388608 mean on a v4 pool? It is 0x800000, Uniswap v4’s dynamic-fee flag. The pool’s LP fee is set by its hook on every swap instead of being fixed at creation.

Why does a new pool have no price? Because nobody has added liquidity or traded yet. Initialize sets a starting price, but until liquidity arrives the pool cannot fill a trade.

Can I get every v4 pool ever created, not just new ones? Yes. Crius indexed Robinhood Chain from block one; use the Uniswap v4 API and historical data for backfills.

Sources

Build with Crius

Robinhood Chain data in one API

Free plan with 1M credits a month. Your key in under a minute.

Related APIs

More from the blog