| type | reference |
|---|
Supporting a new chain is a config edit, not a code change. Tooling under script/config/ keeps
config/chains/<name>.json true to the live CCIP API, needing only
curl and jq (no RPC URL, no keystore, no API key). The golden path is the repo Makefile; each
target sets FOUNDRY_PROFILE=sync for you. The full command reference (required args, the raw
forge script / bash each target runs, the 0/1/2 drift exit-code contract, and the architecture
diagrams) is in Config architecture.
Three commands generate and verify config/chains/<name>.json from the API:
make discover FILTER=base # 1. find the chain in the API catalog, note its NAME + SELECTOR
make add-chain CHAIN=ethereum-testnet-sepolia-base-1 SELECTOR=10344971235874465080 # 2. generate from the API
make doctor CHAIN=ethereum-testnet-sepolia-base-1 # 3. layered verification - re-run until greenCHAIN is the chain's canonical CCIP selectorName as shown by make discover (the API/registry name,
for example ethereum-testnet-sepolia-base-1, not a bespoke base-sepolia). It becomes the file name
config/chains/<CHAIN>.json and is validated against the API. SELECTOR is the explicit identity key,
also from make discover. Every fetch cross-checks both: a valid-but-wrong selector fails loudly as
SELECTOR MISMATCH, and a non-canonical name as SELECTOR NAME MISMATCH, instead of silently writing
another chain's contracts.
New chains are discovered automatically from config/chains/: HelperConfig scans the directory, so no
Solidity edit is needed anywhere. For a newly added chain the chainNameIdentifier (and hence the
rpcEnv and the <ID>_TOKEN/<ID>_TOKEN_POOL override prefix) is derived from the selectorName as
UPPER_SNAKE, so it may differ in style from the bundled chains' curated short forms (for example
AVALANCHE_TESTNET_FUJI, not AVALANCHE_FUJI). A selectorName that starts with a digit is prefixed
with _ so the derived rpcEnv is a valid shell variable name (0g-testnet-galileo-1 derives
_0G_TESTNET_GALILEO_1). add-chain prints the exact chainNameIdentifier and
rpcEnv names it generated, plus your next steps: add the chain's RPC env var to .env, review the
generated defaults in the config file, wire a lane with make add-lane, and re-run the doctor until it
reports VERIFIED.
The doctor ends in one of three verdicts, not a pass/fail boolean. FAILED means a check ran and
contradicted the config - including a declared roles{} value whose read failed, which the roles rung
refuses as a FAIL rather than skipping. INCOMPLETE means no check failed but an infrastructure gap kept
checks from running at all - an unset RPC env, a pool that did not answer the lanes reverse-check - and
the run exits nonzero rather than clean: a wrapper reading exit 0 there would treat an unchecked
chain as a healthy one. Each counted gap is tagged
[SKIP] UNVERIFIED in the output. VERIFIED (exit 0) means every applicable check ran. An optional
block that is not declared is a plain SKIP and never makes a run INCOMPLETE.
A non-EVM chain (solana-devnet and friends) always ends INCOMPLETE: its schema and selectorName are
checked, but this toolkit has no EVM JSON-RPC path to its on-chain state, so there is nothing to read.
That is the honest verdict, not a fault in the config - verify a non-EVM deployment with that chain's
own tooling.
Names may contain underscores, which some CCIP selectorNames use (e.g.
binance_smart_chain-mainnet); pass the name from make discover verbatim.
Deploying your token and pool there is the golden path (see Token operations and Token pool
deployment). Then declare the lane policy with make add-lane LOCAL=<name> REMOTE=<remote> CAPACITY=<wei> RATE=<wei> BOTH=1, apply it on-chain (see Lanes and remote
pools), and re-run make doctor; its lanes rung reconciles the declared policy
against the pool. To retire a lane, make remove-lane LOCAL=<name> REMOTE=<remote> [BOTH=1] removes the
declaration; that is a separate step from the on-chain removal (see Remove a remote
chain), and between the two make doctor WARNs that the
on-chain lane is not declared.
Per-field reference: Config and project-store schema. Command and architecture reference: Config architecture.
| I want to | Run |
|---|---|
| See which chains exist / find a selector | make discover FILTER=<term> |
| See which tokens exist, and each one's pool type and version | make discover-tokens SYMBOL=<sym> POOL=1 (also ADMIN=, CHAIN_SELECTOR=, ENVIRONMENT=) |
| Onboard a new chain | make add-chain CHAIN=<name> SELECTOR=<sel>, then make doctor CHAIN=<name> |
| Check whether any config drifted from the API (routine; what CI runs weekly) | make sync-check (CI/automation: bash script/config/sync-check.sh for the 0/1/2 exit codes) |
| Inspect what the API currently has for one chain before changing anything | make sync-preview CHAIN=<name> |
| Apply the API's current values | make sync CHAIN=<name> / make sync-all |
| Declare a lane policy between two chains | make add-lane LOCAL=<name> REMOTE=<remote> CAPACITY=<wei> RATE=<wei> [BOTH=1] |
Retire a declared lane policy (undo of add-lane) |
make remove-lane LOCAL=<name> REMOTE=<remote> [BOTH=1] |
| Bring externally deployed contracts into the registry | make adopt-token CHAIN=<name> TOKEN=<addr> [TOKEN_POOL=<addr>] |
| Deep-verify one chain end to end (human health check) | make doctor CHAIN=<name> |
Restore canonical formatting after a raw forge script run |
make fmt-config |
doctor and sync-check layer rather than overlap: doctor is the deep single-chain health check for a
human (schema, identity, drift, RPC, on-chain code, registry warnings, mesh reciprocity, on-chain lane
reconciliation, and declared-roles{} authority reconciliation), while sync-check is the fleet-wide
drift verdict for routine use and CI.