Skip to content

About

Adversarial on-chain forensics for EVM tokens using raw RPC: sell simulation via eth_call state overrides, LP-custody classification from bytecode, funder-tree holder clustering and wash-ring detection.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

evm-token-forensics

Adversarial on-chain forensics for EVM tokens using raw RPC: sell simulation via eth_call state overrides, LP-custody classification from bytecode, funder-tree holder clustering and wash-ring detection.

Why it exists

Most token-safety tooling (honeypot checkers, holder-graph tools, security scanners) only covers the dozen or so largest chains. On a new chain they either return nothing or return an empty result that looks like a clean bill of health. This toolkit and its method were developed against a new Arbitrum Orbit L2 that none of the mainstream scanners indexed, where the question "can a holder actually sell this token?" had no tool that could answer it.

The approach is to fall back to primitives every EVM chain has (eth_call, eth_getCode, eth_getStorageAt, eth_getLogs), treat third-party indexers as leads rather than truth, and make every conclusion carry its evidence level.

What it demonstrates

  • Verification over reporting. Every load-bearing claim is tagged VERIFIED, REPORTED, PLAUSIBLE or REFUTED, and a missing or empty data source maps to UNKNOWN, never SAFE.
  • Simulation as evidence. Sellability is proven by simulating a real holder's sell against the live pool inside one eth_call, with nothing broadcast, instead of trusting a scanner's sell_tax: 0 field.
  • Data modelling of ownership. A flat holder list is collapsed into clusters by funding source, and supply sitting in pools, lockers and burn addresses is separated from supply owned by people before any concentration number is read.
  • Governance of the method itself. A gated, phased process (pin the real contract first, quick-kill checks in a fixed order, a refutation pass before anything is published) plus a written log of the failure modes that produced wrong answers and how each was fixed.
  • Honest limits. Reports state what could not be checked (bridge-funded wallets, point-in-time snapshots, v4 settlement) rather than implying certainty.

Architecture

flowchart LR
  subgraph sources [Data sources]
    RPC[(EVM JSON-RPC)]
    IDX[GeckoTerminal / DexScreener]
    GP[GoPlus]
    BS[Blockscout explorer]
  end
  P0[Phase 0: pin the real contract] --> H[harvest.py: market and security snapshot]
  H --> S[simulate.py: sell simulation]
  S --> F[forensics.py: holder clusters and LP custody]
  F --> SO[social.py: propagation metrics]
  SO --> V{Refutation pass}
  V --> R[report.py: HTML report]
  RPC --> H
  RPC --> S
  RPC --> F
  IDX --> H
  IDX --> S
  GP --> H
  GP --> F
  BS --> F
Loading

How the sell simulation works (the core technique):

sequenceDiagram
  participant C as eth_call (from holder)
  participant P as Uniswap V3 pool
  participant Q as quote token (e.g. WETH)
  participant H as holder address (code overridden)
  participant T as token under audit
  C->>P: swap(recipient = holder, exact input)
  P->>Q: transfer quote asset to holder
  P->>H: uniswapV3SwapCallback(amount0, amount1)
  H->>T: transfer(pool, owed)  [injected 69-byte callback]
  P->>T: balanceOf(pool): reverts "IIA" if the pool received less than owed
  P-->>C: (amount0, amount1) = the pool's own settlement
Loading

The holder's code is replaced for the duration of the call, but its storage is not, so the token still sees the holder's real balance. A settled V3 swap is positive proof that no transfer tax applied on that path, because the pool re-checks its own balance after the callback.

File What it does
METHOD.md The phased method, prime directives, observed patterns and failure modes
scripts/harvest.py Deterministic snapshot: all pools, aggregate liquidity, V/L turnover, flow, OHLCV, GoPlus, bytecode identity, EIP-1967 proxy slots
scripts/simulate.py Sell simulation: ERC-20 transfer probe, laddered V3 swaps ($100 to $50k), ERC-20 fee-on-transfer measurement for v4-only tokens
scripts/forensics.py Holder identity (contract vs EOA vs burn), first-funder tracing, cluster collapse, LP-holder custody classification
scripts/social.py Handle resolution, free Telegram counts, narrative share, copy-paste clustering, top-poster (bot) share over a scraped post file
scripts/report.py Renders a standalone HTML report from a JSON spec
scripts/chains.py Chain registry and EVM_* environment overrides
references/ Quick-kill thresholds, endpoint map and RPC-only fallbacks, report format

Scripted vs manual

Not every step in the method is automated. What the scripts do today, and what is still done by hand following METHOD.md:

Step Status
Aggregate liquidity, V/L turnover, txs-per-buyer Scripted (harvest.py)
EIP-1967 proxy check, contract-vs-EOA via eth_getCode Scripted (harvest.py, forensics.py)
Sell simulation (V3 swap ladder, ERC-20 fee-on-transfer delta) Scripted (simulate.py)
First-funder tracing and cluster collapse (one hop) Scripted (forensics.py)
LP holder classification (burned / contract / naked EOA) Scripted (forensics.py)
Copy-paste rings and bot share in social data Scripted (social.py)
PUSH4 selector scan to prove a locker cannot withdraw Manual (method in METHOD.md)
Transfer-graph SCC / short-cycle wash-ring detection Manual
Launch-block (B0 to B0+3) sniper and bundler analysis Manual
Multi-hop funder recursion, multi-block anvil fork sweeps Manual
Phase 0 contract pinning and impostor census Manual

Key design decisions

  • Tri-state verdicts, and tooling failure is never a pass. A probe that fails for a tooling reason (no V3 pool, RPC refuses overrides, holder too small) reports UNKNOWN. Only an executed swap earns SELLABLE. This is enforced in simulate.py and in how harvest.py flags empty GoPlus results.
  • State overrides instead of a local fork. Replacing code at a real holder's address inside a single eth_call needs no node, no fork and no private key, and works on any RPC that supports overrides. anvil forks are reserved for multi-block checks.
  • Measure inside one call when there is no tracer. Public L2 RPCs rarely expose debug_traceCall, so the fee-on-transfer probe injects a small contract that reads balanceOf(pool), transfers, and reads again, all in one call against one state.
  • Size probes to the real balance. An oversized transfer reverts for insufficient balance, which looks exactly like a blacklist. Every probe is capped at the impersonated holder's actual balance, so the simulator does not invent honeypots.
  • Say what the sell pays out in. A swap that settles into another illiquid token proves the token moves, not that it converts to money, so the verdict names the quote asset.
  • Liquidity is the sum of all pools, and the counter-asset is the truth. Main-pool-only numbers produced false alarms, and aggregator USD figures can be inflated by one-sided positions. The method reads the WETH or stablecoin side of the pool directly.
  • Standard library only. No dependencies (urllib, json), so it runs anywhere Python does. Function selectors are hardcoded because keccak is not in the standard library.

How to run / test

Requires Python 3.11 or newer. No third-party packages.

Offline checks (no network):

python3 -m py_compile scripts/*.py
python3 -m unittest discover -s tests -v
python3 scripts/report.py examples/report-spec.example.json /tmp/example-report.html

The unit tests run the simulator's hand-assembled bytecode (the swap callback and the fee-on-transfer probe) through a minimal EVM interpreter against a fake ERC-20, and check calldata encoding, chain config, report rendering and social metrics.

Against a live chain, configure it in scripts/chains.py or with environment variables, then run the phases in order. The address below is illustrative:

export EVM_RPC_URL=https://<YOUR_RPC_URL>          # must support eth_call state overrides for simulate.py
export EVM_CHAIN_ID=<CHAIN_ID>                     # optional, enables GoPlus
export EVM_BLOCKSCOUT_URL=https://<EXPLORER_HOST>  # required by forensics.py
export EVM_GT_NETWORK=<GECKOTERMINAL_SLUG>         # defaults to the <chain> argument
export EVM_V4_DEX_LABELS=<DEX_ID>,...             # optional, extra dex ids to treat as Uniswap v4
export EVM_MONEY_QUOTES=<SYMBOL>,...              # optional, extra quote symbols that count as money

python3 scripts/harvest.py   mychain 0x000000000000000000000000000000000000dEaD out
python3 scripts/simulate.py  mychain 0x000000000000000000000000000000000000dEaD --json out/sim.json
python3 scripts/forensics.py mychain 0x000000000000000000000000000000000000dEaD 40
python3 scripts/social.py handles mychain 0x000000000000000000000000000000000000dEaD

Every script prints its usage with --help.

Status & limitations

  • Personal research toolkit, not a product. Not packaged, not deployed, no users other than the author. It was used in August 2026 to audit a batch of 16 live tokens on one Orbit L2.
  • The published version has not been re-run against a live chain. For publication, the chain-specific configuration was moved into scripts/chains.py and token-specific material was removed. The offline tests cover the bytecode, encoding, config, report and social-metrics logic; the network paths are exercised only by a live run.
  • Uniswap v4 settlement is not simulated. For v4-only tokens the simulator falls back to the ERC-20 layer and reports pool settlement as UNVERIFIED.
  • Funder traces stop at bridges and exchanges, and wallet-age heuristics are weak on a young chain where every wallet is new. Reports say so rather than implying a clean result.
  • Every check is a point-in-time snapshot. An owner can arm a trap after the check.
  • Thresholds are triage triggers from one chain, not calibrated verdicts.
  • Social scraping depends on a paid third-party service and is not included.
  • The output is research, not investment advice.

How this was built

Built by directing AI coding agents (Claude Code / Cursor). Wol wrote the specs, architecture decisions, gates and reviews.

License

MIT. See LICENSE.

About

Adversarial on-chain forensics for EVM tokens using raw RPC: sell simulation via eth_call state overrides, LP-custody classification from bytecode, funder-tree holder clustering and wash-ring detection.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages