Docs · Developers

The scan API

GET /api/scan?hook=0x… returns the full scan of one hook as JSON. Every field, its units, and how to read it.

The API returns exactly what a hook's page shows, as JSON. It runs a fresh scan at the latest block of Robinhood Chain.

Request

GET /api/scan?hook=0x<hook address>

One parameter, hook, an address in any letter case. No key, no account.

Responses

Status Body
200 The scan, described below.
400 { "error": "hook must be an address" } when hook is missing or not an address.
502 { "error": "…" } when the scan could not complete, for example because the chain's RPC did not answer. Retry later.

The response allows any origin (access-control-allow-origin: *), so it can be called from a browser. It is marked cacheable by shared caches for 120 seconds, so two requests close together can return the same scan: compare the block field if freshness matters. A scan can take several seconds; set a timeout of a minute.

Numbers that do not fit in a JavaScript number (amounts, liquidity, prices) are returned as strings of decimal digits. Parse them with BigInt.

The scan

Field Type Meaning
hook string The hook address, checksummed.
block number The block the scan read at.
bits number The 14 permission bits as an integer (0 to 16383).
flags array The fourteen permissions, one object each. See below.
code object or null Facts read from the bytecode. null if there is no contract at the address.
proxy object or null Proxy facts.
owner string or null What owner() returned, or null if it reverted or returned the zero address.
functions array { selector, name } for each selector found. name is null when the database has none.
history object { pools, firstTime, lastTime }: how many pools use the hook, and the Unix times of its first and latest pool (null if it is not in the index yet).
pools array The pools that were measured. See below.
openCallbacks array Names of the callbacks a stranger could call. Should be empty.
checks array The eight checks: { id, title, status, detail }. id is "01" to "08", status is pass, caution, fail or n/a.
score number 0 to 100.
verdict string safe, caution or danger.

If code is null, the address holds no contract: the other fields are empty and there is nothing to route through.

`flags[]`

Field Meaning
bit 13 down to 0.
key The permission's name, e.g. beforeSwapReturnDelta.
label The same in words.
on Whether the address switches it on.
risk 0: runs code only. 1: can refuse deposits or set the fee. 2: can take from swaps or LPs, or refuse a withdrawal.
does One sentence on what it allows.
fn The callback's signature, or null for the four return-delta permissions, which ride on another callback.

`code`

Field Meaning
size Bytecode size in bytes.
selectors Selectors the dispatcher compares against.
delegatecall, selfdestruct, create Whether each opcode appears.
calls Number of CALL and CALLCODE opcodes.
addresses Constant addresses pushed by the code.

If the hook is a proxy or a clone, code describes the implementation, since that is the code that runs.

`proxy`

{ implementation, admin, beacon, clone }. The first three are addresses or null, read from the EIP-1967 slots (implementation is also set for an EIP-1167 clone). clone is true for a minimal clone.

`pools[]`

Field Meaning
id The pool id.
key { currency0, currency1, fee, tickSpacing, hooks }. A fee with the 0x800000 bit set (8388608) is a dynamic fee.
symbols The two tokens' symbols, ETH for the zero address.
state { sqrtP, tick, protocolFee, lpFee, liquidity }, read from the PoolManager. sqrtP and liquidity are strings.
swaps One entry per direction. See below.
roundTrip The LP round trip. See below. null if not tried.

A measured swap:

{ "zeroForOne": true, "ok": true, "amountIn": "…", "paid": "…", "got": "…",
  "expected": "…", "feePpm": 3000, "takePpm": 25000 }

zeroForOne is true when currency0 goes in. expected is what a hookless pool would have paid out. feePpm is the pool fee and takePpm what the hook took beyond it, both in parts per million: 10,000 is 1%. A negative takePpm means the hook added to the output.

A swap that was not measured:

{ "zeroForOne": false, "ok": false, "why": "blocked", "reason": "…" }

why is blocked only when the swap reverted; reason then holds the hook's revert reason when it left one, with custom errors named from the signature database where possible. why is thin when there was nothing to measure: no liquidity at the price, too little to measure (reason says which), or a swap that went through and consumed nothing because the liquidity ends at the current price on that side ("no liquidity below the price" or "no liquidity above the price"). A thin direction is not a refusal.

A round trip:

{ "ok": true, "lossPpm": 2, "added": ["-…", "-…"], "removed": ["…", "…"] }
{ "ok": false, "stage": "add", "reason": "…" }

added and removed are the balance changes for currency0 and currency1: negative is paid in, positive is received. lossPpm is the worst of the two tokens. stage: "add" means deposits are refused (a closed pool); stage: "remove" means the withdrawal was refused.

Stability

The fields above are the ones the site itself renders from. The API is new and may gain fields. Write your client to ignore fields it does not know and to treat a missing field as unknown, never as a pass.