CCIP Architecture in Depth

How Chainlink CCIP works under the hood

CCIP concepts

Before you explore how Chainlink CCIP works in depth, it is best to understand the core concepts.

Interoperability

Interoperability is the ability to exchange information between different systems or networks, even if they are incompatible. Shared concepts on different networks ensure that each party understands and trusts the exchanged information. It also considers the concept of finality to establish trust in the exchanged information by validating its accuracy and integrity.

Finality

Finality refers to the state of irreversibility and permanent record of a transaction on the blockchain. If the parameters for finality are properly set, the likelihood of irreversibility is extremely low. For CCIP, source chain finality is the main factor that determines the end-to-end elapsed time for CCIP to send a message from one chain to another.

Finality varies across different networks. Some networks offer instant finality and others require multiple confirmations. These time differences are set to ensure the security of CCIP and its users. Finality is crucial for token transfers because funds are locked and not reorganized once they are released onto the destination chain. In this scenario, finality ensures that funds on the destination chain are available only after they have been successfully committed on the source chain.

Lane

To recap, a Chainlink CCIP lane is a distinct pathway between a source and a destination blockchain. Lanes are unidirectional. For instance, Ethereum Sepolia => Polygon Mumbai and Polygon Mumbai => Ethereum Sepolia are two different lanes.

Decentralized Oracle Network (DON)

Chainlink Decentralized Oracle Networks, or DONs, run Chainlink OCR2. The protocol runs in rounds during which an observed data value might be agreed upon. The output of this process results in a report which is attested to by a quorum of participants. The report is then transmitted on-chain by one of the participants. No single participant is responsible for transmitting on every round, and all of them will attempt to do so in a round-robin fashion until a transmission has taken place.

In the context of CCIP, a lane contains two OCR DON committees that monitor transactions between a source and destination blockchain: the Committing DON and Executing DON. More on that in the next subchapter.

High-Level Architecture

As mentioned in the Getting Started chapter with Chainlink CCIP, one can:

  • Transfer (supported) tokens

  • Send any kind of data

  • Send both tokens and data

CCIP receiver can be:

  • EOA

  • Any smart contract that implements CCIPReceiver.sol

Basic CCIP Architecture

Note: If you send a message and token(s) to EOA, only tokens will arrive.

Going down the rabbit hole

The figure below outlines the different components involved in a cross-chain transaction:

  • Cross-Chain dApps are user-specific. A smart contract or an EOA (Externally Owned Account) interacts with the CCIP Router to send arbitrary data and/or transfer tokens cross-chain.

  • The contracts in dark blue are the CCIP interface (Router.sol). As we already learned, to use CCIP, users need only to understand how to interact with the router; they don't need to understand the whole CCIP architecture. Note: The CCIP interface is static and remains consistent over time to provide reliability and stability to the users.

  • The contracts in light blue are internal to the CCIP protocol and subject to change.

CCIP Architecture

Now let's explain each of the components from the above diagram.

On-chain components

1) Router

The Router is the primary contract CCIP users interface with. This contract is responsible for initiating cross-chain interactions. One router contract exists per chain. When transferring tokens, callers have to approve tokens for the router contract.

The router contract routes the instruction to the destination-specific OnRamp.

When a message is received on the destination chain, the router is the contract that “delivers” tokens to the user's account or the message to the receiver's smart contract.

Routing message code

2) OnRamp

As mentioned in the previous paragraph, the router contract routes the instruction to the destination-specific OnRamp. One OnRamp contract per lane exists.

This contract performs the following tasks:

  • Checks destination-blockchain-specific validity, such as validating account address syntax

  • Verifies the message size limit and gas limits

  • Keeps track of sequence numbers to preserve the sequence of messages for the receiver

  • Manages billing

  • Interacts with the TokenPool if the message includes a token transfer.

  • Emits an event monitored by the committing DON

Forwarding from Router code

3) Token Pool

Each token has its own token pool, an abstraction layer over ERC-20 tokens that facilitates OnRamp and OffRamp token-related operations.

Token pools are configurable to lock or burn at the source blockchain and unlock or mint at the destination blockchain. The mechanism for handling tokens depends on the characteristics of the token in question. Token handling mechanisms are described in the Token handling mechanisms paragraph.

Token pools provide rate limiting, which is a security feature enabling token issuers to set a maximum rate at which their token can be transferred. Rate limits are described in the Rate Limits paragraph.

Lock or Burn code (called by the OnRamp)
Unlock or Mint code (called by the OffRamp)

4) Risk Management Network contract

The Risk Management Network contract maintains the list of Risk Management Network nodes' addresses allowed to bless/curse and holds the quorum logic for blessing a committed Merkle Root and cursing CCIP on a destination blockchain.

Risk Management Network is described in the Risk Management Network chapter.

5) Commit Store

