Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/chains/aptos-testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
"tokenAdminRegistry": "0x0000000000000000000000000000000000000000",
"tokenPoolFactory": "0x0000000000000000000000000000000000000000"
},
"ccipNative": {
"feeQuoter": "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45",
"link": "0x3d5d565c271d6b9c52f1a963f2b7bddad3453b0de2ace5e254b8db6549cc335e",
"rmnProxy": "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45",
"router": "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45",
"tokenAdminRegistry": "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
},
"chainFamily": "aptos",
"chainId": "0",
"chainNameIdentifier": "APTOS_TESTNET",
Expand All @@ -17,6 +24,7 @@
"environment": "testnet",
"explorerUrl": "https://explorer.aptoslabs.com/?network=testnet",
"name": "aptos-testnet",
"nativeChainId": "2",
"nativeCurrencySymbol": "APT",
"rpcEnv": "APTOS_TESTNET_RPC_URL"
}
11 changes: 11 additions & 0 deletions config/chains/solana-devnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
"tokenAdminRegistry": "0x0000000000000000000000000000000000000000",
"tokenPoolFactory": "0x0000000000000000000000000000000000000000"
},
"ccipNative": {
"feeQuoter": "FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi",
"link": "LinkhB3afbBKb2EQQu7s7umdZceV3wcvAUJhQAfQ23L",
"rmnProxy": "RmnXLft1mSEwDgMKu2okYuHkiazxntFFcZFrrcXxYg7",
"router": "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C",
"tokenPoolPrograms": {
"burnMint": "41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB",
"lockRelease": "8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC"
}
},
"chainFamily": "svm",
"chainId": "0",
"chainNameIdentifier": "SOLANA_DEVNET",
Expand All @@ -17,6 +27,7 @@
"environment": "testnet",
"explorerUrl": "https://explorer.solana.com?cluster=devnet",
"name": "solana-devnet",
"nativeChainId": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
"nativeCurrencySymbol": "SOL",
"rpcEnv": "SOLANA_DEVNET_RPC_URL"
}
5 changes: 3 additions & 2 deletions docs/config-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ that need it, never exported. Targets that touch the API need only `curl` + `jq`
| `add-lane` | Append a `lanes{}` policy entry LOCAL → REMOTE (writes **only** the `lanes` subtree; no API fetch) | `LOCAL=` `REMOTE=` `CAPACITY=` `RATE=` (all required); `INBOUND_CAPACITY=` + `INBOUND_RATE=` (paired, optional) add the `inbound{}` block; `BOTH=1` adds the reciprocal; `GROUP=` (optional) scopes to a token group | `SyncCcipConfig.s.sol --sig "addLane(string,string,uint256,uint256)" <LOCAL> <REMOTE> <CAP> <RATE>` (6-arg overload with the inbound pair; twice with `BOTH=1`) → canonicalize both files |
| `remove-lane` | Remove a `lanes{}` policy entry LOCAL → REMOTE (undo of `add-lane`; writes **only** the `lanes` subtree, declaration only - an applied lane must also be removed on-chain via `RemoveChain`, or `RemoveRemotePool` for a single pool) | `LOCAL=` `REMOTE=` (both required); `BOTH=1` removes the reciprocal; `GROUP=` (optional) scopes to a token group | `SyncCcipConfig.s.sol --sig "removeLane(string,string)" <LOCAL> <REMOTE>` (twice with `BOTH=1`) → canonicalize both files |
| `adopt-token` | Adopt an externally deployed token (and optionally its pool) into the project store's `addresses{}` after on-chain validation (needs the chain's `rpcEnv` RPC; see [`enabling-existing-token.md`](enabling-existing-token.md)) | EVM: `CHAIN=` **+** `TOKEN=` (both required), `TOKEN_POOL=` optional. Non-EVM: `CHAIN=` **+** `TOKEN_B58=`, `POOL_B58=` optional. `GROUP=` (optional) scopes to a token group | EVM: `AdoptToken.s.sol --sig "run(string,address,address)" <CHAIN> <TOKEN> <TOKEN_POOL or 0x0>`. Non-EVM: `--sig "runNonEvm(string,string,string)" <CHAIN> <TOKEN_B58> <POOL_B58>` |
| `sync` | Refresh one chain's API-served fields (`ccip{}` + identity/metadata) from the API | `CHAIN=` (required) | `SyncCcipConfig.s.sol --sig "run(string)" <CHAIN>` → canonicalize |
| `sync` | Refresh one chain's API-served fields (`ccip{}` or `ccipNative{}` + identity/metadata) from the API | `CHAIN=` (required) | `SyncCcipConfig.s.sol --sig "run(string)" <CHAIN>` → canonicalize |
| `sync-preview` | Fetch + log a chain's `ccip{}` from the API **without writing** | `CHAIN=` (required) | `SyncCcipConfig.s.sol --sig "preview(string)" <CHAIN>` |
| `sync-all` | Refresh every configured chain (non-EVM SKIP; failures collected) | - | loops `--sig "run(string)"` over `config/chains/*.json` + canonicalize each |
| `sync-check` | Read-only drift check vs the live API (pass/fail via make) | `CHAIN=` (optional) | `bash script/config/sync-check.sh [<CHAIN>]` → `SyncCcipConfig --sig "check(string)"` |
Expand Down Expand Up @@ -141,7 +141,8 @@ Project state lives in two files per chain, both keyed by the canonical **select

