Sourced verification primitives for token contracts, liquidity and holders.
Every result carries the call that produced it. A value we could not establish is unknown — never 0.
Token scanners are confidently wrong, and the confidence is the dangerous part.
When a scanner cannot read a pool's reserves and prints liquidity: 0, a reader sees an empty pool.
When it scans a proxy's dispatcher bytecode, finds no isBlacklisted selector and prints
blacklist: false, a reader sees a token that cannot freeze them. Both outputs are indistinguishable
from a real answer once they reach a screen, and both are the opposite of the truth.
Here that is structurally impossible. Every field is one of two things:
sourced(value, { call, at }) // established — and here is exactly how, and when
unknown(reason, { call }) // NOT established — and here is whyunknown is a first-class result, not an error and not a zero. It has known: false and
value: null, so a caller cannot read a number out of it by accident.
mintAuthority: UNKNOWN — 0x8335…2913 is a proxy (zeppelinos.implementation), so its bytecode is a
dispatcher and the token's logic is not present to scan. This check established NOTHING about
mintAuthority — it is not evidence of absence. Re-run against the implementation at 0x2ce6311d…
That is Circle USDC on Base. A scanner that reports blacklist: false there is telling you a
regulated stablecoin cannot freeze your account. This library refuses to answer instead.
npm install @aetherial/verifyZero runtime dependencies. Node 20+. Global fetch only.
import { checkContract, createEvmAdapter, explain } from '@aetherial/verify';
const adapter = createEvmAdapter({ chain: 'base' });
const report = await checkContract('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', {
chain: 'base', adapter,
});
console.log(explain(report)); // renders unknowns rather than hiding themReading results in code:
import { fullyKnown } from '@aetherial/verify';
if (report.mintAuthority.known && report.mintAuthority.value.detected) {
// supply can be inflated — and report.mintAuthority.call says how we know
}
if (!fullyKnown(report.mintAuthority, report.blacklist)) {
// do NOT present this as a clean bill of health
}Copy .env.example to .env and set an RPC URL per chain you use.
| Area | Checks |
|---|---|
contract/ |
Mint authority · minter roles · pausable + live paused state · blacklist · proxy & upgradeability (EIP-1967, EIP-1822, pre-1967 zeppelinos) · ownership renounced (three-way: no owner() ≠ zero owner ≠ live owner) · transfer tax, measured by simulation · honeypot simulation |
liquidity/ |
V2 pair + V3 pool discovery · reserves and depth · LP disposition (burned / locked / freely-held / unidentified) · real slippage on a realistic sell, with the full impact curve |
holders/ |
Top-N concentration from Transfer replay · funding-source clustering — wallets funded from one source counted as one · supply reconciliation |
solana/ |
Mint authority · freeze authority · metadata mutability + update authority (Metaplex PDA, derived locally) · top holders with owner resolution — pool vaults are identified and excluded rather than reported as whales |
adapters/ |
EVM (BSC, Base) and Solana, normalised output shape |
Every check is independently callable. checkContract, checkLiquidity and checkHolders just
compose the checks in their area into one report.
Stated plainly, because a security tool that hides its limits is the problem it claims to solve:
- It never reports a transfer-tax rate from a bytecode scan. Selector presence cannot yield a
percentage. The rate comes from simulation or it is
unknown. - A negative bytecode scan on a proxy is
unknown, notfalse. The logic is not there to scan. fundingClustersis one-hop. Multi-hop chains, native-gas funding and CEX-laundered funding read as unrelated wallets. It catches lazy sybils, not careful ones.- Lock expiry is not read. A lock ending tomorrow currently scores like a five-year lock.
- A holder ranking over a partial block window is withheld, not published as if it were balances.
- Locker registries are incomplete by choice. Where a chain's locker list is unverified it is
empty, so
lpLockedisunknownrather than a false "unlocked". - Not implemented: EIP-2535 diamonds, non-standard proxies, source-verification status, whether an owner is a multisig or timelock.
None of these is a bug to be quietly fixed later. Each is a limit stated on the field itself, in
the note a caller can read at runtime.
npm run verify # provenance gate + testsThe suite runs entirely offline — no network, no flakes — against recorded mainnet fixtures
(test/fixtures/*.json, each carrying its source endpoint and capture timestamp) and declarative
fake chains. Re-record fixtures with node tools/record-fixtures.mjs.
Two independent layers cover the same modules on purpose: *.rpc.test.mjs fakes at the fetch
boundary and proves chain decoding; *.fixtures.test.mjs replays recorded responses and proves
composition logic. The unknown paths are tested as hard as the success paths — a dead node must
yield known: false, value: null on every field, and no check may return a bare 0 or false
for something it did not establish.
npm run gate fails the build if any value that was not established is passed to sourced().
It is not a grep. A grep for sourced(false is defeated by one rename —
const LOOKS_FINE = false; sourced(LOOKS_FINE, …) — so the gate resolves module-local constants
to their initialisers as well. Genuinely established negatives (the node answered, and the answer
was "nothing exists there") are allowed through an explicit, justified marker:
// provenance-ok: the node answered and reported no account at this address; absence is
// the read fact here, not a default standing in for a call we failed to make.
out.isMint = sourced(MINT_ACCOUNT_ABSENT, { call, note: … });Every exemption is printed on each run, so they stay countable rather than accumulating unread. The gate is mutation-tested against all four ways round it: a bare literal, a constant indirection, an empty marker, and a marker whose "reason" is a single word.
Why unknown is not zero — the reasoning behind the provenance contract, the three places it cost us something, and how the gate is enforced mechanically.
Apache-2.0. See LICENSE.
Built by Aetherial · public build log