CCIP Bridge Operator Surface
Source files
src/qtg/interfaces/api/routes/ccip_registry.pysrc/qtg/application/services/ccip_refresh.pysrc/qtg/infrastructure/executors/ccip/__init__.pysrc/qtg/infrastructure/executors/ccip/action.pysrc/qtg/infrastructure/executors/ccip/sidecar_client.pysrc/qtg/infrastructure/executors/ccip/repository.pysrc/qtg/infrastructure/executors/ccip/token_registry_repository.py
1. Architecture overview
The CCIP lane is one executor (exec.ccip.send) backed by:
Unlike CCTP and Stargate (Pro), CCIP does not expose row-level admin CRUD routes for the registry. The registry is rebuilt by calling
POST /v3/executors/ccip/refresh-registry, which delegates to the sidecar’s /network-info endpoint to discover the current canonical CCIP routers and lanes, diff against the DB, and apply (or dry-run) the result.
2. Admin route table
2.1 Auth boundary
Single write surface, gated by HMACadmin purpose:
- Route module:
src/qtg/interfaces/api/routes/ccip_registry.py - Guard:
require_admin_hmac→require_request_auth - Accepted purposes:
admin,all - Canonical mapping source:
interfaces/api/middleware/auth/hmac.py
2.2 Routes
RouterDiff fields:
added: list[dict]— routers newly discovered on a chainchanged: list[dict]— routers whose address changed under the same selectorremoved: list[str]— selectors no longer present
LaneDiff fields:
added: list[dict]— new(src_selector, dst_selector, token)laneschanged: list[dict]— lanes whose router or trust tier flippedorphaned: list[dict]— lanes still in DB but no longer in network-infoskipped: list[dict]— lanes the diff intentionally did not touch
requires_reapproval: list[dict] is the operator-actionable result: lanes whose registry_version changed and which the operator must explicitly re-approve before they become dispatchable again.
2.3 Typical operator workflow
- Dry-run first. Always.
- Review
diff,lane_diff, andrequires_reapprovalin the response. - If the changes look right, repeat with
dry_run: falseto apply. - For every lane in
requires_reapproval, re-approve out-of-band (the registry write-side keeps prior status until an operator confirms the new snapshot).
2.4 Idempotency and audit semantics
The refresh endpoint always writes an audit row viaaudit_log_post_commit(descriptor_for_route('POST', '/v3/executors/ccip/refresh-registry'), ...). The audit captures dry_run, operator_label, applied, and sdk_version so postmortems can correlate registry mutation with a specific SDK build.
If dry_run=true, applied=false is recorded and no DB rows change. If the sidecar’s network_info_hash matches the prior refresh result, the diff is empty and the response carries the same new_snapshot_version as before — the operation is safely re-runnable.
2.5 Failure semantics
The refresh request propagates sidecar HTTP errors with the sourceerror_code:
3. Lane registry semantics
The lane registry is the approval surface that gatesexec.ccip.send at runtime. Three column states matter:
The
registry_version invariant is the most subtle: when prepare() allocates a nonce and writes the intent, it stamps snapshot_version_at_dispatch. At submit() time, the executor re-reads the row with for_update=True and refuses to broadcast if the version changed. This is what makes the refresh endpoint safe to run mid-flight — any in-flight intent against an updated lane will eager-tombstone instead of broadcasting against a stale router approval.
Signed-recipient invariant (validated == signed)
CCIP now participates in QTG’s cross-lane signed-recipient invariant at the broadcast boundary: QTG independently RLP-decodes the actual signed artifact, ABI-decodesccipSend, and asserts router, destination chain selector, receiver, and token against trusted intent/binding state before the sidecar broadcasts. Failures close the node with ccip_signed_artifact_mismatch and a specific detail.error_code such as SIGNED_RECIPIENT_MISMATCH or SIGNED_TARGET_MISMATCH.
The dispatch allowlist is also now chain-scoped for CCIP only: source and destination addresses must match an AllowedAddress row whose chain_id equals the selector-derived EVM chain id. Chain-NULL allowlist rows no longer pass for CCIP, but they remain valid for CCTP/Gateway lanes that still use chain-agnostic matching.
Runbook: chain-match setup and recovery
For the operator setup and cutover steps, use CCIP allowlist chain-match setup. Keep this reference page as the lane surface and the runbook as the configuration/recovery guide.4. Sidecar contract
The sidecar exposes six HTTP routes consumed by the executor:
The auth header is
Authorization: Bearer $MG_CCIP_SIDECAR_AUTH_TOKEN. The token is a deployment-level secret — rotating it requires both QTG and sidecar to receive the new value, so coordinate via a runbook.
4.1 Status classification
CCIPSidecarClient._request partitions HTTP responses:
200→ success, parse JSON.400 / 401 / 409 / 422→FatalMovementErrorwith the sidecar’serror_code.404 / 429 / 500 / 502 / 503 / 504→TemporaryMovementError(executor will retry).- Anything else →
TemporaryMovementError("ccip_sidecar_unknown_status")— treat as transient pending diagnosis.
5. Intent lifecycle and observability
A CCIP movement node’s lifecycle in QTG matches the intent FSM (see CCIP Lane):polling_phase is computed from (elapsed_seconds, lane_latency_seconds) and persisted on every observe call — operators can query the field directly to find intents stuck over_baseline before they tip into fatal.
6. Settings reference
If
MG_CCIP_SIDECAR_BASE_URL is empty at bootstrap, register_builtin_ccip_executors returns None and the executor is not registered. CCIP movements then fail at preflight with an unrecognized-executor error rather than running against a missing sidecar.
7. Related Docs
- CCIP Lane concepts — the design and protocol-level story
- Bridge Lane overview — comparison with CCTP and LayerZero
- CCTP Lane reference — sibling lane
- Executors overview — common executor protocol