The Committing DON interacts with the CommitStore contract on the destination blockchain to store the Merkle root of the finalized messages on the source blockchain. This Merkle root must be blessed by the Risk Management Network before Executing DON can execute them on the destination blockchain. The CommitStore ensures the message is blessed by the Risk Management Network and only one CommitStore exists per lane.

Commit reporting code

6) OffRamp

One OffRamp contract per lane exists.

This contract performs the following tasks:

  • Ensures the message is authentic by verifying the proof provided by the Executing DON against a committed and blessed Merkle root

  • Makes sure transactions are executed only once

  • After validation, the OffRamp contract transmits any received message to the Router contract. If the CCIP transaction includes token transfers, the OffRamp contract calls the TokenPool to transfer the correct assets to the receiver.

Message execution code

Off-chain components

1) Committing DON

The Committing DON has several jobs where each job monitors cross-chain transactions between a given source blockchain and destination blockchain:

  • Each job monitors events from a given OnRamp contract on the source blockchain.

  • The job waits for finality, which depends on the source blockchain.

  • The job bundles transactions and creates a Merkle root. This Merkle root is signed by a quorum of oracles nodes part of the Committing DON.

  • Finally, the job writes the Merkle root to the CommitStore contract on the given destination blockchain.

2) Executing DON

Like the Committing DON, the Executing DON has several jobs where each executes cross-chain transactions between a source blockchain and a destination blockchain:

  • Each job monitors events from a given OnRamp contract on the source blockchain.

  • The job checks whether the transaction is part of the relayed Merkle root in the CommitStore contract.

  • The job waits for the Risk Management Network to bless the message.

  • Finally, the job creates a valid Merkle proof, which is verified by the OffRamp contract against the Merkle root in the CommitStore contract. After these check pass, the job calls the OffRamp contract to complete the CCIP transactions on the destination blockchain.

Separating commitment and execution permits the Risk Management Network to have enough time to check the commitment of messages before executing them. The delay between commitment and execution also permits additional checks such as abnormal reorg depth, potential simulation, and slashing.

Saving a commitment is compact and has a fixed gas cost, whereas executing user callbacks can be highly gas intensive. Separating commitment and execution permits execution by end users in various cases, such as retrying failed executions.

3) Risk Management Network

The Risk Management Network is a set of independent nodes that monitor the Merkle roots committed by the Committing DON into the Commit Store.

Each node compares the committed Merkle roots with the transactions received by the OnRamp contract. After the verification succeeds, it calls the Risk Management Network contract to "bless" the committed Merkle root. When there are enough blessing votes, the root becomes available for execution. In case of anomalies, each Risk Management Network node calls the Risk Management Network contract to "curse" the system. If the cursed quorum is reached, the Risk Management Network contract is paused to prevent any CCIP transaction from being executed.

Risk Management Network is described in the Risk Management Network chapter.

Processing the CCIP Message

CCIP Message Flow

We will now see what the workflow of a CCIP Message looks like in a couple of steps.

Step 1: Prepare

  1. The Sender prepares a CCIP Message (EVM2AnyMessage) for their cross-chain transaction to a destination blockchain (chainSelector) of choice. A CCIP message includes the following information:

    • Receiver

    • Data payload

    • Tokens and amounts

    • Fee token

    • Additional parameters (gasLimit, strict)

  2. The Sender calls Router.getFee() to receive the total fees (gas + premium) to pay CCIP and approves the requested fee amount.

  3. The Sender calls Router.ccipSend(), providing the CCIP Message they want to send along with their desired destination chainSelector. In the case of token transfers, the amount to be transferred must be approved to the Router.

Step 2: Send

  1. The Router validates the received Message (e.g., valid and supported destination chainId and supported tokens at the destination chain).

  2. The Router receives and transfers fees to the OnRamp.

  3. The Router receives and transfers tokens to its corresponding Token Pool. If the sender has not approved the tokens to the Router, this will fail.

  4. The Router forwards the Message to the correct OnRamp (based on destination chainSelector) for processing:

    • Validate the message (number of tokens, gasLimit, data length …)

    • [For token transfers] Ensure that the transferred value does not hit the aggregate rate limit of the lane.

    • Sequence the message with a sequence number.

    • For each Token included in the Message: instruct the token pool to lock/burn the tokens. This will also validate the token pool rate limit for this lane.

  5. The OnRamp emits an event containing the sequenced message. This triggers the DONs to process the message.

  6. A messageId is generated and returned to the Sender.

Step 3: Committing DON

  1. Nodes in the Committing DON listen for events of Messages that are ready to be sent

  2. Messages must be finalized to be considered secure against reorg attacks.

  3. Triggered by time or number of messages queued in the OnRamp, the Committing DON creates a Report with a commitment of all messages ready to be sent, in a batch. This commitment takes the form of a Merkle Root

  4. Upon consensus in the Committing DON, the Report containing the Merkle Root is transmitted to the CommitStore contract on the destination chain

  5. The Risk Management Network “blesses” the Merkle Root in the CommitStore, to make sure it is a correct representation of the queued messages at the OnRamp.

