---
title: "censoredHooks: reading the hooks that never published their source"
version: "Version 1.0"
date: "October 2026"
---

## Abstract

Uniswap v4 lets every pool attach a hook, a contract that runs inside the pool's swaps, deposits and withdrawals. A hook can tax a trade, set its own fee, refuse a deposit, refuse a withdrawal, or let a buy through and refuse the sell. On Robinhood Chain, hundreds of distinct hooks sit behind v4 pools, and most of them have never published their source. To a trader they are censored: code that will run inside the trade, and nothing to read.

censoredHooks reads them anyway, from what the chain cannot hide. The hook's address states its permissions. Its bytecode states its structure: proxy or not, owner or not, the functions it exposes. And its behaviour can be measured: a probe contract, placed at an empty address for a single `eth_call`, swaps through the hook's real pool in both directions and adds and removes liquidity, then reverts with what it received. Nothing settles and no tokens are needed, and the difference from a hookless pool, beyond the pool fee, is what the hook took.

From these readings, censoredHooks grades eight fixed checks, computes a score from 0 to 100 and gives a verdict of safe, caution or danger. It publishes a report per hook, a board of every hook on the chain, and an API that bots, aggregators and AI agents can call before they route. This paper describes the method, the checks and the outputs, and states the limits as plainly as the rest.

## 1. Hooks run inside your trade

Uniswap v4 replaced the contract-per-pool design of earlier versions with a single PoolManager that holds every pool and every pool's tokens. On Robinhood Chain (chain id 4663) it lives at `0x8366a39CC670B4001A1121B8F6A443A643e40951`. A pool is defined by its key: two currencies, a fee, a tick spacing, and a hook address. The same pair can exist many times over, once per hook, and two pools that trade the same token can behave in entirely different ways.

The PoolManager calls the hook before and after each of the pool's actions: initialize, add liquidity, remove liquidity, swap and donate. A callback is ordinary code, and a revert in it refuses the action it wraps. Four further permissions, the "return delta" permissions, let a hook insert its own balance change into a swap or a liquidity change: it can take part of a swap's input or output, or charge an LP on the way in or out. A pool created with a dynamic fee lets the hook set the fee, including separately for every swap.

These are legitimate tools, behind limit orders, custom curves and launch mechanics. From the trader's side they are also code the trader did not choose, running inside a trade the trader did.

## 2. Most hooks are censored

On Robinhood Chain, hooks are the norm rather than the exception. Launchpads attach a hook to every memecoin pool they create. Pons V2 graduates its tokens into v4 pools. The result is hundreds of distinct hooks.

Most of these hooks have never had their source verified on the block explorer, and nothing requires them to. A trader about to buy a token faces a pool whose hook is a block of bytecode, and scanners that work from verified source have nothing to read.

A trader needs to know what this hook will do to the swap, and to the sell after it. An LP needs to know whether a deposit can come back out, whole. Neither question requires the source; both can be answered from the chain.

## 3. What the chain cannot hide

censoredHooks is built on three readings, in increasing order of strength.

1. **The address** is a declaration that cannot be false. The PoolManager enforces it.
2. **The bytecode** is a description that can be read but not fully understood without the source. It is read for structure, not for logic.
3. **The behaviour** is a measurement. It does not say why a hook does what it does, only what it did, exactly, at one block.

Two principles follow. What can be measured is measured rather than inferred: the cost of a swap and an LP's exit are never guessed from code. And every claim carries the block it was true at, because a hook can change.

## 4. Reading the address

A v4 hook's permissions are the last 14 bits of its address. The deployer mines the address with CREATE2 until those bits match the callbacks the hook implements, and the PoolManager reads them on every action. From bit 13 down to bit 0: beforeInitialize, afterInitialize, beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity, beforeSwap, afterSwap, beforeDonate, afterDonate, beforeSwapReturnDelta, afterSwapReturnDelta, afterAddLiquidityReturnDelta, afterRemoveLiquidityReturnDelta.

The PoolManager calls only the callbacks these bits switch on, rejects a hook address whose bits are inconsistent (a return delta without the callback it rides on), and reverts any action where a switched-on callback does not answer correctly. The bits and the code have to agree or the pool does not work.

This reading has an unusual property: an off bit is a guarantee. A hook whose four return-delta bits are off cannot take tokens from a swap or an LP through a delta, whatever its code says, and a hook whose withdrawal callbacks are off cannot refuse a withdrawal. An on bit is only a permission, which the hook may use or leave idle. That question is left to measurement.

## 5. Reading the bytecode

