Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aetherial-verify

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.

Apache-2.0


The problem this library exists to fix

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 why

unknown 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.


Install

npm install @aetherial/verify

Zero runtime dependencies. Node 20+. Global fetch only.

Use

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 them

Reading 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.

What it checks

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.

What it deliberately will not do

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, not false. The logic is not there to scan.
  • fundingClusters is 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 lpLocked is unknown rather 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.

Tests

npm run verify     # provenance gate + tests

The 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.

The provenance gate

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.

Design note

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.

Licence

Apache-2.0. See LICENSE.

Built by Aetherial · public build log

About

Sourced verification primitives for token contracts, liquidity and holders. Every result carries the call that produced it; unknown is never 0.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages