CCIP Lane (Chainlink Cross-Chain Interoperability Protocol)
What is CCIP?
Chainlink CCIP is a generic cross-chain messaging and token-transfer protocol. Where CCTP burns and mints USDC against a single Circle attestation, CCIP supports arbitrary tokens registered in its router, and the attestation is produced by a Chainlink DON plus an independent Risk Management Network (ARM). The high-level flow is: The “ARM curse” step is what distinguishes CCIP from CCTP and other single-attester bridges: an independent network can reject a transfer even after the DON has committed it.The CCIP lane in QTG
CCIP is exposed as a single executor key —exec.ccip.send — that owns both the send-side dispatch and the observe-side polling. This differs from CCTP’s 5-node split because the destination-side execution is performed by Chainlink’s off-ramp, not by QTG.
Intent FSM
CCIP intents transition through a persisted status column: The terminal statuses areCOMPLETED, FAILED, and TOMBSTONED_PRE_BROADCAST. The tombstone is special: it releases the nonce slot for retry, whereas a post-broadcast FAILED keeps the nonce locked (the tx is on-chain and the nonce is consumed even on revert). Concretely, every status except TOMBSTONED_PRE_BROADCAST is treated as holding an active nonce.
Required bindings
The lane refuses to run unless all of these template bindings are resolved:source_chain_selectordest_chain_selectorroutersendertokenamountreceiver
ccip_missing_binding and a detail listing exactly which bindings are absent.
source_chain_selector and dest_chain_selector are the Chainlink-assigned uint64-encoded chain IDs (e.g. Ethereum mainnet = 5009297550715157269). They are distinct from EVM chain IDs.
Preflight: lane registry gating
The first thing preflight does after binding validation is a lane-registry dispatch check. This:- Looks up the approved router for the source chain in the lane registry.
- Verifies the
(source_chain_selector, router)pair isstatus="active". - Returns the current
snapshot_version— a monotonically increasing version stamp on the registry.
prepare and submit, the lane raises ccip_registry_version_drift_at_submit and tombstones the intent — preventing the signed payload from being broadcast against a stale router approval.
Signed-recipient invariant (validated == signed)
CCIP participates in QTG’s cross-lane signed-recipient invariant: the recipient/source re-derived from the actual signed artifact must equal the allowlist-validated intent, fail-closed (SIGNED_RECIPIENT_MISMATCH / SIGNED_SOURCE_MISMATCH). CCIP is a strict source + destination action (ccip_send), and prepare-time intent-only resolution pins the receiver before signing.
Prepare: nonce allocation + payload hash
Key invariants:- Nonce is allocated under a Postgres advisory lock to prevent two prepare calls from claiming the same chain nonce.
- The allocated nonce is
max(chain_pending_nonce, db_max_active_nonce + 1)— guards against forgotten in-flight tx. - If the chain’s pending nonce has already passed the allocated nonce, the lane performs a log scan over
CCIPSendRequestedevents to detect a tx that was broadcast but lost its DB write. A match resumes fromSUBMITTING; no match means the nonce was burned elsewhere and the intent is tombstoned. - The payload hash is computed by the sidecar, verified locally, and persisted alongside the intent. It is later re-verified at submit time to guard against payload mutation between prepare and submit.
Submit: registry drift recheck + raw broadcast
submit is more than a thin wrapper around eth_sendRawTransaction. In order, it:
- Loads the intent for this node; if there is no row, it fails with
ccip_intent_not_found. - Short-circuits if the intent already has a
send_tx_hash— it just hydrates themessage_idrather than re-broadcasting. - Re-checks registry drift between sign and submit; a rotated registry eagerly tombstones the intent.
- Forwards the signed raw tx to the sidecar along with the expected payload hash, expected router, and snapshot version, so the sidecar can reject a mismatch before broadcasting.
- Persists the returned
send_tx_hashandmessage_id.
ccip_message_indexing_timeout (Temporary). QTG persists the send_tx_hash alone so the next observe round can pick up the message_id, and re-raises.
Observe: status mapping
The sidecar’s/observe endpoint returns the Chainlink SDK message status as a string. QTG maps it to a node state (and a terminal flag):
The interesting non-terminal stages are:
Polling pacing: lane latency baseline
CCIP messages have wildly different end-to-end latencies — minutes on common L2 pairs, tens of minutes on cross-network paths. Rather than a fixed poll interval, the lane reads a per-lane latency baseline from the registry (falling back to 600 seconds) and classifies elapsed time against it. The polling phase is one ofpre_baseline, at_baseline, over_baseline, or fatal. The fatal phase fires when elapsed exceeds a hard multiple of the baseline — at that point the lane raises ccip_stalled_no_execution and FAILs the node so the operator can investigate.
Error code reference
Curated subset of CCIP-specific terminal errors:Why a sidecar?
The Chainlink CCIP SDK is a TypeScript library. Running it in-process from Python would mean shipping a Node runtime inside the QTG image. The sidecar approach instead:- Keeps QTG free of a heavyweight TS dependency.
- Lets the sidecar evolve independently (it’s the only place that needs CCIP SDK upgrades).
- Centralizes Chainlink network connection state.
- Gives the executor a single well-defined HTTP contract (
/prepare,/submit,/observe,/health,/network-info,/internal/decode-ccip-sent-log).
auth_token is a deployment-level secret (configured via MG_CCIP_SIDECAR_BASE_URL and MG_CCIP_SIDECAR_AUTH_TOKEN).
Related Docs
- Bridge Lane — the shared 4-phase pattern
- CCTP Lane — Circle’s centralized attestation alternative
- LayerZero Lane — Stargate v2 OFT
- CCIP Reference — admin/operator surface
- Executor Protocol — preflight/prepare/submit/observe/recover contract
- State Machine — UNKNOWN handling and recovery flow