The scanner fetches the hook's runtime bytecode and walks it opcode by opcode, skipping push data and the compiler's metadata at the end. It does not decompile; it collects a short list of facts:

- the function selectors the dispatcher compares against, which is how a Solidity contract routes calls;
- whether DELEGATECALL, SELFDESTRUCT or CREATE appear;
- the number of outside calls in the code;
- the constant addresses the code pushes, such as the PoolManager or a fee wallet.

It reads the three EIP-1967 storage slots (implementation, admin, beacon) and recognises EIP-1167 minimal clones. If the hook forwards to an implementation, the implementation's code is read instead, since that is the code that runs. It calls `owner()`.

The selectors are then named from the public signature database at openchain.xyz. This step is what turns a censored hook into one that reads in words: a hook nobody published still shows `setFee`, `withdraw` or `blacklist` when its functions match known signatures. Names that look like an administrator's tools are flagged.

Finally, each callback the address switches on is called directly, from an outside address, with a harmless pool key. A hook is meant to answer only the PoolManager. Every callback should refuse.

## 6. Measuring with a probe

The probe answers the two questions that matter most, what a swap costs and whether an LP can leave, by doing both through the real pool.

### A contract that is never deployed

The probe is a small contract that implements the PoolManager's unlock callback. The scanner sends a single `eth_call` to an empty address and, using a state override, places the probe's runtime code at that address for the length of the call. The probe unlocks the PoolManager, performs a swap (or a liquidity change) on the real pool key, and reverts with the balance delta it received, or with the revert data if the action failed.

Because the call reverts, nothing settles: the probe needs no balance and no approvals, and no transaction reaches the chain. The node still executes the hook's real code, on the real pool, at the real block.

### An exact baseline

Before probing, the scanner reads the pool's state directly from the PoolManager's storage with `extsload`: the square-root price, the tick, the LP and protocol fees, and the liquidity active at the current price.

Inside one tick-spacing interval, liquidity is constant, so a swap that stays inside the interval moves the price along a single curve whose output can be computed exactly. The probe sizes its swap to stay well inside: at most a quarter of the distance to the interval's edge, and a small fraction of the liquidity. The scanner computes what the same swap would return from a pool with no hook, net of the pool fee, and compares it with what the probe actually got:

```
take = 1 - (got / paid) / (expected / amountIn)
```

The take is expressed in parts per million. It captures a tax on the input, a tax on the output, and a per-swap fee override alike, because all three change the rate received per unit paid. A negative take means the hook added to the output. The probe swaps once in each direction, because a hook can treat buys and sells differently, and that difference is often the whole story.

### The LP round trip

A second probe adds a small amount of liquidity around the current price and removes it in the same unlock. Three outcomes are possible. The liquidity comes back, and the scanner records the worst loss across the two tokens. The deposit is refused, which means the pool is closed to outside LPs and only the hook's own liquidity is in it. Or the deposit goes in and the withdrawal is refused, which is the outcome an LP most needs to know about before depositing.

## 7. The eight checks

The readings are graded into eight checks. Each is pass, caution, fail, or n/a when there was nothing to check, and each carries one sentence of evidence.

| # | Check | Pass | Caution | Fail |
|---|---|---|---|---|
| 01 | Permissions that touch your money | No return delta, no beforeRemoveLiquidity | Any of them on | Never |
| 02 | What it takes from a swap | Take at most 1% beyond the pool fee | Up to 10% | Above 10%, or a swap refused in one or both directions |
| 03 | LPs can get out | Loss of 0.1% or less | Up to 5% | Above 5%, or withdrawal refused |
| 04 | The fee is the fee | Fixed fee of 1% or less | Fixed above 1%, or dynamic | Pool fee above 10% |
| 05 | No one holds the switch | No owner, no owner-style functions | Otherwise | Never |
| 06 | The code cannot change | No proxy, no DELEGATECALL, no SELFDESTRUCT | DELEGATECALL or SELFDESTRUCT | Proxy |
| 07 | Only the PoolManager can call it | Every callback refuses a stranger | | Any callback accepts |
| 08 | Track record | 3 pools or more over 7 days or more | Otherwise | Never |

Five choices deserve explanation. Check 01 never fails, because permission is not action, and checks 02 and 03 measure the action. Check 02 fails a one-directional refusal even when the other direction is clean, because being able to buy and not sell is the failure that matters most to a trader. Check 03 is n/a, not a fail, for a pool that refuses deposits: an outside LP who cannot enter cannot be trapped. Check 04 judges the fee's level, not only its kind, because a dynamic fee is stored in the pool, the probe expects it, and it would otherwise never appear as take. The two halves of a swap's cost are judged separately and a trader pays both: one hook on Robinhood Chain, attached to 15,300 pools at the time of writing, has dynamic-fee pools standing at a 10% fee and takes 2% on top. Check 08 carries little weight on purpose. Age is not safety, but a hook from this morning has proven nothing.

