# CCIP Message Lifecycle
Source: https://docs.chain.link/ccip/concepts/message-lifecycle

> For the complete documentation index, see [llms.txt](/llms.txt).

This page follows a single cross-chain message through CCIP's secure-by-default message path, from send to delivery. For component descriptions and architecture context, see the [CCIP 2.0 Architecture](/ccip/concepts/architecture/overview) page.

## Overview

When the application sends a cross-chain message on a lane, four phases run in order:

1. **Send**: on the source chain
2. **Verify**: offchain attestation
3. **Index**: collect attestations into one store
4. **Execute**: on the destination chain

![End-to-end CCIP 2.0 message lifecycle sequence diagram showing the send, verify, index, and execute phases across source chain, offchain verifiers, and destination chain.](/images/ccip/concepts/architecture/v2-message-lifecycle-sequence.svg)

## Phase 1: Source-chain send

### 1.1 Quote the fee

The application calls `Router.getFee(destChainSelector, message)`.

- The Router resolves the active OnRamp for the destination and delegates to `OnRamp.getFee()`.
- If `feeToken == address(0)`, the Router treats payment as the native token (internally using the wrapped native address for pricing).
- `OnRamp.getFee()` builds the same verifier and executor list that send will use, then queries:
  - Fee Quoter: converts the combined gas and byte overheads into an execution cost on the destination chain, and returns the fee token price the OnRamp uses to convert every fee component into the fee token.
  - Each CCV: `getFee()` per verifier (flat USD cents plus gas/byte components).
  - Token pool: `getFee()` if the pool implements the V2 interface (skipped if no token transfer). V1 pools, and V2 pools without a custom fee config, use the Fee Quoter's token transfer fee instead.
  - Executor: `getFee()` unless the no-execution address is selected (zero executor fee).
- `getFee()` must match the send path. The same logic runs again inside `forwardFromRouter()`, and the send reverts if that recomputed fee is higher than the amount paid.

If paying in an ERC-20, the application approves the Router for the quoted amount before sending.

### 1.2 Send via the Router

The application calls `Router.ccipSend(destChainSelector, message)` with the payload, tokens (if any), fee token, and `extraArgs` in the message. New integrations should use `ExtraArgsV3` (CCVs, executor, finality, gas limit, token receiver for non-EVM destinations only, token/executor args). Existing applications can continue sending legacy extra args on CCIP 2.0. The OnRamp normalizes them internally before building the message.

Supported `extraArgs` formats (EVM source → EVM destination):

| Format               | When used                 | What the sender controls                                 | CCIP 2.0 defaults applied                                                                   |
| :------------------- | :------------------------ | :------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
| `ExtraArgsV3`        | New integrations          | CCVs, executor, finality, gas limit, token/executor args | Lane default CCVs/executor only when the sender passes zero-value placeholders              |
| `GenericExtraArgsV2` | Existing v1.x-style apps  | Gas limit (`allowOutOfOrderExecution` was deprecated)    | Chainlink Committee Verifier; Chainlink executor; messages wait for full finality on source |
| `EVMExtraArgsV1`     | Existing v1.x-style apps  | Gas limit only                                           | Same as above                                                                               |
| Empty `extraArgs`    | Apps that omit extra args | Nothing                                                  | Same as above, plus the destination chain's default gas limit                               |

**Router (`ccipSend`):**

- Reverts if the destination lane is not supported (`isChainSupported`) or the RMN contract reports a curse.
- Re-computes the fee via `OnRamp.getFee()` (validates that the fee token is allowed).
- Native fee token: wraps `msg.value` and transfers the wrapped native to the OnRamp.
- ERC-20 fee token: `transferFrom` the application to the OnRamp; `msg.value` must be zero.
- For each token in the message: `transferFrom` the application to the token pool (pool resolved via `OnRamp.getPoolBySourceToken()`).
- Calls `OnRamp.forwardFromRouter(destChainSelector, message, feeTokenAmount, msg.sender)`, passing `msg.sender` as the recorded source sender in the message.

### 1.3 OnRamp processing (`forwardFromRouter`)

**1. Parse extra args and apply defaults**

- If `extraArgs` carries the V3 tag (`0xa69dd4aa`), decodes `ExtraArgsV3` directly.
- Otherwise, calls `FeeQuoter.resolveLegacyArgs()` to extract the gas limit from V1/V2 legacy formats, then applies CCIP 2.0 defaults for everything else.
- Resolves CCVs, executor, finality, gas limit, token receiver, and args into an internal V3-shaped struct.
- Applies the lane default executor when the sender omits one. Applies the lane default CCVs when the sender omits a verifier list (except on token-only transfers: no data, one token, and a zero gas limit) or includes an `address(0)` placeholder.
- Validates the destination receiver address length for the destination chain family.

**2. Merge verifier lists**

Final CCV list = sender preferences + lane defaults (where applicable) + lane-mandated (always) + pool-required (from `getRequiredCCVs()` on V2 pools, or lane defaults for V1 pools).

Computes `ccvAndExecutorHash` from the final CCV list and executor. This hash is embedded in the message for offchain validation only; the destination chain does not check it.

**3. Compute and distribute fees**

- Calls `getFee()` on each CCV, the pool (if applicable), and the executor. These are the same participants as in the quote path.
- Sets `executionGasLimit` to the sum of the receipt gas components (CCV verification gas, pool overhead, executor execution cost, user gas limit) plus the Fee Quoter's per-byte gas for the quoted byte overheads.
- Reverts with `InsufficientFeeTokenAmount` if the fee paid is less than computed.
- `_distributeFees()` transfers fee-token payments to each receipt issuer: the CCVs, the executor, and the pool if it is a V2 pool. The network fee and any V1 pool fee stay on the OnRamp.

**4. Lock or burn tokens**

- At most one token per message.
- Calls the pool's `lockOrBurn()` with finality and token args; pool output (`destPoolData`) is encoded into the message.
- Validates that the pool extra data fits within the byte overhead quoted in the fee receipt.

**5. Encode message and compute message ID**

- Assigns the next per-lane `messageNumber` (monotonic, starts at 1).
- Encodes `MessageV1` and sets `messageId = keccak256(encodedMessage)`.

**6. Register with each CCV (outbound)**

For each CCV in the final list:

- Resolves the outbound implementation via `ICrossChainVerifierResolver.getOutboundImplementation()`.
- Calls `ICrossChainVerifierV1.forwardToVerifier()`, which runs the CCV's outbound send hook and returns a verifier blob: CCV-specific bytes the OnRamp includes in `CCIPMessageSent.verifierBlobs`. Offchain verifier nodes use this blob to match the message and build attestations. For example, the Committee Verifier returns its 4-byte version tag, which committee nodes include in the hash they sign.

**7. Emit event**

Emits `CCIPMessageSent` with:

- `messageId`, `encodedMessage`, `receipts` (fee breakdown per issuer), and `verifierBlobs` (per-CCV hints).
- `sender` (original application address), `feeToken`, and the token amount before pool fees.

Curse status is checked at both the Router and the OnRamp, so a send to a cursed destination reverts.

## Phase 2: Offchain verification

### 2.1 Shared verifier pipeline

Each CCV type's offchain service:

1. Monitors the source chain for `CCIPMessageSent`.
2. Filters events where `receipts` include an entry issued by that CCV's onchain resolver address.
3. Decodes `encodedMessage` and confirms `messageId` matches `keccak256(encodedMessage)`.
4. Reads the requested finality from the message and waits until the source chain meets that verifier's onchain policy (full finality by default).
5. Checks RMN curse status. Cursed source or destination subjects are not attested.
6. Reads `verifierBlobs` from the event (the CCV-specific bytes that `forwardToVerifier` returned on send) and uses them to match the message and build attestations. For the Committee Verifier, the blob is the 4-byte version tag that nodes include in the hash they sign.

### 2.2 Committee Verifier (default)

For each message:

- Each committee node independently signs a hash over the verifier version tag and `messageId` (which commits to the full encoded payload).
- Posts the signature to the Aggregator over an authenticated channel.
- The Aggregator validates each ECDSA signature, persists it, and checks quorum for the source chain.
- When quorum is reached, it stores the combined `VerifierResult` and exposes it via a read API.

> **NOTE**
>
> For how Faster-Than-Finality (FTF) changes the finality wait, see [FTF -
> Overview](/ccip/concepts/execution-latency/ftf).

### 2.3 Additional CCVs

Each additional verifier service operates independently, with its own monitoring, finality wait, and publication path. Results are stored in that verifier's own database and exposed via its REST API (not the Aggregator). Some apply only when their specific token is transferred.

Attestations are bound to `messageId`. Offchain services use the onchain message ID and do not derive a different identity.

## Phase 3: Indexing

### 3.1 Discovery

- The Indexer polls the Aggregator using a sequence cursor to discover newly verified messages without scanning by message ID.
- For each discovered message ID, it checks local storage for which CCV attestations are still missing.

### 3.2 Collection

- Committee Verifier results: fetched from the Aggregator (by message ID or via the cursor stream).
- Additional CCVs: fetched from each configured verifier's API.
- Missing attestations are retried until present, or until operational timeout policies apply.

### 3.3 Storage

- Attestations are stored keyed by message ID and CCV identity.
- Only CCV types in the Indexer's configuration are collected. If a message requires an untracked CCV, automated execution may stall until the configuration is updated.

Executor services may bypass the Indexer and query the Aggregator and verifier APIs directly.

## Phase 4: Destination-chain execution

### 4.1 Prepare submission

Once the required attestations exist:

- The application or any party calls `OffRamp.execute(encodedMessage, ccvs, verifierResults, gasLimitOverride)` on the destination chain.
- The `ccvs` and `verifierResults` arrays must be the same length. Each entry pairs a CCV resolver address with its proof bytes.
- A `gasLimitOverride` of `0` means use the gas limit from the message; a non-zero override must be ≥ `ccipReceiveGasLimit` in the message. Example: if the message specifies `200_000`, pass `0` to use `200_000`, or `300_000` to grant more headroom; `100_000` reverts.

The executor chosen at send time governs fees and does not restrict who may call `execute`.

### 4.2 Default executor service (offchain)

The default executor service runs these checks before submitting:

1. Reads `OffRamp.getCCVsForMessage()` (view), which re-derives the required and optional CCVs from the receiver, pool, and lane config for the encoded message (the same logic as onchain execution).
2. Confirms all required CCVs have attestation results and that the optional quorum is met.
3. Checks curse status and the `OffRamp` execution state for the message ID (skips if already SUCCESS).
4. Submits `execute()`; retries on FAILURE within a configured window (currently 8h).

If another party has already reached SUCCESS, the service skips the message.

### 4.3 OffRamp entry checks (`execute`)

Before execution begins, the OffRamp validates:

- The source chain is not cursed and is enabled in lane config.
- `onRampAddress` in the message matches an allowlisted OnRamp for that source chain.
- `offRampAddress` in the message matches this OffRamp contract.
- `destChainSelector` matches the local chain.
- The message has not already executed successfully (a retry from FAILURE or a first attempt from UNTOUCHED is allowed).

Sets the execution state to IN\_PROGRESS, then calls `executeSingleMessage()` internally (with a gas buffer so state can be updated even if inner execution runs out of gas).

### 4.4 OffRamp message processing (`executeSingleMessage`)

**1. Resolve required CCVs (independent of submitter)**

Re-derives the required set via `_getCCVsForMessage()` from:

- Receiver: `getCCVsAndFinalityConfig(sourceChainSelector, sender)` if the receiver implements the V2 interface (required + optional CCVs with an optional threshold). If the receiver does not implement it, or returns no required CCVs and a zero optional threshold, the lane default CCVs apply.
- Token pool: inbound `getRequiredCCVs()` (or lane defaults for V1 pools).
- Lane configuration: lane-mandated CCVs are always included.

For token-only transfers, receiver CCVs and sender-level defaults are omitted; pool and lane-mandated verifiers still apply.

**2. Verify CCV quorum**

This step runs before token release or receiver delivery. The OffRamp first confirms quorum at the CCV-set level, then calls each CCV's inbound verification.

*CCV-set quorum (OffRamp):*

- Confirms every required CCV resolver address is present in the submitted `ccvs` array, and reverts with `RequiredCCVMissing` if not.
- Confirms the optional CCV threshold is met when the receiver specified optional verifiers, and reverts with `OptionalCCVQuorumNotReached` if not.
- Extra CCV entries beyond what is required or optional are ignored (not verified).
- Only CCVs that pass this set-level check proceed to `verifyMessage`.

*Per-CCV verification (resolver → implementation):*

For each CCV to verify:

1. `getInboundImplementation(verifierResults[i])` on the CCV resolver reads the 4-byte version tag prefix from the proof and returns the inbound implementation address. If that version is unregistered, the resolver returns the zero address and the OffRamp reverts with `InboundImplementationNotFound`.
2. `verifyMessage(message, messageId, verifierResults[i])` on that implementation must succeed (not revert) for execution to continue.

The `messageId` passed here is `keccak256(encodedMessage)`, the same ID that committee nodes hash with the version tag and sign offchain, and that the Aggregator keys results on.

*Checks inside `CommitteeVerifier.verifyMessage`:*

The Committee Verifier is the baseline CCV on every lane. Its destination-side `verifyMessage` performs onchain checks on the decoded message and the proof bytes. Offchain, each committee node signs `keccak256(versionTag ‖ messageId)` and posts to the Aggregator. When quorum is reached, the Aggregator packs the quorum signatures (sorted by signer address) and exposes them as `CcvData` in the `VerifierResult`. Executors pass this blob as `verifierResults[i]` at execution time.

