NOTE: This repository represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink’s systems, products, and services to integrate them into your own. This template is provided “AS IS” and “AS AVAILABLE” without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code.
A Foundry toolkit for deploying, registering, wiring, and governing Chainlink CCIP cross-chain tokens
(CCT). It ships composable, idempotent primitives (forge scripts) and a make golden path over them.
Chains are data (config/chains/*.json, synced from the CCIP API), deploy state is a project store (git-tracked once a fork un-gitignores it), and history is an append-only local ledger.
AI agents: start at AGENTS.md. Humans: pick a path below.
- New here? Follow the Quick start to deploy a token and a pool on a testnet and send a transfer.
- Running in production? Go to Production operations for execution modes, the roles handoff, mesh expansion, and the pre-mainnet checklist.
- Looking for one command? The primitives catalog has one page per script,
and
make helplists every make target. - Forking this to run your own tokens? Un-gitignore
project/so your lanes, roles, and deployed addresses become your team's tracked source of truth (public addresses only, never secrets), then keep it reconciled withmake doctor/make roles-check. See the Tracking rule.
The core layer below is enough to deploy, wire, and configure. Two more layers (sending and monitoring transfers, and running on a live testnet) are in prerequisites.
| Tool | Why | Check |
|---|---|---|
Foundry (forge, cast) |
Build, test, and every deploy/config script. | forge --version |
| Node.js + npm | Installs the Solidity dependencies (npm ci) and dev tooling. |
npm --version |
make, bash, curl, jq |
The golden-path targets and the config/API sync (config sync needs no RPC, keystore, or API key). | make tools |
| A Foundry keystore account | Signing (cast wallet import <name>, used as --account <name>). Never a private key in .env. See the signing decision. |
cast wallet list |
npm ci # install Solidity + dev dependencies
cp .env.example .env # then set KEYSTORE_NAME, per-chain RPC URLs, and ETHERSCAN_API_KEY
forge buildDeploy a token and a BurnMint pool on two testnets, wire the lane, and send a transfer. Every command is
copy-pasteable. The make golden path resolves the RPC and keystore from each chain file's rpcEnv, so
you never hand-export an RPC per chain; the raw forge script each target wraps is documented on the
linked operations page as the escape hatch.
A target also resolves the chain's EVM version. Most chains use the foundry.toml default and the flag
is a no-op, but the few CCIP networks without PUSH0 declare "evmVersion": "paris" in their chain
file so their bytecode stays deployable there. The escape hatch bypasses that resolution along with the
RPC and keystore, so on such a chain pass it yourself:
--evm-version "$(bash script/config/evm-version.sh <chain>)". See
per-chain EVM version.
Run these for EACH of your two chains (<chain> is a selectorName such as ethereum-testnet-sepolia;
selectorNames may contain underscores, e.g. binance_smart_chain-mainnet; set
its rpcEnv and KEYSTORE_NAME in .env, then source .env).
-
Onboard the chain from the CCIP API (operations/chains):
make discover # search the CCIP API chain catalog make add-chain CHAIN=<chain> SELECTOR=<sel> # generate config/chains/<chain>.json from the API
-
Deploy the token and its pool (tokens, pools):
make deploy-token CHAIN=<chain> TOKEN_NAME="My Token" TOKEN_SYMBOL=MTK make deploy-pool CHAIN=<chain> # BurnMint pool; token resolved from the registry make doctor CHAIN=<chain> # verify on-chain code at the recorded addresses
make deploy-new-chain CHAIN=<chain> SELECTOR=<sel>runsadd-chain -> deploy-token -> deploy-pool -> doctorend to end. -
Register the token with CCIP and set its pool (registration). Registration auto-detects the claim path (
getCCIPAdmin, thenowner, then AccessControlDEFAULT_ADMIN_ROLE):forge script script/setup/ClaimAndAcceptAdmin.s.sol --rpc-url $<CHAIN>_RPC_URL --account $KEYSTORE_NAME --broadcast forge script script/setup/SetPool.s.sol --rpc-url $<CHAIN>_RPC_URL --account $KEYSTORE_NAME --broadcast
-
Wire the lane to the other chain (remote pool plus rate limits), on both sides (lanes and remote pools):
make add-lane LOCAL=<chain> REMOTE=<other-chain> CAPACITY=<wei> RATE=<wei> BOTH=1 forge script script/setup/ApplyChainUpdates.s.sol --rpc-url $<CHAIN>_RPC_URL --account $KEYSTORE_NAME --broadcast make doctor CHAIN=<chain>
-
Send and track a transfer (send, track, and diagnose). Install
@chainlink/ccip-cli(>= 1.10) for this step only; it is a testing tool, not a build dependency. Before the first real send, preflight the transfer withmake preflightto prove the pools would release or mint:unset CCIP_API_URL ccip-cli send --source <chain> --dest <other-chain> --router <src-router> \ --receiver <you> --transfer-tokens <token>=<amt> --approve-max ccip-cli show <messageId> # or: curl -s https://api.ccip.chain.link/v2/messages/<messageId> | jq
The same primitives, parameterized by governance mode and hardened for a real launch. Each flow links its operations page or guide with the exact commands.
- Execution modes. Every write primitive runs under EOA (default) or a Safe (
MODE=safeemits one batch to sign). See governance modes. - Roles handoff (EOA to Safe). Move the privileged roles to a Safe with the reviewed ceremony, then
reconcile with
make roles-check. See roles. - Configure v2 pool features. Rate limits, CCV, hooks and allowlist, fees, finality, dynamic config.
- LockRelease liquidity. The rebalancer and ERC20 LockBox models, side by side, in liquidity.
- Mesh expansion. Add or remove lanes in both directions, then reconcile with
make doctor(lanes and remote pools). - Config sync. Keep
config/chainscurrent against the CCIP API (chains). - Verification. Source-verify every deployed contract (verification).
- Pool migration. Move a token from an old pool version to a new one without dropping in-flight messages (migrate a pool).
- Multiple tokens in one project. Thread
GROUP=<g>to keep each token group isolated; the store model explains the config/project/history separation and the redeploy guard (FORCE_REDEPLOY). - Before mainnet. Walk the production checklist.
- Documentation map - the full index.
- Primitives catalog - one page per script;
catalog.jsonfor agents. - Guides - task-focused how-tos.
- Operations - per-operation command reference.
- Reference: prerequisites, config and project-store schema, config architecture, deployed addresses, pool versions.
- Concepts: composition, store model, diagnosis, roles, governance modes.
- Learnings: troubleshooting, gotchas, decisions.
The configured chains live in config/chains/*.json, each named by its canonical CCIP selectorName. Add
one with make add-chain (see chains); the file name IS the selectorName, so
there are no bespoke slugs. Non-EVM chains (for example solana-devnet) are configured as adoption
targets.
forge test # full suite (fork tests read RPCs from .env)
npm run docs:gate # primitives catalog freshness + docs links/anchors/prose
npm run lint:sol # solhint (chainlink-ccip ruleset, zero warnings)