一键导入
add-explorer
// Add support for a new blockchain explorer API to diffyscan. Use when a new chain or explorer type needs to be supported.
// Add support for a new blockchain explorer API to diffyscan. Use when a new chain or explorer type needs to be supported.
| name | add-explorer |
| description | Add support for a new blockchain explorer API to diffyscan. Use when a new chain or explorer type needs to be supported. |
| disable-model-invocation | true |
| argument-hint | ["explorer-name"] |
Guide through adding support for a new blockchain explorer to diffyscan.
Most new chains need NO code changes. Work through this list in order:
Etherscan v2 API (preferred) -- If the chain is listed on Etherscan's v2 supported chains, create a config with "explorer_hostname": "api.etherscan.io" and "explorer_chain_id": <chain-id>. Done. No code changes. This uses the endpoint https://api.etherscan.io/v2/api?chainid=<id>&module=contract&action=getsourcecode&address=<addr>.
Legacy Etherscan-compatible hostname -- If the chain has its own Etherscan-style API (e.g. api.basescan.org, api-optimistic.etherscan.io), create a config with just "explorer_hostname" (no explorer_chain_id). The default fetcher handles it. No code changes.
Existing Blockscout domain -- If the chain uses a Blockscout instance whose domain already ends with one of the recognized suffixes (see dispatcher below), create a config with that hostname. No code changes.
New Blockscout domain -- If the chain uses Blockscout but with an unrecognized domain, add the domain suffix to _get_explorer_fetcher(). One-line change.
Entirely new API format -- Only if the explorer has a non-Etherscan, non-Blockscout API do you need a new fetcher function.
diffyscan/utils/explorer.pyAll explorer logic lives in one file. The call flow is:
get_contract_from_explorer() # public entry point, handles caching
-> _get_explorer_fetcher() # dispatcher: hostname -> (fetcher_fn, requires_token)
-> fetcher(...) # one of the four fetchers below
-> _validate_contract_name() # verify name matches config
_get_explorer_fetcher(explorer_hostname)This function maps hostnames to fetcher functions using prefix/suffix matching. It returns a tuple of (fetcher_function, requires_token: bool).
Current routing rules (checked in order):
| Condition | Fetcher | Token required? |
|---|---|---|
hostname.startswith("zksync") | _get_contract_from_zksync | No |
hostname.endswith("mantle.xyz") | _get_contract_from_mantle | No |
hostname.endswith("lineascan.build") | _get_contract_from_etherscan (token forced to None) | No |
hostname.endswith(...) any of: mode.network, blockscout.com, swellnetwork.io, lisk.com, inkonchain.com, routescan.io, monadvision.com | _get_contract_from_blockscout | No |
| Default (everything else) | _get_contract_from_etherscan | Yes |
When requires_token is True, the caller passes (token, hostname, address, chain_id). When False, the caller passes (hostname, address) only.
_get_contract_from_etherscan(token, hostname, address, chain_id=None)Handles both Etherscan v2 and legacy Etherscan APIs.
chain_id is set): https://{hostname}/v2/api?chainid={chain_id}&module=contract&action=getsourcecode&address={address}&apikey={token}chain_id is None): https://{hostname}/api?module=contract&action=getsourcecode&address={address}&apikey={token}{"message": "OK", "result": [{"ContractName": ..., "SourceCode": ..., "CompilerVersion": ..., ...}]}SourceCode starts with {{, it is treated as a JSON solc standard input (stripped of outer braces and parsed)_build_source_files() + _build_solc_input() construct the solc input from flat sourceAdditionalSources, but without the original compiler settings (e.g. remappings, via-IR). This can cause bytecode mismatches even when source diffs are clean._get_contract_from_blockscout(hostname, address)https://{hostname}/api/v2/smart-contracts/{address}name, file_path, source_code, additional_sources (list of {file_path, source_code}), compiler_version, compiler_settings, optimization_enabled, optimization_runs, constructor_args, evm_version, external_libraries (list of {name, address, ...})optimization_runs and optimizations_runs (typo in some Blockscout versions); the code checks both_get_contract_from_zksync(hostname, address)https://{hostname}/contract_verification/info/{address}{"verifiedAt": ..., "request": {"ContractName": ..., "CompilerVersion": ..., "sourceCode": {"sources": ...}}}name, sources, compiler -- does NOT go through _build_contract_payload or _attach_contract_metadatasolcInput. Downstream code (run_source_diff, run_bytecode_diff) expects contract["solcInput"], so zkSync contracts use a different code path. New fetchers should follow the standard shape returned by _build_contract_payload (name, compiler, solcInput, plus optional constructor_arguments, evm_version, libraries)._get_contract_from_mantle(hostname, address)https://{hostname}/api?module=contract&action=getsourcecode&address={address}FileName (not ContractName) as primary path, and AdditionalSources list uses Filename/SourceCode keys (note capitalization differences)_build_source_files(primary_path, primary_source, additional_sources, *, path_key, content_key) -- Assembles a {path: {"content": source}} dict. The path_key and content_key parameters handle the field name differences between explorers._build_solc_input(source_files, *, optimizer_enabled, optimizer_runs, settings=None) -- Wraps source files into a standard solc JSON input with optimizer config and output selection._build_contract_payload(name, compiler, solc_input, *, constructor_arguments, evm_version, libraries) -- Assembles the final contract dict and calls _attach_contract_metadata()._attach_contract_metadata(contract, source_files, constructor_arguments, evm_version, libraries) -- Normalizes and attaches constructor args (hex string without 0x prefix), EVM version (None if "default"), and libraries (resolved to {path: {name: address}} format). Libraries can come from explorer response OR from solcInput.settings.libraries; both are merged.config_samples/<chain>/<config>.json:
{
"contracts": { ... },
"explorer_hostname": "api.etherscan.io",
"explorer_token_env_var": "ETHERSCAN_EXPLORER_TOKEN",
"explorer_chain_id": <chain-id>,
"github_repo": { ... }
}
uv run diffyscan <config> --yes --cache-explorer_get_explorer_fetcher() in diffyscan/utils/explorer.py (the any(explorer_hostname.endswith(domain) for domain in [...]) block)"explorer_hostname": "<blockscout-hostname>"uv run diffyscan <config> --yes --cache-explorerUnderstand the API: Document the endpoint URL, response shape, and which fields map to contract name, source code, compiler version, optimizer settings, constructor args, EVM version, and libraries.
Add hostname detection in _get_explorer_fetcher(): Add a new elif branch with startswith() or endswith() matching. Return (your_fetcher, False) -- most non-Etherscan explorers do not use API tokens.
Implement the fetcher _get_contract_from_<name>(hostname, address):
fetch(url).json()_build_source_files() to assemble sources (pass the correct path_key/content_key for the response format)_build_solc_input() to wrap into solc input_build_contract_payload() to get normalized metadata(hostname, address) (two args). If it does need a token, use (token, hostname, address, chain_id=None) (four args) and set requires_token=True in the dispatcher.Add tests in tests/test_explorer_utils.py -- follow the existing pattern: monkeypatch fetch to return a DummyResponse, call get_contract_from_explorer(), assert the result.
Add a config sample in config_samples/<chain>/.
| Field | Required | Description |
|---|---|---|
explorer_hostname | Yes | API hostname (e.g. api.etherscan.io, explorer.mode.network) |
explorer_chain_id | No | Chain ID for Etherscan v2 API; omit for legacy or non-Etherscan explorers |
explorer_token_env_var | No | Env var name holding the API key (e.g. ETHERSCAN_EXPLORER_TOKEN) |
tests/test_explorer_utils.py -- monkeypatch diffyscan.utils.explorer.fetch and CACHE_DIRuv run diffyscan <config> --yes --cache-explorer--cache-explorer flag caches responses to .diffyscan_cache/ so repeated runs do not hit the APIexplorer_hostname + explorer_chain_id_get_explorer_fetcher() tupleconfig_samples/<chain>/tests/test_explorer_utils.py.env.example updated if a new token env var is neededDebug a failed diffyscan verification run. Analyzes diffs, identifies root causes. Use when diffyscan exits with non-zero or shows unexpected diffs.
Create a new diffyscan verification config file for a deployed smart contract. Use when the user wants to verify a new contract or deployment.
Validate a diffyscan config file for correctness before running verification. Checks schema, required fields, type correctness, and common mistakes.