1. **RMN curse**: reverts if the message's source chain selector is cursed.
2. **Minimum length**: the proof must contain at least the version tag and signature-length prefix; otherwise `InvalidVerifierResults`.
3. **Version tag match**: the first 4 bytes must equal this implementation's immutable `versionTag()`; otherwise `InvalidCCVVersion`. This binds the proof to this verifier type and prevents cross-CCV replay.
4. **Signature length sanity**: the declared signature length must not exceed the bytes available; otherwise `InvalidVerifierResults`.
5. **ECDSA quorum validation**: recomputes the signed hash as `keccak256(bytes.concat(verifierVersion, messageId))` and validates signatures:
   - Loads the signer allowlist and threshold configured for this message's source chain selector on the destination Committee Verifier. Reverts `SourceNotConfigured` if none.
   - Rejects if the chain ID at deployment differs from the current chain ID (`ForkedChain`). This defends against replay on a forked network.
   - Requires at least threshold signatures in the blob; otherwise `WrongNumberOfSignatures`.
   - For the first threshold signatures (in order): recovers the signer with `ecrecover`, checks the signer is in the allowlist (`UnauthorizedSigner`), and enforces strictly increasing signer addresses (`NonOrderedOrNonUniqueSignatures`) to prevent duplicate votes.
   - Additional signatures beyond the threshold may be present; only the first threshold are validated (a gas optimization).

*What a successful check guarantees:*

- The proof authorizes this exact message ID, which commits to the full encoded payload (receiver, tokens, data, finality, etc.), so the proof cannot authorize a different message or a partial edit.
- At least a quorum of independent, allowlisted committee signers attested to that ID for this verifier version.
- The attestation cannot be applied while the source chain is cursed, or through the wrong CCV implementation.

*Failure behavior:*

Any revert from `verifyMessage` fails the entire `executeSingleMessage` call. No tokens are released and the receiver is not called. The message ends in the FAILURE execution state, which is retryable.

Additional CCV types use the same resolver routing and OffRamp call pattern, but their `verifyMessage` validates their own proof formats (for example, Circle CCTP attestation bytes or Lombard attestation bytes) instead of committee ECDSA quorum.

**3. Release or mint tokens**

- If the message includes a token transfer: calls the pool's `releaseOrMint()` with the source sender, source chain, and finality from the message.
- Delivers to the `tokenReceiver` in the encoded message. On the source chain, the OnRamp sets it to the message receiver when the sender doesn't specify one.
- Confirms the delivered amount via balance delta (unless the pool itself is the receiver).

**4. Deliver to receiver (if not token-only)**

- Skipped when `data` is empty and `ccipReceiveGasLimit` is zero, when the receiver is not a contract, or when the receiver does not implement the receiver interface (the pure token-only path).
- Otherwise calls `Router.routeMessage()` → the receiver's `ccipReceive(Any2EVMMessage)` with exact gas enforcement.
- The `execute()` caller (executor or manual submitter) never calls the receiver directly; the Router does. In `ccipReceive`, verify that `msg.sender` is the Router. The original source-chain application is `message.sender` in the decoded message.

If `ccipReceive` reverts, the entire execution attempt fails atomically (tokens are not successfully delivered in that attempt).

### 4.5 Execution outcome

- **SUCCESS**: recorded in `s_executionStates[messageId]`; the message cannot be executed again. Emits `ExecutionStateChanged`.
- **FAILURE**: recorded; the message may be retried. Emits `ExecutionStateChanged` with error data.
- A retry that fails again reverts the whole transaction with `NoStateProgressMade`, so wallets and tooling see the failure. The message stays in FAILURE, ready to be retried once the underlying issue is fixed.

> **CAUTION: Disclaimer**
>
> Chainlink CCIP is an interoperability messaging protocol. Chainlink does not hold or transfer any assets. The
> performance and behaviour of applications using Chainlink CCIP may depend on coding, engineering, configuration, and
> other technical implementation choices made by developers, token issuers, Cross-Chain Verifiers, and other
> participants. Users remain responsible for evaluating, configuring, testing, deploying, operating, and maintaining
> their own applications and integrations, including assessing any applicable operational, security, technical, and
> legal or regulatory risks. Please review the [Chainlink Terms of Service](https://chain.link/terms) which provides
> important information and disclosures. By using Chainlink CCIP, you expressly acknowledge and agree to accept these
> terms. Cross-Chain Verifiers (CCVs) may be operated by third parties. The security, availability, governance, and
> operational profile of a CCV varies depending on the verifier selected. Users are solely responsible for evaluating
> any CCVs used in connection with their applications or integrations and determining whether they are appropriate for
> their intended use case. This code represents an example of using a Chainlink product or service. It is provided "AS
> IS" and "AS AVAILABLE" without warranties of any kind, has not been audited, and may omit checks or error handling.
> Each party intending to use this reference implementation must perform its own audits, security and code review, and
> testing before any production deployment and ensure the operation and performance of such code matches expectations.
> Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for outcomes due to
> errors in this example or how it is deployed or operated. Use of the Chainlink Network is subject to the Chainlink
> Foundation Terms of Service, which provides important information and disclosures. By using this code, you acknowledge
> and agree to these terms.