## 8. Score and verdict

Each check has a weight: 10, 25, 15, 5, 15, 15, 10 and 5, adding up to 100. A pass earns the full weight, a caution half, a fail nothing, and n/a checks are left out of both the points earned and the points possible. The score is the share earned, from 0 to 100.

The verdict is a rule, not a sum. It is **danger** if any check fails, **caution** if any check from 01 to 07 is a caution, and **safe** otherwise. Safe means safe by construction: nothing that may take, no hand on a switch, a fixed fee, fixed code. The track record alone never makes a hook a caution.

The two answer different questions. The score summarises how much of what censoredHooks looks at is clean, which helps compare hooks. The verdict says whether anything threatens the user's money directly. An upgradeable proxy that is otherwise clean scores 85 and is a danger, because everything measured can be replaced in one transaction. A hook with an owner and a dynamic fee scores 90 with clean measurements and is a caution, because what was measured can change. A new hook that is safe by construction scores 98 and is safe.

## 9. Reports, the board and the API

Every hook has a report at `/app/hook/<address>`, scanned live when opened: the verdict and score, the eight checks with their evidence, the fourteen permission bits, the measured swaps and LP round trips, the bytecode facts, and the hook's functions by name.

The board at `/hooks` covers the whole chain. An indexer reads every pool creation the PoolManager has recorded and groups the pools by hook. Every hook is then scanned and listed with its pool count, measured take, LP status, owner or proxy status, score and verdict. The board is a snapshot, refreshed by re-running the index and the grading.

The API, `GET /api/scan?hook=0x…`, returns the full scan as JSON, with large numbers as strings. It is meant for software that routes trades (launchpads, aggregators, trading bots and AI agents), which can call it before sending flow through an unfamiliar hook and apply their own thresholds. Hook builders can use the same report to see what the market will see.

## 10. Limits

A scanner that overstates what it knows is worse than none.

**A scan is a measurement at one block, not a promise.** A hook with an owner can change its settings after the scan. A dynamic fee can change at the next swap. A proxy can replace its code. The report names each of these when it applies, and a fresh scan is one page load away.

**The probe is one particular trade.** It swaps a small amount, from a fresh address, with empty hook data, through its own unlock rather than a router. A hook can treat senders, sizes, routers and hook data differently, and many launch hooks have taxes that decay with time since launch. The probe measures a real trade, not the user's trade.

**Only some pools are measured.** A scan measures up to two of a hook's most recent pools that have a price. Other pools with the same hook may differ. Pools with no liquidity at the current price cannot be measured, nor can a direction in which the liquidity ends at the price, common on untraded launch pools. Both are marked thin, never refused.

**Bytecode reading is heuristic.** Selectors are found by the patterns a standard dispatcher uses; non-standard dispatchers can hide them. Names come from a public database and can be wrong or missing. Owner-style functions are matched by name. Ownership is read through `owner()` only.

**It is not an audit.** censoredHooks does not read the hook's logic, does not prove the absence of bugs, and does not judge the token traded in the pool or the people behind it. Whether the source is verified is not read and does not affect the score.

## 11. What comes next

The current version covers Robinhood Chain and scans on demand. The following are plans, not features.

**Private review for hook builders.** A builder who wants to keep the source private could submit it for review and receive the same eight checks plus a signed review of what the code does, without publishing it.

**Alerts.** A watch on a hook would notify when its fee, its measured take, its LP round trip or its code changes.

**More chains.** v4 hooks exist wherever v4 is deployed. Base, Unichain and Ethereum are the next candidates.

**On-chain attestations.** Writing scan results to the chain would let contracts, not only off-chain bots, refuse to route through a hook that has failed a check.

## 12. The token

`$CENSORED` is the project's token. It is pre-launch on Pons, the launchpad on Robinhood Chain, and it has no contract address yet. Scans, the board and the API do not require it. This paper makes no claims about its supply, fees or uses beyond this paragraph.

## Conclusion

A hook is code that runs inside someone else's trade, and most hooks on Robinhood Chain have not published that code. censoredHooks closes part of that gap without asking anyone to publish anything: it reads the permissions from the address, the structure from the bytecode, and the behaviour from a real swap and a real deposit, measured exactly and thrown away. The output is eight answers with evidence, a number and a word, stated at a block. It is not an audit and not a promise. It is a measurement anyone can repeat, of what a hook will do to a swap.