Merkle Root & Merkle Proof

  • Merkle Root: the root hash of the Merkle Tree. It is a commitment to all the leaves (Messages M1-M4) in the tree. Each node in the tree is the hash of the nodes below it.

  • Merkle Proof: to prove that Message M1 is included in the Merkle Root (commitment), a Prover provides the following elements as proof to a Verifier (who only has the root hash):

    • M1

    • H(M2)

    • H(H(M3),H(M4))

    Using this Merkle Proof, a Verifier can easily verify that, indeed, M1 is included in the commitment (root hash) that it possesses.

Step 4: Executing DON

  1. Nodes in the Executing DON listen for events of Messages that are ready to be sent, similar to the Committing DON

  2. Messages must be finalized to be considered secure against reorg attacks.

  3. In addition to the time or number of messages queued in the OnRamp, the Executing DON also monitors the CommitStore to make sure the messages are ready to be executed at the destination chain, i.e., are the messages included in a blessed on-chain commitment?

  4. If conditions are met, the Executing DON creates a Report with all messages ready to be sent, in a batch. It accounts for the gasLimit of each message in its batching logic. It also calculates a relevant Merkle Proof for each message to prove that the message is included in the Merkle Root submitted by the Committing DON in the CommitStore. Note that Executing DON batches can be any subset of a Committing DON batch.

  5. Upon consensus, the Report is transmitted to the OffRamp contract on the destination chain

Step 5: Execute

  1. For each message in the batch received, the OffRamp verifies using the provided Merkle Proof whether the transaction is included in the blessed commitment in the CommitStore.

  2. If tokens are included in the transaction, the OffRamp validates the aggregate rate limit of the lane and identifies the matching Token Pool(s).

  3. OffRamp calls the TokenPool’s unlock/mint function. This will validate the token pool rate limit, unlock or mint the token and transfer them to the specified receiver.

  4. If the receiver is not an EOA and has the correct interface implemented, the OffRamp uses the Router to call the receiver’s ccipReceive() function

  5. The receiver processes the message and is informed where the message comes from (blockchain + sender), the tokens transferred, and the data payload (with relevant instructions).

  6. Based on the data payload, the receiver might transfer the tokens to the final recipient (end-user)

Token handling mechanisms

To transfer tokens using CCIP, token pools on both blockchains must exist. That's why, if you remember the first chapter of this Masterclass, we said that you can transfer only supported tokens, not all of them.

Technically, tokens are not transferred. Instead, they are locked or burned on the source chain and then unlocked or minted on the destination chain

Token handling mechanisms are a key aspect of how token transfers work. They each have different characteristics with trade-offs for issuers, holders, and DeFi applications.

Burn & Mint

Tokens are burned on the source chain and minted natively on the destination chain

Burn & Mint
  • Use cases:

    • Tokens that are natively minted on multiple blockchains and have a variable total supply.

    • Examples: stablecoins, synthetic/derivative tokens, wrapped tokens (from Lock & Mint)

Lock & Mint (Reverse: Burn & Unlock)

Tokens are locked on the source chain (in Token Pools), and wrapped/synthetic/derivative tokens that represent the locked tokens are minted on the destination chain.

Lock & Mint (Reverse: Burn & Unlock)
  • Use cases:

    • Tokens minted on a single chain (e.g., LINK)

    • Tokens with encoded constraints (supply/burn/mint)

    • Secure minting function with Proof-of-Reserve

Lock & Unlock [ON THE ROADMAP]

Transferred tokens are locked on the source chain (in Token Pools) and unlocked from Token Pools on the destination chain. This feature is not live yet.

Lock & Unlock
  • Use cases:

    • Canonical/dominant wrapped tokens (eg: WETH, LINK …)

Rate Limits

The main objective of Rate Limits is to manage risk by limiting the value flowing through CCIP. A rate limit always applies to a lane.

Each available lane has two rate limits:

  • Network rate limit: Each network has a limit on the total USD value that can be transferred from one network across all available lanes. CCIP uses Chainlink Data Feeds to calculate the total USD value of tokens transferred on one network.

  • Token rate limits: Each individual lane on a network might have a limit on the total number of tokens that it can transfer. This limit is independent of the USD value of the tokens.

Both limits have the following concepts:

  • Bucket: holds the value that can be transferred at any given moment.

  • Capacity: the capacity of the bucket represents the maximum value that can be transferred at once via a single transaction.

  • Refill Rate: the rate at which the bucket is refilled, denominated per second.

  • Availability: the current amount of value in the bucket to be transferred.

