This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Diffyscan verifies deployed EVM smart contracts match their GitHub source by:
- Fetching verified source from blockchain explorers (Etherscan, Blockscout, zkSync, Mantle)
- Diffing against GitHub repo sources (HTML diff reports saved under
digest/<timestamp>/diffs/) - Optionally recompiling from GitHub and comparing bytecode against on-chain bytecode, handling constructor args, libraries, and immutable references
# Install dependencies
uv sync --locked --group dev
# Run tests
uv run pytest -q
# Run a single test
uv run pytest tests/test_config_loading.py -q
uv run pytest tests/test_config_loading.py::test_name -q
# Run the CLI
uv run diffyscan configs/<project>/<mainnet|testnet>/<config>.yaml
# Machine-readable run (one JSON document on stdout, logs only in digest/<ts>/logs.txt; format: docs/json-output.md)
uv run diffyscan configs/<project>/<mainnet|testnet>/<config>.yaml --json -E -G
# Format code
uv run black diffyscan/ tests/
# Run pre-commit hooks
uv run pre-commit run --all-files
# Install git hooks (pre-commit + commit-msg)
uv run pre-commit install --hook-type pre-commit --hook-type commit-msgUse the relevant repository skill; read linked references only when needed:
- new-config: create or extend a deployment config.
- validate-config: review config structure and runtime prerequisites.
- debug-diff: diagnose failed or incomplete verification.
- allowed-diffs: justify and scope expected differences or tighten wildcards.
- add-explorer: extend explorer routing or response parsing.
Entry point: diffyscan/diffyscan.py:main — parses CLI args, loads config (JSON or YAML), then runs source diff and bytecode diff for each contract.
process_config() orchestrates per-config:
run_source_diff()— fetches explorer sources and GitHub sources, generates HTML diffs viadifflib.HtmlDiffrun_bytecode_diff()— compiles GitHub sources with solc, simulates deployment viaeth_call, compares bytecode
- explorer.py — largest module; fetches/parses contracts from blockchain explorers, handles multi-chain API differences, library detection, EVM version normalization, solc compilation
- github.py — GitHub API integration, file fetching with caching, dependency resolution (e.g.
@openzeppelin/contracts-v4.4) - binary_verifier.py — EVM bytecode parsing into instructions, metadata separation, and difference analysis with immutable region annotations
- compiler.py — solc binary download (platform-aware), SHA256 verification, compilation via standard JSON
- encoder.py — ABI encoding for constructor arguments (address, bool, int/uint, bytes, tuples, arrays)
- calldata.py — resolves constructor calldata from config or explorer metadata
- node_handler.py — RPC calls:
eth_getCode,eth_chainId,eth_call - common.py — config loading (with YAML hex address validation), caching with SHA256 validation
- http_client.py — shared HTTP requests, headers, per-host pacing, bounded transient GET retries and error handling
- custom_types.py — TypedDict definitions:
Config,BinaryConfig,ExplorerContract,GithubRepo - custom_exceptions.py — exception hierarchy for handled contract errors
Configs live in configs/, laid out state-mate-style as configs/<project>/<mainnet|testnet>/<file>.yaml:
contracts: { "0xaddr": "ContractName" }
network: "mainnet" # optional legacy label, unused at runtime
explorer_hostname: "api.etherscan.io"
explorer_token_env_var: "ETHERSCAN_EXPLORER_TOKEN"
github_repo: { url, commit, relative_root }
dependencies: { "dep_name": { url, commit, relative_root } }
bytecode_comparison: { constructor_calldata, constructor_args, libraries, deployment_from, extra_sources }
# libraries keyed by the library's DEFINITION file: { "src/lib/Foo.sol": { "Foo": "0xaddr" } }
# explorer-provided libraries are auto-detected and re-keyed to the definition file for solc linking
allowed_diffs: { bytecode: {...}, source: {...} } # optional; see below
allowed_diffs declares known/expected diffs per contract so a run still passes while everything else stays verified (validated by diffyscan/utils/allowed_diffs.py). Each rule needs a reason. Prefer the tightest facet — bytecode: immutables, byte_ranges, cbor_metadata, constructor_args/constructor_calldata; source: line_ranges, files. any: true is a blanket wildcard that hides all future drift — avoid it unless a diff genuinely can't be scoped, and say why in the reason. tests/test_no_wildcard_regression.py guards against new wildcards.
bytecode_comparison.extra_sources ({ "0xaddr": ["path/to/File.sol"] }, optional) names additional source files to fetch from the configured GitHub repo/commit and add to the compilation — for contracts whose explorer-verified source set omits a file the pinned GitHub source imports (e.g. a newly-added interface). The files are fetched the same way as all others (honoring dependencies), so verification stays honest.
Supports loading from .env (see .env.example), or set directly: GITHUB_API_TOKEN, ETHERSCAN_EXPLORER_TOKEN, REMOTE_RPC_URL (for bytecode comparison).
- Formatter: black (enforced via pre-commit)
- Commit messages: validated by conventional-pre-commit
- Python >=3.11