Skip to main content

Chainlink CCIP Integration

Chainlink’s Cross-Chain Interoperability Protocol (CCIP) provides a decentralized, secure method for relaying intent fulfillment proofs between blockchains. The CCIPProver leverages Chainlink’s oracle network for reliable cross-chain messaging.

Overview

CCIP offers several advantages for cross-chain proving:
  • Decentralized Security: Multi-oracle verification with Risk Management Network
  • Reliability: Guaranteed message delivery with automatic retries
  • Wide Network Support: Available on 15+ EVM chains
  • Message Tracking: Built-in explorer for monitoring cross-chain transactions
  • Active-Active Architecture: No single point of failure

Contract Architecture

CCIPProver
├── ROUTER (CCIP Router address)
├── MIN_GAS_LIMIT (minimum execution gas)
└── Whitelist (authorized provers)
Contract Location: contracts/prover/CCIPProver.sol

Constructor Parameters

constructor(
    address router,        // CCIP Router for this chain
    address portal,        // Portal contract address
    bytes32[] memory provers,  // Whitelisted prover addresses
    uint256 minGasLimit    // Minimum gas for execution (0 = 200k default)
)
router
address
required
The CCIP Router contract address for the current chain. Find addresses in CCIP documentation.
portal
address
required
The Portal contract address on this chain.
provers
bytes32[]
required
Array of whitelisted prover addresses on other chains. Use bytes32 format for cross-VM compatibility.
minGasLimit
uint256
Minimum gas limit for cross-chain execution. Pass 0 to use the default 200,000 gas.

Proving Flow

1

Fulfill intent on destination chain

A solver fulfills an intent on the destination chain via the Portal contract.
2

Call prove() on CCIPProver

The prover or solver calls prove() on the destination chain’s CCIPProver with payment for CCIP fees.
bytes memory encodedProofs = abi.encode(intentHashes, claimants);
bytes memory data = abi.encode(sourceChainProver, gasLimit);

uint256 fee = ccipProver.fetchFee(sourceChainSelector, encodedProofs, data);

ccipProver.prove{value: fee}(
    intentHash,
    sourceChainSelector,  // CCIP chain selector, not chain ID!
    encodedProofs,
    data
);
3

CCIP delivers message

Chainlink’s oracle network validates and delivers the message to the source chain.
4

ccipReceive processes proof

The source chain’s CCIPProver receives the message via ccipReceive() and creates a proof record.
5

Withdraw rewards

The solver can now call withdraw() on the source chain Portal to claim their rewards.

Chain Selectors vs Chain IDs

CCIP uses chain selectors, not standard chain IDs. Always use the correct selector when calling prove().

Common Chain Selectors

NetworkChain IDCCIP Chain Selector
Ethereum Mainnet15009297550715157269
Ethereum Sepolia1115511116015286601757825753
Polygon Mainnet1374051577828743386545
Polygon Amoy8000216281711391670634445
Arbitrum One421614949039107694359620
Arbitrum Sepolia4216143478487238524512106
Optimism Mainnet103734403246176062136
Optimism Sepolia111554205224473277236331295
Base Mainnet845315971525489660198786
Base Sepolia8453210344971235874465080
Avalanche C-Chain431146433500567565415381
Avalanche Fuji4311314767482510784806043
Always verify selectors in the official CCIP documentation as they may change.

Data Parameter Structure

The data parameter encodes information needed for cross-chain message delivery:
struct UnpackedData {
    address sourceChainProver;  // Prover address on source chain
    uint256 gasLimit;           // Gas limit for execution
}

bytes memory data = abi.encode(sourceChainProver, gasLimit);
sourceChainProver
address
required
The CCIPProver contract address on the source chain where the proof will be delivered.
gasLimit
uint256
required
Gas limit for proof processing on the source chain. Automatically enforced to be at least MIN_GAS_LIMIT. Maximum is 3,000,000 gas.

Fee Calculation

CCIP fees depend on:
  • Destination chain: Different chains have different costs
  • Gas limit: Higher execution limits cost more
  • Message size: Larger proofs (up to 30KB) increase fees
  • Network congestion: Dynamic pricing based on demand
// Query fee before sending
uint256 fee = ccipProver.fetchFee(
    destinationChainSelector,
    encodedProofs,
    data
);

// Include 10% buffer for gas price fluctuations
uint256 feeWithBuffer = fee * 110 / 100;

// Send proof with payment
ccipProver.prove{value: feeWithBuffer}(
    intentHash,
    destinationChainSelector,
    encodedProofs,
    data
);
Always add a fee buffer (5-10%) to account for gas price fluctuations between fee calculation and transaction execution.

