Deploy your first cell

This page stands up an EVM CCV in order, using the project's two starter kits: the on-chain contracts kit for the verifier contracts, and the off-chain kit for the cell, one verifier and one aggregator with the aggregator exposed publicly. Each step names what you do, says which kit it uses, and links that kit's documentation for the exact commands.

Step 1: Deploy the on-chain contracts

Start by choosing the source and destination chains you want to connect and collecting their identifiers from the CCIP directory: each chain's selector, its Router (the on-chain lane config needs it), and, for every source chain, its OnRamp (the cell needs it as on_ramp_addresses). These are existing CCIP network contracts that the kit does not deploy, so you look them up here.

Then use the on-chain contracts kit to deploy the verifier's on-chain contracts on both the source and destination chains. It sets up four things per chain:

  • The CREATE2 factory: a deployer that produces deterministic contract addresses, so the resolver lands at the same address on every chain. That cross-chain address parity is what lets pools and receivers reference your CCV by one address regardless of chain.
  • The resolver (VersionedVerifierResolver): the stable address that token pools and receivers name as your CCV. It stays constant even when you upgrade the verifier implementation behind it, so it is the address you carry everywhere else.
  • The committee verifier: the verifier implementation that the resolver points at, and the contract that checks the committee's signatures on chain.
  • The lane config: wires each source and destination together, so the verifier knows which directed lanes it serves.

The kit is driven by make targets, with OUTPUT_MODE=EOA to broadcast directly or OUTPUT_MODE=SAFE to emit Safe Transaction Builder calldata for governance (see Operate: day 2). Follow the on-chain contracts kit's stepped guides for the exact commands: Getting started covers install, build, and how the make targets take CHAIN, TAG, RPC_URL, and OUTPUT_MODE; the full flow walks the deploy-then-configure sequence end to end, in the fixed per-chain order (factory, resolver, verifier). The deploy and configure pages have the per-target detail.

Carry two things from this step into the cell config: the resolver address (the same on every chain, via CREATE2) and the source and destination chain selectors.

Custody note: with KMS, derive the signer address and register the committee's signer set on both chains' verifiers now. With the Postgres keystore, you come back to this in step 4. See Signer key custody and KMS.

Checkpoint: on both chains you have deployed the CREATE2 factory, the resolver, and the committee verifier, and applied the lane config. The resolver reads back the same address on both chains (make deployments-check asserts the parity). On the KMS path the on-chain signature config is set too; on the Postgres keystore path it is still pending step 4.

Step 2: Configure the cell values

The cell is the verifier and aggregator you run from the off-chain kit's ccv-cell Helm chart. Create one my-values.yaml for that chart and override only what you need. The chart values reference and the off-chain kit's Configure documentation are the field reference.

The most common mistake is putting the CommitteeVerifier implementation address where the resolver belongs. sourceVerifierAddress, every destinationVerifiers entry, and every committee_verifier_addresses entry take the VersionedVerifierResolver from step 1, on every selector. The implementation address matches no message, so the cell looks healthy but never attests: green pods and no errors in the logs.

For the secret backend, the secrets the cell needs, and where RPC URLs that carry an API key go, see Choose a secret backend. For the keystore choice and the KMS signer, see Signer key custody and KMS.

Checkpoint: your my-values.yaml for the verifier and aggregator renders with helm template and no schema error, with the resolver address and selectors from step 1 in place.

Step 3: Deploy the verifier and aggregator

Deploy the cell with the off-chain kit's Helm chart. The chart renders StatefulSets with replicas: 1 by design, so use statefulset/... in kubectl commands. The order matters because the secret grant uses ServiceAccount names that only exist once the chart is rendered:

  1. Run helm template and read the generated ServiceAccount names.
  2. Grant those ServiceAccounts access to each secret they read. On GKE the direct workload-identity principal (principal://.../sa/<KSA>) needs no Google service account and no annotation; the annotation-plus-GSA pattern also works. Skip this and the pods sit in ContainerCreating with PermissionDenied on secretmanager.versions.access, visible only in kubectl describe.
  3. Run helm upgrade --install.

Checkpoint: both the verifier and aggregator pods report 1/1, and the verifier log is clean after the startup window.

Step 4: Expose the aggregator and register the signer

Expose the aggregator (TLS-terminated, HTTP/2 and gRPC end to end, a stable public hostname). The route type depends on what your cluster supports rather than which cloud it runs on, and every managed load balancer needs its health check pointed at the readiness path. The full procedure, with per-cloud health-check settings, is in Expose the aggregator.

With the Postgres keystore, read the signer address the verifier logged on first boot (Using signer address), then register the committee's signer set into the verifier contract with the on-chain contracts kit's apply-signature-configs target. The signer set is written to the destination chain's verifier, so for a two-way lane you register it on both chains' verifiers. The target refuses a weak committee (a 1-of-1, any N-of-N, or a threshold at or below 2/3 of the committee) unless you set ALLOW_WEAK_COMMITTEE=true, which is for test committees only (see Scale to a committee). On the KMS path you did this in step 1.

Checkpoint: the on-chain signer matches the logged address, and the aggregator answers over its public hostname.

Step 5: Add your custom policy hook

Run a minimal PASS/FAIL endpoint and reference it from your off-chain kit cell config. Read the rules on Add a custom policy hook first; an outage that returns FAIL is treated as a compliance decision.

Checkpoint: a blocked-address send logs the reason string and never executes.

Your cell (the verifier and aggregator) is deployed, the aggregator is exposed, and the signer is registered on chain. Prove it end to end in Test your setup.

What's next

Get the latest Chainlink content straight to your inbox.