6.9 KiB
6.9 KiB
CCIP Integration Guide for Developers
Date: 2025-01-27
Network: ChainID 138 (DeFi Oracle Meta Mainnet)
Overview
This guide provides technical details for developers integrating with CCIP infrastructure on ChainID 138.
Architecture
Components
- CCIP Router: Routes messages between chains
- CCIP Bridges: Handle token transfers
- CCIP Sender/Receiver: Handle oracle data transmission
Message Flow
Source Chain → CCIP Router → Destination Chain → CCIP Router → Receiver
Integration Patterns
Pattern 1: Token Bridge Integration
// Import bridge interface
import "./IC CIPWETH9Bridge.sol";
contract MyContract {
CCIPWETH9Bridge public bridge;
function transferCrossChain(
uint64 destinationSelector,
address recipient,
uint256 amount
) external {
// Approve bridge
weth9.approve(address(bridge), amount);
// Send cross-chain
bytes32 messageId = bridge.sendCrossChain(
destinationSelector,
recipient,
amount
);
emit TransferInitiated(messageId, destinationSelector, amount);
}
}
Pattern 2: Oracle Data Integration
import "./CCIPSender.sol";
import "./CCIPReceiver.sol";
contract MyOracleContract {
CCIPSender public sender;
CCIPReceiver public receiver;
function sendOracleUpdate(
uint64 destinationSelector,
uint256 answer,
uint256 roundId,
uint256 timestamp
) external payable {
bytes32 messageId = sender.sendOracleUpdate{value: msg.value}(
destinationSelector,
answer,
roundId,
timestamp
);
}
function ccipReceive(
IRouterClient.Any2EVMMessage calldata message
) external {
// Process received oracle data
(uint256 answer, uint256 roundId, uint256 timestamp) =
abi.decode(message.data, (uint256, uint256, uint256));
// Update oracle
updateOracle(answer, roundId, timestamp);
}
}
Pattern 3: Custom Message Sending
import "./IRouterClient.sol";
contract MyCustomContract {
IRouterClient public router;
function sendCustomMessage(
uint64 destinationSelector,
address receiver,
bytes memory data
) external payable {
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: address(0), // Native ETH
extraArgs: ""
});
uint256 fee = router.getFee(destinationSelector, message);
(bytes32 messageId, ) = router.ccipSend{value: fee}(
destinationSelector,
message
);
emit MessageSent(messageId, destinationSelector);
}
}
Interfaces
IRouterClient
interface IRouterClient {
function ccipSend(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external payable returns (bytes32 messageId, uint256 fees);
function getFee(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external view returns (uint256 fee);
}
CCIPWETH9Bridge / CCIPWETH10Bridge
interface ICCIPBridge {
function sendCrossChain(
uint64 destinationChainSelector,
address recipient,
uint256 amount
) external returns (bytes32 messageId);
function calculateFee(
uint64 destinationChainSelector,
uint256 amount
) external view returns (uint256 fee);
function ccipReceive(
IRouterClient.Any2EVMMessage calldata message
) external;
}
Event Monitoring
Router Events
event MessageSent(
bytes32 indexed messageId,
uint64 indexed destinationChainSelector,
address indexed sender,
bytes receiver,
bytes data,
TokenAmount[] tokenAmounts,
address feeToken,
bytes extraArgs
);
event MessageReceived(
bytes32 indexed messageId,
uint64 indexed sourceChainSelector,
address indexed sender,
bytes data,
TokenAmount[] tokenAmounts
);
Bridge Events
event CrossChainTransferInitiated(
bytes32 indexed messageId,
address indexed sender,
uint64 indexed destinationChainSelector,
address recipient,
uint256 amount,
uint256 nonce
);
event CrossChainTransferCompleted(
bytes32 indexed messageId,
uint64 indexed sourceChainSelector,
address indexed recipient,
uint256 amount
);
Error Handling
Common Errors
// Router errors
"CCIPRouter: chain not supported"
"CCIPRouter: insufficient native token fee"
"CCIPRouter: duplicate message"
// Bridge errors
"CCIPWETH9Bridge: destination not enabled"
"CCIPWETH9Bridge: transfer failed"
"CCIPWETH9Bridge: transfer already processed"
Error Handling Pattern
try bridge.sendCrossChain(selector, recipient, amount) returns (bytes32 messageId) {
emit TransferSuccess(messageId);
} catch Error(string memory reason) {
emit TransferFailed(reason);
} catch (bytes memory lowLevelData) {
emit TransferFailed("Low-level error");
}
Testing
Unit Tests
function testCrossChainTransfer() public {
// Setup
uint256 amount = 1 ether;
uint64 destSelector = 5009297550715157269; // Ethereum Mainnet
// Approve
weth9.approve(address(bridge), amount);
// Send
bytes32 messageId = bridge.sendCrossChain(destSelector, recipient, amount);
// Verify
assertTrue(messageId != bytes32(0));
emit TransferInitiated(messageId);
}
Integration Tests
Test end-to-end flow:
- Send message on source chain
- Verify message received on destination
- Verify state changes
- Test error cases
Gas Optimization
Tips
- Batch Operations: Group multiple operations when possible
- Optimize Data: Minimize message data size
- Cache Values: Cache frequently accessed values
- Use Events: Emit events instead of storage for logging
Gas Estimates
- Router Message: ~50,000 - 100,000 gas
- Bridge Transfer: ~100,000 - 200,000 gas
- Fee Calculation: ~5,000 gas (view function)
Best Practices
- Always Calculate Fees: Don't hardcode fee amounts
- Handle Errors: Implement comprehensive error handling
- Monitor Events: Track all CCIP events
- Verify Addresses: Always verify contract addresses
- Test Thoroughly: Test on testnet before mainnet
Related Documentation
Last Updated: 2025-01-27