Token Bucket Rate Limiting code

Let's see how these Rate Limits work in an example:

  • Capacity = $100,000

  • Refill rate = $100 per second

  • Simulation

    • t=0s: the bucket is empty

    • t=300s: availability of $30,000

    • t=300s: user tries to send $40,000 → has to wait until t=400s when bucket has $40,000 available

    • t=400s: user submits $40,000 transfer again → transfer is executed, availability is $0

    • t=500s: availability of $10,000

    • t=1400s: bucket has maxed at $100,000 capacity

You can check these limits for each lane on the Official Chainlink Documentation. Here are the parameters for the real-world example, Ethereum Sepolia => Optimism Goerli lane:

Rate Limits

If a rate limit is hit, the following errors can be returned. The user can handle these gracefully in their front end so end-users are optimally informed to decide their next step. In the case of multiple tokens in a single token transfer, the error is returned for the first hit. That is why tokenAddress is included in the error as a parameter.

  • TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress); - User requests to transfer more of a token than the capacity of the bucket.

  • TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress); - User requests to transfer more of a token than is currently available in the bucket. The User might have to wait at least minWaitInSeconds for enough availability or transfer the currently available amount.

  • AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested); - User requests to transfer more value than the capacity of the aggregate rate limit bucket.

  • AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available); - User requests to transfer more value than currently available in the bucket. The User might have to wait at least minWaitInSeconds for enough availability or transfer the currently available amount.

Risk Management Network

The Risk Management Network is built using off-chain and on-chain components:

  • Off-chain: Several Risk Management Network nodes continually monitor all supported chains against abnormal activities

  • On-chain: One Risk Management Network contract per supported CCIP chain

Off-chain (Risk Management Network nodes)

The Risk Management Network is a secondary validation service parallel to the primary CCIP system. It doesn't run the same codebase as the DON to mitigate against security vulnerabilities that might affect the DON's codebase. The Risk Management Network has two main modes of operation:

  • Blessing: Each Risk Management Network node monitors all Merkle roots of messages committed on each destination chain. The Committing DON commits these Merkle roots. The Risk Management Network node independently reconstructs the Merkle tree by fetching all messages on the source chain. Then, it checks for a match between the Merkle root committed by the Committing DON and the root of the reconstructed Merkle tree. If both Merkle roots match, the Risk Management Network node blesses the root to the Risk Management Network contract on the destination chain. The Risk Management Network contract tracks the votes. When a quorum is met, the Risk Management Network contract dubs the Merkle root blessed.

  • Cursing: If a Risk Management Network node detects an anomaly, the Risk Management Network node will curse the CCIP system. After a quorum of votes has been met, the Risk Management Network contract dubs the CCIP system cursed. CCIP will automatically pause on that chain and wait until the contract owner assesses the situation before potentially lifting the curse. There are two cases where Risk Management Network nodes pause CCIP:

    • Finality violation: A deep reorganization that violates the safety parameters set by the Risk Management Network configuration occurs on a CCIP chain.

    • Execution safety violation: A message is executed on the destination chain without any matching transaction being on the source chain. Double executions fall into this category since the executing DON can only execute a message once.

On-chain (Risk Management Network contract)

There is one Risk Management Network contract for each supported destination chain. The Risk Management Network contract maintains a group of nodes authorized to participate in the Risk Management Network blessing/cursing. Each Risk Management Network node has five components:

  • an address for voting to curse

  • an address for voting to bless

  • an address for withdrawing a vote to curse

  • a curse weight

  • a blessing weight

Risk Management Network Node representation

The contract also maintains two thresholds to determine the quorum for blessing and cursing. There are two different voting logics depending on the mode:

  • Blessing voting procedure: every time a Risk Management Network node blesses a Merkle root, the Risk Management Networkcontract adds the blessing weight for that node. If the sum exceeds the blessing threshold, the Risk Management Network contract considers the contract blessed.

  • Cursing voting procedure: a Risk Management Network node that sends a vote to curse assigns the vote a random 32-byte ID. The node may have multiple active votes to curse at any time. However, if there is at least one active cursing vote, the Risk Management Network contract considers the node to have voted to curse. The Risk Management Network contract adds the cursing weight for that node. If the sum of the weights of votes to curse exceeds the curse threshold, the Risk Management Network contract considers the contract cursed.

Blessing code

If the Risk Management Network contract is cursed, then the owner of the original contract must resolve any underlying issues the original contract might have. If the owner is satisfied that these issues have been resolved, they can revoke the cursing on behalf of Risk Management Network nodes.

Cursing code

That's a wrap 🎉

If you made it this far, congratulations! You were amazing!

It was an intense ride, but this is the end of our CCIP Masterclass. If you are still curious about what you can build with CCIP, there is a Going Beyond Masterclass section.

Last updated