Message Limits

CCIP has strict message limits that must be respected:
  • Maximum payload size: 30 KB (30,720 bytes)
  • Maximum gas limit: 3,000,000 gas
  • Minimum gas limit: 200,000 gas (configurable via minGasLimit)
If you need to prove many intents at once, batch them carefully to stay within the 30KB limit.

CCIP Router Addresses

Each chain has its own CCIP Router contract. Here are key mainnet addresses:
ChainRouter Address
Ethereum0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D
Polygon0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe
Arbitrum0x141fa059441E0ca23ce184B6A78bafD2A517DdE8
Optimism0x261c05aFb2f581bC4119aee23A8E67947EaaC7c0
Base0x881e3A65B4d4a04dD529061dd0071cf975F58bCD
Avalanche0xF4c7E640EdA248ef95972845a62bdC74237805dB
Testnet router addresses are different. Check the CCIP documentation for current addresses.

Deployment Example

// Deploy CCIPProver on destination chain
address ccipRouter = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D; // Ethereum Router
address portal = 0x...; // Portal on this chain

// Whitelist the source chain prover
bytes32[] memory provers = new bytes32[](1);
provers[0] = bytes32(uint256(uint160(0x...))); // Source chain CCIPProver

CCIPProver prover = new CCIPProver(
    ccipRouter,
    portal,
    provers,
    300000 // 300k minimum gas
);

// Deploy matching CCIPProver on source chain with reciprocal whitelist

Monitoring and Debugging

CCIP Explorer

Track your cross-chain messages using the CCIP Explorer:
  1. Copy the transaction hash from your prove() call
  2. Enter it in the CCIP Explorer
  3. View message status: Pending, Success, or Failed
  4. See execution details and any error messages

Common Issues

Cause: Gas prices increased between fee calculation and transaction execution.Solution: Always add a 5-10% buffer to the calculated fee.
Cause: Gas limit too low or invalid proof data.Solution: Increase gas limit in the data parameter or verify proof encoding.
Cause: The destination chain prover is not in the source chain’s whitelist.Solution: Add the prover address to the whitelist via contract upgrade or constructor.
Cause: Using chain ID instead of CCIP chain selector.Solution: Verify you’re using the correct CCIP chain selector from the table above.

Security Considerations

  1. Decentralized Oracles: CCIP uses multiple independent oracles for message verification
  2. Risk Management Network: Additional security layer monitors for anomalies
  3. Whitelist Enforcement: Only approved provers can submit proofs
  4. Router Authorization: Only the CCIP Router can call ccipReceive()
  5. Gas Limit Validation: Enforces minimum gas to prevent execution failures

Best Practices

  • Fee Estimation: Always query fees with fetchFee() before proving
  • Fee Buffer: Add 5-10% buffer for gas price volatility
  • Batch Proofs: Combine multiple proofs to save on fixed CCIP costs
  • Monitor Status: Use CCIP Explorer to track message delivery
  • Test First: Verify on testnets before mainnet deployment
  • Whitelist Management: Keep prover whitelists synchronized across chains

Integration Example

Complete example of integrating CCIP proving into your solver:
contract MySolver {
    CCIPProver public ccipProver;
    Portal public portal;
    
    function fulfillAndProve(
        Intent memory intent,
        uint64 sourceChainSelector
    ) external payable {
        // 1. Fulfill intent on destination chain
        portal.fulfill(intent.hash, intent.route, intent.rewardHash, msg.sender);
        
        // 2. Prepare proof data
        bytes32[] memory intentHashes = new bytes32[](1);
        intentHashes[0] = intent.hash;
        
        bytes32[] memory claimants = new bytes32[](1);
        claimants[0] = bytes32(uint256(uint160(msg.sender)));
        
        bytes memory encodedProofs = abi.encode(intentHashes, claimants);
        
        // 3. Calculate CCIP fee
        address sourceProver = ccipProver.getWhitelistedProver(sourceChainSelector);
        bytes memory data = abi.encode(sourceProver, 500000); // 500k gas
        
        uint256 fee = ccipProver.fetchFee(sourceChainSelector, encodedProofs, data);
        uint256 feeWithBuffer = fee * 110 / 100; // 10% buffer
        
        // 4. Send proof via CCIP
        ccipProver.prove{value: feeWithBuffer}(
            intent.hash,
            sourceChainSelector,
            encodedProofs,
            data
        );
        
        // 5. Refund excess fee
        if (msg.value > feeWithBuffer) {
            payable(msg.sender).transfer(msg.value - feeWithBuffer);
        }
    }
}

External Resources

Build docs developers (and LLMs) love