- **`config/chains/<selectorName>.json`** - pure API/chain facts. Everything the CCIP REST API serves
(`ccip{}` addresses + the identity/metadata fields `displayName`/`chainFamily`/`environment`/
`explorerUrl`/`nativeCurrencySymbol`) is API-owned, written by the sync. The keys the API serves nothing
`explorerUrl`/`nativeCurrencySymbol`, plus `ccipNative{}`/`nativeChainId` on non-EVM chains) is
API-owned, written by the sync. The keys the API serves nothing
for are hand-authored in reviewed PRs (`chainNameIdentifier`, `rpcEnv`, the optional `verifier{}` block, the optional
`evmVersion`), and the join keys
(`name`/`chainSelector`/`chainId`) are seeded once and guard-validated.
Expand Down
35 changes: 34 additions & 1 deletion docs/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ API serves nothing for it, so a reviewed PR owns it and the sync preserves it ve
| -------------------------------- | ------------------------------------- | -------------------- | ------------------------------------------------- | --------------------------------------------- |
| `name` | string (canonical selectorName) | **API sync (guard)** | `chain.name` | file key + basename; validated by the sync |
| `chainId` | quoted decimal string (`"0"` non-EVM) | **API sync (guard)** | `chain.chainId` (EVM; `"0"` placeholder non-EVM) | `ChainConfig.chainId`; sync identity guard |
| `nativeChainId` | string, **non-EVM only** | **API sync** | `chain.chainId` (base58 / hash) | the chain's real id, kept OUT of `chainId` |
| `ccipNative{}` | strings, **non-EVM only** | **API sync** | `chainConfig` in the chain's own encoding | the native address plane (see below) |
| `chainSelector` | quoted `uint64` string | **API sync (guard)** | `chain.chainSelector` | `ChainConfig.load`; the primary join key |
| `displayName` | string | **API sync** | `chain.displayName` | `ChainConfig.load` → `chainName`; log output |
| `chainFamily` | `"evm"` \| `"svm"` \| `"aptos"` | **API sync** | `chain.chainFamily` (lowercased) | `ChainConfig.load`; EVM/non-EVM dispatch |
Expand Down Expand Up @@ -725,7 +727,22 @@ source). `config/chains/solana-devnet.json` keeps the same shape but:
identity; the sync/doctor selectorName guard is what protects a non-EVM file from a wrong selector.
- The `ccip{}` block is **all-zero** and `feeTokens` is empty: non-EVM chains have no EVM-shaped
`chainConfig`, so they are excluded from the API **address** sync (the sync SKIPs the `ccip{}`
transform cleanly).
transform cleanly). It stays as the EVM-typed skeleton every `.ccip.*` reader needs - a base58 value
there makes `HelperConfig`'s constructor scan abort on UNRELATED chains, because it parses every
configured file's `.ccip.*` as an EVM address.
- The real addresses live in the sibling **`ccipNative{}`** block, with **`nativeChainId`** next to it,
both API-synced and both non-EVM only. Values are family-native STRINGS (base58 on SVM, 32-byte hex on
Aptos). Key names mirror `ccip{}` where the concept is shared (`rmn` -> `rmnProxy`, `registryModule` ->
`registryModuleOwnerCustom`), plus `tokenPoolPrograms{burnMint,lockRelease}` - SVM ships both pool
programs and lock-release is a first-class use case, not a burn-mint fallback. **A key the API does not
serve for that family is ABSENT, not zero**: which contracts exist differs by family (SVM has no
`tokenAdminRegistry`; Aptos has one but no `registryModule` and no pool programs), and an absent key
says "not a thing here" where a zero would say "missing". `feeTokens` is not carried - `link` is the one
fee token the repo keys on.
- **`nativeChainId` is where the chain's real id goes; `chainId` keeps its `"0"` sentinel.** Putting the
base58 genesis hash in `chainId` makes the chain VANISH from `HelperConfig`'s configured-chain scan, and
Aptos's real `"2"` silently empties its remote-pool resolution. Several `HelperConfig` paths key off the
sentinel (the constructor scan, the broadcast guard, `getSelectorName`, the chainId lookups).
- The non-EVM chain's own **project store** carries **no `lanes{}`**: lanes are outbound policy, and
non-EVM chains are destination-only here - an EVM chain may declare a lane **to** `solana-devnet` (exempt
from the doctor's reciprocity rung), but `make add-lane LOCAL=solana-devnet ...` is refused (no lanes to
Expand Down Expand Up @@ -779,11 +796,27 @@ source). `config/chains/solana-devnet.json` keeps the same shape but:
"link": "0x00...00",
"feeTokens": []
},
"ccipNative": {
// the same plane in Solana's own encoding - non-EVM only, API-synced, absent keys are unserved
"router": "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C",
"rmnProxy": "RmnXLft1mSEwDgMKu2okYuHkiazxntFFcZFrrcXxYg7",
"feeQuoter": "FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi",
"link": "LinkhB3afbBKb2EQQu7s7umdZceV3wcvAUJhQAfQ23L",
"tokenPoolPrograms": {
"burnMint": "41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB",
"lockRelease": "8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC"
}
},
"nativeChainId": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", // <- chain.chainId (API-synced)
"explorerUrl": "https://explorer.solana.com?cluster=devnet", // <- chainMetadata.explorer.url (API-synced)
"nativeCurrencySymbol": "SOL" // <- chainMetadata.nativeCurrency.symbol (API-synced)
}
```

Aptos is a THIRD shape and carries the same block: `tokenAdminRegistry` present, `tokenPoolPrograms` and
`registryModuleOwnerCustom` absent, one Move package address serving router/feeQuoter/tokenAdminRegistry/rmn.
It is read-only here.

The Solana deployed addresses live in `project/solana-devnet.json` `addresses{}` as base58 strings, not in
this file.

Expand Down
13 changes: 13 additions & 0 deletions docs/gotchas/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ link to.
peer's project store, so a peer store you do not keep locally is reported as unchecked, not as clean,
and the on-chain lane itself is only removed by `RemoveChain`.

<a id="non-evm-addresses-stay-out-of-ccip"></a>
- **A non-EVM address in `ccip{}` breaks scripts on UNRELATED chains, and no guard catches it.**
`HelperConfig` scans every `config/chains/*.json` and parses each one's `.ccip.*` as an EVM address, so
one base58 or Move value anywhere aborts the scan for everybody: a run against Fuji dies with
``vm.parseJsonAddress: failed parsing "Ccip842gz..." as type `address`: invalid string length``, naming
the value but not the file it came from. Measured: `make doctor` still reports VERIFIED on both the
offending chain and the unrelated one, because `VerifyChain` never builds a `HelperConfig`. Non-EVM
addresses belong in the sibling `ccipNative{}` block, which the sync owns; `ccip{}` stays the all-zero
EVM skeleton every `.ccip.*` reader needs. The same applies to the id: the chain's real one goes in
`nativeChainId` and `chainId` keeps its `"0"` sentinel, which several `HelperConfig` paths key off -
and the schema rung does not catch a violation there either, since it asserts the values are quoted
strings, not that they are decimal.

<a id="a-successful-call-can-still-revert-your-frame"></a>
- **A successful call can still revert your frame.** `try C(a).f() returns (string memory)` routes a
REVERT to its catch, but the return data is decoded in the CALLER's frame after the call already
Expand Down
14 changes: 14 additions & 0 deletions docs/troubleshooting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ after a `forge script` run, with no `--broadcast`.
DEST_CHAIN=<dst> TOKEN=<token> AMOUNT=<wei> RECEIVER=<addr>` reproduces the revert before a real send.
- **Verify.** The same `make preflight` returns `GO:`, and `make doctor CHAIN=<siloed chain>` is VERIFIED.

## `vm.parseJsonAddress: failed parsing "..." as type `address`

- **Diagnosis.** Some chain config holds a non-EVM value in `ccip{}`, and `HelperConfig`'s constructor
parses every configured file's `.ccip.*` as an EVM address - so the run that fails is usually against a
DIFFERENT, healthy chain, and the message names the value but not the file. Find the culprit:
`jq -r 'select((.ccip | to_entries | map(.value | strings | startswith("0x") | not) | any)) | input_filename' config/chains/*.json`.
`make doctor` will NOT find it: `VerifyChain` builds no `HelperConfig`, so both chains still verify.
- **Fix.** Non-EVM addresses live in the sibling `ccipNative{}` block (base58 on SVM, 32-byte hex on
Aptos) and the chain's real id in `nativeChainId`; `ccip{}` keeps the all-zero EVM skeleton and
`chainId` its `"0"` sentinel. Both are API-owned: `make sync CHAIN=<chain>` rewrites them, so the
repair is a sync, not a hand edit.
- **Verify.** The failing script runs again, and `make sync-check CHAIN=<chain>` reports
`CLEAN ... identity metadata + ccipNative match the live API`.

## Advanced forensics: a leg that failed then recovered

The REST API and `ccip-cli show` report only the final `SUCCESS` and hide an earlier failed attempt. To
Expand Down
Loading
Loading