How this scanner
reaches its numbers
Every check, where its data comes from, and what it cannot know. Pons Scanner never says a token is safe — it reports what is readable on-chain and links you to the source.
The two numbers
Score and coverage are separate and are never combined into a single badge.
- Coverage, 0–100%
- Purely deterministic: the share of attempted checks that returned real data. No model is involved. A check that returns Unknown lowers coverage automatically.
- Risk score, 0–100
- Findings are detected by rules. Each carries a point band rather than a fixed value, and a model allocates within those bands and writes the interpretation. The score is the exact sum of the allocations. Every allocation is validated against its band before anything renders; a malformed or out-of-band response is retried once, and a second failure falls back to band midpoints, which the report labels as rule-derived.
Unknown is never a risk signal
This is the rule the rest of the design bends around.
Any finding whose confidence is unknown has its point band forced to zero in code, before the model is ever shown it. An Unknown lowers coverage and appears in the report with the reason it is unknown. It can never add points.
The reason is concrete. A token that is legitimately hard to index would otherwise score as critical purely for being unreadable, with the same missing data counted twice — once as reduced coverage, and again as risk. That is a real failure mode in existing tools, and this scanner is built not to reproduce it.
Every Unknown in a report states why it is unknown. A bare “Unknown” is treated as a bug.
Checks, sources, and limits
What each module reads, and what it cannot see.
Contract
Source. Chain (bytecode, ERC-20 getters, EIP-1967 storage slots) and Blockscout (verification metadata).
Cannot know. If the source is not verified, nothing about the contract’s behaviour can be established from the bytecode alone. Verification also does not mean the code is correct or benign — only that it is readable.
Ownership and permissions
Source. Chain for owner() and role state. Verified source for capabilities, parsed for access-control modifiers and reachability.
Cannot know. Capabilities are read from the source only. Where the source is unverified, every capability is reported Unknown — we do not guess from the ABI selector list, because a function called mint() that always reverts is not mint authority, and a differently named function guarded by onlyOwner may well be. Deeply indirect authority (a call through an external contract) may be missed.
Transfer mechanics
Source. Chain. 35 getter names are probed across the common tax-token conventions.
Cannot know. There is no standard for how tax values are stored. Where the contract exposes no denominator, the scale is inferred and the assumption is printed alongside the raw value. A token using a getter name outside the probed set reports Unknown rather than zero.
Holder distribution
Source. Blockscout supplies the holder addresses; every balance is then re-read from the contract, because indexed balances go stale.
Cannot know. Only the top holders the explorer returns are examined. Wallets controlled by one person across several addresses are counted separately — on-chain data cannot tell them apart.
Liquidity
Source. Chain. The Uniswap V3 factory is asked for every combination of 3 quote tokens and 4 fee tiers.
Cannot know. Only Uniswap V3 pools against the listed quote tokens are found. Liquidity on another venue, another AMM, or against an unlisted quote token is invisible to this check. Depth is the quote side only, valued using the explorer’s ETH reference price.
V4 liquidity
Source. Chain. V4 keeps every pool inside one contract, so pools are found from its Initialize logs — both currencies are indexed — and price, fee and liquidity are read out of that contract’s storage directly.
Cannot know. Reserves cannot be read per pool, because every V4 pool’s tokens sit in the same balance. Depth is therefore established by simulating a sale of a fixed share of supply through the quoter, and only the deepest pools are simulated; the rest are listed with what was read. A pool’s hook is reported as present, never interpreted — this scanner does not read hook code.
LP status
Source. Chain. Position NFTs are resolved through the V3 position manager and their owners classified.
Cannot know. Lock duration and release terms are not read — a position held by a locker may unlock tomorrow. A contract we cannot identify is reported as unidentified rather than assumed safe. Positions minted directly to a pool rather than through the position manager cannot be resolved as NFTs.
Deployer
Source. Blockscout for the creation transaction, creator, counters and history.
Cannot know. Account age is only knowable when the account’s full history fits inside the bounded page walk; beyond that it is reported Unknown. The whole module also runs against a four-second budget and reports Unknown if the explorer does not answer inside it. A new account is not evidence of intent, and an old one is not evidence of good intent.
pons launch
Source. Chain. The pons launch factories, the launched-token record, the token itself, and the locker.
Cannot know. Every parameter is read per token — none is assumed from documentation, because they vary. Creator-supplied links are stored on-chain but are not verified by anyone. Creator history across launches needs an index and is not built yet.
Two block numbers
Robinhood Chain is an Arbitrum Orbit chain, and mixing its two block counters produces plausible-looking wrong answers.
Anything a contract stores or emits — a launch block, a restriction end block — is in the L1 namespace, because that is what Solidity’s block.number returns. Anything from eth_blockNumber or an event log is L2.
We measured 0.1s per L2 block against 11.93s per L1 block — the two counters tick about 119× apart, yet their absolute values currently sit within roughly 10% of each other because the L2 started later. A mistaken comparison therefore yields a believable wrong answer rather than an obviously absurd one, so the two are separate types in this codebase and cannot be compared by accident.
Point bands are provisional
Stated plainly rather than implied to be settled.
The bands below have not been calibrated against a real distribution of tokens yet. They are a starting point. A calibration script exists to scan a large sample and report the score distribution and where in each band the model tends to land; bands that always resolve to the same end will be collapsed to fixed values.
Treat the score as a way to rank what to read first, not as a measurement.
| Finding | Severity | Band |
|---|---|---|
| Source code is not verified Nobody outside the deployer can read what this contract actually does. Behaviour cannot be checked against its bytecode. contract.unverified | high | 6–22 |
| Source is only partially verified The explorer matched the source without an exact metadata hash, so the published source may differ in comments or compiler settings. contract.partially_verified | low | 1–4 |
| Contract is an upgradeable proxy The logic behind this address can be replaced by whoever controls the proxy admin. Today’s behaviour is not a guarantee of tomorrow’s. contract.proxy_upgradeable | medium | 4–12 |
| Proxy points at an unverified implementation The proxy is readable but the logic it delegates to is not. Verifying the proxy alone tells you nothing about what actually runs. contract.proxy_impl_unverified | high | 6–20 |
| Ownership is not renounced A single address still holds the owner role. Whether that matters depends entirely on what the owner-only functions can do. ownership.not_renounced | medium | 3–10 |
| Supply can be increased A privileged function can create new tokens, diluting every existing holder. ownership.mint_authority | high | 6–20 |
| Addresses can be blocked from transferring A privileged function can stop specific addresses from moving their tokens. ownership.blacklist_authority | high | 4–18 |
| Transfers can be paused or gated A privileged function can halt trading for everyone. ownership.pause_authority | high | 4–18 |
| Fees can be changed after launch Current tax rates are not fixed. A privileged function can raise them. ownership.fee_change_authority | medium | 3–12 |
| Transaction or wallet limits can be changed Maximum transaction and wallet sizes can be adjusted by a privileged address. ownership.limit_change_authority | low | 1–6 |
| Implementation can be swapped A privileged function can repoint the proxy at entirely different logic. ownership.upgrade_authority | high | 6–20 |
| High transfer tax A large share of each trade is taken as tax. transfer.high_tax | high | 4–18 |
| Moderate transfer tax A noticeable share of each trade is taken as tax. transfer.moderate_tax | medium | 2–9 |
| Small transfer tax A small tax applies to trades. transfer.low_tax | low | 0–3 |
| Sell tax is much higher than buy tax Buying is cheap and selling is expensive, which makes exiting a position materially worse than entering one. transfer.asymmetric_tax | high | 4–16 |
| Maximum transaction size is restrictive Single transfers are capped at a small fraction of supply, which constrains how quickly a position can be exited. transfer.restrictive_max_tx | medium | 2–8 |
| Maximum wallet size is restrictive Any one address can hold only a small fraction of supply. transfer.restrictive_max_wallet | medium | 2–8 |
| One wallet holds a majority of circulating supply A single independent address can move the market on its own. Pools, burn addresses and the token contract are excluded from this figure. holders.single_wallet_majority | high | 6–20 |
| One wallet holds a large share of supply The largest independent holder is big enough to move price substantially. holders.single_wallet_large | medium | 3–12 |
| Top ten wallets hold most of the supply Supply is concentrated among a small number of independent addresses. holders.top_ten_concentrated | medium | 3–12 |
| Very few holders The holder count is low enough that there is little distribution to speak of. holders.very_few | medium | 2–10 |
| No liquidity pool found No Uniswap V3 pool exists for this token against any quote token we check, at any fee tier. That is the only venue this scanner reads, so it means no readable V3 market — not that no market exists anywhere. liquidity.none_found | high | 2–20 |
| Very thin liquidity Pool depth is small enough that an ordinary trade would move the price sharply. liquidity.very_thin | high | 4–16 |
| Thin liquidity Pool depth is modest; larger trades will suffer noticeable slippage. liquidity.thin | medium | 2–9 |
| Liquidity position is held by a regular wallet The LP NFT sits in an externally-owned account, so whoever holds the key can withdraw the pooled liquidity at any time. lp.withdrawable_by_eoa | high | 6–20 |
| Liquidity position is held by an unidentified contract The LP NFT is held by a contract we could not identify as a locker. It may or may not be able to withdraw. lp.held_by_contract | medium | 2–10 |
| Liquidity position is burned The LP NFT was sent to a burn address, so the position cannot be withdrawn. This is a fact about the position, not a statement about the token. lp.burned | info | 0–0 |
| Liquidity position is held by a locker contract The LP NFT is held by a contract that looks like a locker. Lock terms and expiry are not read by this scanner. lp.locked | info | 0–0 |
| V4 pools exist but could not fill a sale Every V4 pool for this token was asked, by simulation, to buy a small fixed share of supply, and none could complete it. Pooled tokens are present but the quote side is too thin to sell into at that size. v4.no_fill | medium | 0–8 |
| V4 pool fee takes most of the trade A Uniswap V4 pool sets its own swap fee, and this token has pools charging a fee large enough that selling into them returns almost nothing. The liquidity is real and the pool quotes — the fee is what makes the position unexitable, which is a trap structure rather than a depth problem. v4.extreme_fee | high | 5–18 |
| V4 pool runs a hook contract A V4 hook runs on every swap against the pool and can change what a trade does, including blocking it. This scanner reads that a hook is attached, not what it does — so this is a pointer to read the hook, not a finding about behaviour. v4.hooked | low | 1–4 |
| Deployer account is new The deploying account has little history on this chain, so there is nothing to judge it by. deployer.new_account | medium | 2–8 |
| Deployer has little transaction history The deploying account has made few transactions. deployer.low_activity | low | 1–5 |
| Creator bought a large share at launch The launch configuration gave the creator a sizeable opening position. pons.large_creator_buy | medium | 2–10 |
| Launch restrictions are still active Per-token buy limits from the launchpad are still in force. They expire on their own. pons.restriction_window_open | info | 0–0 |
| Creator fee payout has been redirected Fee income no longer goes to the original creator address, which normally indicates a community takeover. pons.fee_redirected | info | 0–0 |
| Launch has not graduated Paired principal has not reached the graduation threshold configured for this launch. pons.not_graduated | info | 0–0 |
Determinism
Why the same token scanned twice returns the same score.
The model is pinned to claude-opus-5, and the prompt is versioned (2026-07-30.1). Assessments are cached against a stable hash of the sorted finding ids, their evidence, their bands, the model and the prompt version — identical evidence returns the identical assessment.
The scoring model is pinned and called with no sampling parameters; the current API rejects temperature outright. Identical evidence returns an identical cached assessment.
What the model may not do
Enforced in the prompt and validated on the output.
- It may not output verdict words. Responses containing them are rejected and retried.
- It may not invent a finding it was not given, or allocate to an unknown finding id.
- It may not allocate outside a band, and the score must equal the sum of allocations.
- It may not reference data it was not supplied.
- It never sees a finding’s band as anything but zero when that finding is Unknown.