# Chainlink Local API Reference Source: https://docs.chain.link/chainlink-local/api-reference ## Available Versions ### Latest Release - **[Chainlink Local v0.2.3](/chainlink-local/api-reference/v0.2.3)** (Current Version) - Added support for `EVMExtraArgsV2` and OZ's `AccessControl` in CCIPLocalSimulator - Added `supportNewTokenViaAccessControlDefaultAdmin` function - Bumped `@chainlink/contracts-ccip` to `1.5.1-beta.0` - Enhanced token pool support and EVMExtraArgsV2 compatibility ### Previous Versions - **[Chainlink Local v0.2.2](/chainlink-local/api-reference/v0.2.2)** (Legacy Version) - Added support for CCIP v1.5 - Enhanced token support functions - Updated network configurations - Improved testing utilities - **[Chainlink Local v0.2.1](/chainlink-local/api-reference/v0.2.1)** (Legacy Version) - Added support for Chainlink Data Feeds - Introduced mock aggregator contracts - Basic testing framework setup ## Documentation Structure Each version includes detailed documentation for: - CCIP Components (Router, Simulator) - Data Feed Contracts - Token Implementations - JavaScript Utilities - Testing Helpers --- # AggregatorInterface v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/aggregator-interface ## AggregatorInterface [`AggregatorInterface`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/data-feeds/interfaces/AggregatorInterface.sol) defines the standard interface for Chainlink Data Feed aggregators. This interface provides methods to access price feed data and timestamps, as well as historical values. ## Events ### AnswerUpdated ```solidity event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt) ``` Emitted when a new answer is available for a price feed. | Parameter | Type | Description | | --------- | --------- | -------------------------------------- | | current | `int256` | The new price value (indexed) | | roundId | `uint256` | The round ID for this update (indexed) | | updatedAt | `uint256` | Timestamp when this answer was updated | ### NewRound ```solidity event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt) ``` Emitted when a new round of price updates begins. | Parameter | Type | Description | | --------- | --------- | ------------------------------------------ | | roundId | `uint256` | The round ID that started (indexed) | | startedBy | `address` | Address that initiated the round (indexed) | | startedAt | `uint256` | Timestamp when the round started | ## Functions ### getAnswer ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` Retrieves the answer for a specific round. **Parameters:** | Parameter | Type | Description | | --------- | --------- | --------------------- | | roundId | `uint256` | The round ID to query | **Returns:** | Type | Description | | -------- | ---------------------------------------- | | `int256` | The price answer for the specified round | ### getTimestamp ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` Retrieves the timestamp for a specific round. **Parameters:** | Parameter | Type | Description | | --------- | --------- | --------------------- | | roundId | `uint256` | The round ID to query | **Returns:** | Type | Description | | --------- | ---------------------------------------- | | `uint256` | The timestamp when the round was updated | ### latestAnswer ```solidity function latestAnswer() external view returns (int256) ``` Retrieves the most recent answer. **Returns:** | Type | Description | | -------- | ---------------------------- | | `int256` | The latest price feed answer | ### latestRound ```solidity function latestRound() external view returns (uint256) ``` Retrieves the most recent round ID. **Returns:** | Type | Description | | --------- | ------------------- | | `uint256` | The latest round ID | ### latestTimestamp ```solidity function latestTimestamp() external view returns (uint256) ``` Retrieves the timestamp of the latest answer. **Returns:** | Type | Description | | --------- | ------------------------------------------------ | | `uint256` | The timestamp when the latest answer was updated | --- # AggregatorV2V3Interface v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/aggregator-v2-v3-interface ## AggregatorV2V3Interface [`AggregatorV2V3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/data-feeds/interfaces/AggregatorV2V3Interface.sol) combines the functionality of both AggregatorInterface and AggregatorV3Interface, providing backward compatibility while supporting newer Data Feed features. ## Interface Inheritance ```solidity interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {} ``` This interface provides access to all functions and events from both parent interfaces. --- # AggregatorV3Interface v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/aggregator-v3-interface ## AggregatorV3Interface [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/data-feeds/interfaces/AggregatorV3Interface.sol) defines the extended interface for Chainlink Data Feed aggregators, providing comprehensive round data and metadata about the feed. ## Functions ### decimals ```solidity function decimals() external view returns (uint8) ``` Retrieves the number of decimals used to format the answer. **Returns:** | Type | Description | | ------- | -------------------------------------- | | `uint8` | The number of decimals in the response | ### description ```solidity function description() external view returns (string memory) ``` Retrieves the description of the price feed. **Returns:** | Type | Description | | -------- | --------------------------------- | | `string` | The description of the price feed | ### getRoundData ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the complete round data for a specific round ID. **Parameters:** | Parameter | Type | Description | | --------- | -------- | --------------------- | | _roundId | `uint80` | The round ID to query | **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The price answer for this round | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### latestRoundData ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the latest round data from the aggregator. **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The latest price answer | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### version ```solidity function version() external view returns (uint256) ``` Retrieves the version number of the aggregator implementation. **Returns:** | Type | Description | | --------- | ------------------------------------ | | `uint256` | The version number of the aggregator | --- # BurnMintERC677Helper v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/burn-mint-erc677-helper ## BurnMintERC677Helper [`BurnMintERC677Helper`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/ccip/BurnMintERC677Helper.sol) is a helper contract that extends the BurnMintERC677 token implementation. It provides simplified functionality for minting tokens, primarily intended for testing and development environments. ## Functions ### constructor Initializes a new BurnMintERC677Helper token with specified name and symbol. | Parameter | Type | Description | | --------- | -------- | ------------------------------ | | name | `string` | The name of the token | | symbol | `string` | The symbol/ticker of the token | ### drip Mints exactly one full token (1e18 base units) to the specified address. | Parameter | Type | Description | | --------- | --------- | ------------------------------------------------- | | to | `address` | The recipient address to receive the minted token | --- # CCIPLocalSimulatorFork v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/ccip-local-simulator-fork-js ## CCIPLocalSimulatorFork [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/main/scripts/CCIPLocalSimulatorFork.js) provides utilities for simulating Cross-Chain Interoperability Protocol (CCIP) operations in a forked environment. It enables testing of cross-chain messaging and token transfers within a Hardhat project. ## Types ### EVM2EVMMessage An object representing a cross-chain message with the following structure: | Property | Type | Description | | ------------------- | ---------------------------------------- | -------------------------------------- | | sourceChainSelector | `bigint` | The chain selector of the source chain | | sender | `string` | The address of the message sender | | receiver | `string` | The address of the message receiver | | sequenceNumber | `bigint` | Message sequence number | | gasLimit | `bigint` | Gas limit for message execution | | strict | `boolean` | Whether strict mode is enabled | | nonce | `bigint` | Message nonce | | feeToken | `string` | Address of the token used for fees | | feeTokenAmount | `bigint` | Amount of fee token | | data | `string` | Message payload data | | tokenAmounts | `Array<{token: string, amount: bigint}>` | Array of token transfers | | sourceTokenData | `Array` | Source token metadata | | messageId | `string` | Unique message identifier | ## Functions ### requestLinkFromTheFaucet Requests LINK tokens from a faucet contract for testing purposes. | Parameter | Type | Description | | ----------- | -------- | ------------------------------------------------------- | | linkAddress | `string` | The address of the LINK contract on the current network | | to | `string` | The address to receive the LINK tokens | | amount | `bigint` | The amount of LINK tokens to request | **Returns**: `Promise` - The transaction hash of the fund transfer ### getEvm2EvmMessage Parses a transaction receipt to extract CCIP message details from the `CCIPSendRequested` event. | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------ | | receipt | `object` | The transaction receipt from the `ccipSend` call | **Returns**: `object | null` - The parsed EVM2EVMMessage object or null if no valid message is found ### routeMessage Routes a sent message from the source network to the destination network in the forked environment. | Parameter | Type | Description | | -------------- | -------- | ------------------------------------------ | | routerAddress | `string` | Address of the destination Router contract | | evm2EvmMessage | `object` | The cross-chain message to be routed | **Returns**: `Promise` - Resolves when the message is successfully routed --- # CCIPLocalSimulatorFork v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/ccip-local-simulator-fork ## CCIPLocalSimulatorFork [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/ccip/CCIPLocalSimulatorFork.sol) enables testing of CCIP cross-chain messaging in forked network environments. This contract provides essential utilities for configuring networks and simulating message routing between chains in a Foundry test environment. ## Events ### CCIPSendRequested ```solidity event CCIPSendRequested(Internal.EVM2EVMMessage message) ``` Emitted when a cross-chain message is requested to be sent through CCIP. ## Variables ### i_register ```solidity Register immutable i_register ``` Stores and manages network configuration details required for CCIP operations. ### LINK_FAUCET ```solidity address constant LINK_FAUCET = 0x4281eCF07378Ee595C564a59048801330f3084eE ``` Provides test LINK tokens to addresses during testing. ### s_processedMessages ```solidity mapping(bytes32 messageId => bool isProcessed) internal s_processedMessages ``` Prevents duplicate processing by tracking which messages have already been handled. ## Functions ### constructor ```solidity constructor() public ``` Initializes the simulator environment by deploying and configuring a persistent Register contract. ### getNetworkDetails ```solidity function getNetworkDetails(uint256 chainId) external view returns (Register.NetworkDetails memory) ``` Retrieves network-specific CCIP configuration details. Use this function to access default settings or custom configurations for a given network. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ---------------------------------------------------------------------------------------------------- | | chainId | `uint256` | The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. Not CCIP chain selector. | **Returns:** | Parameter | Type | Description | | -------------- | ------------------------- | -------------------- | | networkDetails | `Register.NetworkDetails` | A struct containing: | • chainSelector - The unique CCIP Chain Selector • routerAddress - The address of the CCIP Router contract • linkAddress - The address of the LINK token • wrappedNativeAddress - The address of the wrapped native token for CCIP fees • ccipBnMAddress - The address of the CCIP BnM token • ccipLnMAddress - The address of the CCIP LnM token | ### requestLinkFromFaucet ```solidity function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) ``` Transfers LINK tokens from the faucet to a specified address for testing purposes. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ----------------------------------------------- | | to | `address` | The address to which LINK tokens are to be sent | | amount | `uint256` | The amount of LINK tokens to send | **Returns:** | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------------------------------- | | success | `bool` | Returns `true` if the transfer of tokens was successful, otherwise `false` | ### setNetworkDetails ```solidity function setNetworkDetails(uint256 chainId, Register.NetworkDetails memory networkDetails) external ``` Updates or adds CCIP configuration details for a specific blockchain network. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------- | ---------------------------------------------------------------------------------------------------- | | chainId | `uint256` | The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. Not CCIP chain selector. | | networkDetails | `Register.NetworkDetails` | A struct containing: | • chainSelector - The unique CCIP Chain Selector • routerAddress - The address of the CCIP Router contract • linkAddress - The address of the LINK token • wrappedNativeAddress - The address of the wrapped native token for CCIP fees • ccipBnMAddress - The address of the CCIP BnM token • ccipLnMAddress - The address of the CCIP LnM token | ### switchChainAndRouteMessage ```solidity function switchChainAndRouteMessage(uint256 forkId) external ``` Processes a cross-chain message by switching to the destination fork and executing the message. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ------------------------------------------------------------------------------------------------------------ | | forkId | `uint256` | The ID of the destination network fork. This is the returned value of `createFork()` or `createSelectFork()` | --- # CCIPLocalSimulator v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/ccip-local-simulator ## CCIPLocalSimulator [`CCIPLocalSimulator`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/ccip/CCIPLocalSimulator.sol) is a contract that provides local simulation capabilities for Cross-Chain Interoperability Protocol (CCIP) operations. It manages token deployments and routing configurations for testing CCIP functionality. ## Variables ### CHAIN_SELECTOR ```solidity uint64 constant CHAIN_SELECTOR = 16015286601757825753 ``` The unique identifier for the simulated chain in CCIP operations. ### i_ccipBnM ```solidity BurnMintERC677Helper internal immutable i_ccipBnM ``` The CCIP BnM token contract instance used for cross-chain token transfers. ### i_ccipLnM ```solidity BurnMintERC677Helper internal immutable i_ccipLnM ``` The CCIP LnM token contract instance used for cross-chain token transfers. ### i_linkToken ```solidity LinkToken internal immutable i_linkToken ``` The LINK token contract instance used for paying CCIP fees. ### i_mockRouter ```solidity MockCCIPRouter internal immutable i_mockRouter ``` The mock CCIP router contract instance that simulates cross-chain message routing. ### i_wrappedNative ```solidity WETH9 internal immutable i_wrappedNative ``` The wrapped native token contract instance used for fee payments. ## Functions ### constructor Initializes the simulator by deploying necessary token contracts and configuring supported tokens. ### configuration Returns the configuration details for pre-deployed contracts and services needed for local CCIP simulations. **Returns:** | Parameter | Type | Description | | ------------------- | ---------------------- | ------------------------------------- | | chainSelector_ | `uint64` | The unique CCIP Chain Selector | | sourceRouter_ | `IRouterClient` | The source chain Router contract | | destinationRouter_ | `IRouterClient` | The destination chain Router contract | | wrappedNative_ | `WETH9` | The wrapped native token contract | | linkToken_ | `LinkToken` | The LINK token contract | | ccipBnM_ | `BurnMintERC677Helper` | The CCIP-BnM token contract | | ccipLnM_ | `BurnMintERC677Helper` | The CCIP-LnM token contract | ### getSupportedTokens Gets a list of token addresses that are supported for cross-chain transfers by the simulator. **Parameters:** | Parameter | Type | Description | | ------------- | -------- | ------------------------------ | | chainSelector | `uint64` | The unique CCIP Chain Selector | **Returns:** | Parameter | Type | Description | | --------- | ------------------ | --------------------------------- | | tokens | `address[] memory` | List of supported token addresses | ### isChainSupported Checks whether the provided chain selector is supported by the simulator. **Parameters:** | Parameter | Type | Description | | ------------- | -------- | ------------------------------ | | chainSelector | `uint64` | The unique CCIP Chain Selector | **Returns:** | Parameter | Type | Description | | --------- | ------ | --------------------------------------- | | supported | `bool` | True if the chain selector is supported | ### requestLinkFromFaucet Requests LINK tokens from the faucet for a specified address. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ---------------------------------- | | to | `address` | The address to receive LINK tokens | | amount | `uint256` | The amount of LINK tokens to send | **Returns:** | Parameter | Type | Description | | --------- | ------ | ----------------------------------- | | success | `bool` | True if the transfer was successful | ### supportNewToken Allows users to add support for new tokens besides CCIP-BnM and CCIP-LnM for cross-chain transfers. **Parameters:** | Parameter | Type | Description | | ------------ | --------- | --------------------------------------------------- | | tokenAddress | `address` | The address of the token to add to supported tokens | --- # Chainlink Local v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1 ## CCIP These contracts provide Cross-Chain Interoperability Protocol (CCIP) functionality in a local testing environment: - [CCIPLocalSimulator](/chainlink-local/api-reference/v0.2.1/ccip-local-simulator) - Local CCIP message routing simulator - [CCIPLocalSimulatorFork](/chainlink-local/api-reference/v0.2.1/ccip-local-simulator-fork) - CCIP simulator for forked networks - [CCIPLocalSimulatorForkJS](/chainlink-local/api-reference/v0.2.1/ccip-local-simulator-fork-js) - JavaScript utilities for CCIP simulation - [MockEvm2EvmOffRamp](/chainlink-local/api-reference/v0.2.1/mock-evm2evm-offramp) - Mock CCIP off-ramp contract - [Register](/chainlink-local/api-reference/v0.2.1/register) - CCIP network configuration registry ## Data Feeds Contracts for simulating Chainlink Data Feeds: - [AggregatorInterface](/chainlink-local/api-reference/v0.2.1/aggregator-interface) - Basic price feed interface - [AggregatorV2V3Interface](/chainlink-local/api-reference/v0.2.1/aggregator-v2-v3-interface) - Combined V2/V3 price feed interface - [AggregatorV3Interface](/chainlink-local/api-reference/v0.2.1/aggregator-v3-interface) - Extended price feed interface - [MockOffchainAggregator](/chainlink-local/api-reference/v0.2.1/mock-offchain-aggregator) - Mock implementation of off-chain aggregator - [MockV3Aggregator](/chainlink-local/api-reference/v0.2.1/mock-v3-aggregator) - Mock implementation of V3 aggregator ## Token Contracts Standard token implementations for testing: - [BurnMintERC677Helper](/chainlink-local/api-reference/v0.2.1/burn-mint-erc677-helper) - Helper contract for ERC677 token operations - [LinkToken](/chainlink-local/api-reference/v0.2.1/link-token) - LINK token implementation - [WETH9](/chainlink-local/api-reference/v0.2.1/weth9) - Wrapped Ether implementation --- # LinkToken v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/link-token ## LinkToken [`LinkToken`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/shared/LinkToken.sol) implements the ERC677 token standard for LINK tokens. This contract provides the basic functionality of the Chainlink token with a fixed total supply. ## Variables ### NAME ```solidity string private constant NAME = "ChainLink Token" ``` The name of the token. ### SYMBOL ```solidity string private constant SYMBOL = "LINK" ``` The symbol of the token. ### TOTAL_SUPPLY ```solidity uint private constant TOTAL_SUPPLY = 10 ** 27 ``` The total supply of LINK tokens (1 billion LINK with 18 decimals). ## Functions ### constructor ```solidity constructor() ERC677(NAME, SYMBOL) ``` Initializes the contract with the token name and symbol, then calls `_onCreate()`. ### _onCreate ```solidity function _onCreate() internal virtual ``` Hook that is called when this contract is created. --- # MockEvm2EvmOffRamp v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/mock-evm2evm-offramp ## MockEvm2EvmOffRamp [`MockEvm2EvmOffRamp`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/ccip/MockEvm2EvmOffRamp.sol) simulates the destination chain component of CCIP's cross-chain message delivery system. This contract handles token releases and message execution in a test environment. ## Errors ### CanOnlySimulatorCall ```solidity error CanOnlySimulatorCall() ``` Thrown when a non-simulator address attempts to call restricted functions. ### ReceiverError ```solidity error ReceiverError(bytes error) ``` Thrown when the CCIP receiver execution fails. ### TokenHandlingError ```solidity error TokenHandlingError(bytes error) ``` Thrown when token release or minting operations encounter non-rate-limiting errors. ### UnsupportedToken ```solidity error UnsupportedToken(IERC20 token) ``` Thrown when attempting to interact with a token that doesn't have an associated pool. ## Structs ### DynamicConfig ```solidity struct DynamicConfig { uint32 permissionLessExecutionThresholdSeconds; address router; address priceRegistry; uint16 maxNumberOfTokensPerMsg; uint32 maxDataBytes; uint32 maxPoolReleaseOrMintGas; } ``` Configuration parameters that can be updated during contract operation: • permissionLessExecutionThresholdSeconds - Waiting time before manual execution is enabled • router - Router address • priceRegistry - Price registry address • maxNumberOfTokensPerMsg - Maximum number of ERC20 token transfers per message • maxDataBytes - Maximum payload data size in bytes • maxPoolReleaseOrMintGas - Maximum gas for token pool releaseOrMint calls ## Variables ### i_sourceChainSelector ```solidity uint64 internal immutable i_sourceChainSelector ``` The chain selector identifying the source chain for this off-ramp. ### s_ccipLocalSimulator ```solidity address internal s_ccipLocalSimulator ``` The address of the local CCIP simulator controlling this off-ramp. ### s_dynamicConfig ```solidity DynamicConfig internal s_dynamicConfig ``` Current dynamic configuration settings for the off-ramp. ### s_poolsBySourceToken ```solidity EnumerableMapAddresses.AddressToAddressMap private s_poolsBySourceToken ``` Maps source token addresses to their corresponding token pool addresses. ## Functions ### constructor ```solidity constructor( address ccipLocalSimulator, DynamicConfig memory dynamicConfig, RateLimiter.Config memory config, uint64 sourceChainSelector, address[] memory sourceTokens, address[] memory pools ) AggregateRateLimiter(config) ``` Initializes the off-ramp with simulator settings, configuration parameters, and token pool mappings. **Parameters:** | Parameter | Type | Description | | ------------------- | -------------------- | ------------------------------------------- | | ccipLocalSimulator | `address` | Address of the CCIP simulator | | dynamicConfig | `DynamicConfig` | Initial dynamic configuration settings | | config | `RateLimiter.Config` | Rate limiter configuration | | sourceChainSelector | `uint64` | Chain selector for the source chain | | sourceTokens | `address[]` | Array of source token addresses | | pools | `address[]` | Array of corresponding token pool addresses | ### executeSingleMessage ```solidity function executeSingleMessage( Internal.EVM2EVMMessage memory message, bytes[] memory offchainTokenData ) external ``` Processes a single cross-chain message by releasing tokens and executing the receiver's callback. **Parameters:** | Parameter | Type | Description | | ----------------- | ------------------------- | ------------------------------------ | | message | `Internal.EVM2EVMMessage` | The cross-chain message to process | | offchainTokenData | `bytes[]` | Token data provided by off-chain DON | ### getPoolBySourceToken ```solidity function getPoolBySourceToken(IERC20 sourceToken) public view returns (IPool) ``` Retrieves the token pool associated with a source token. **Parameters:** | Parameter | Type | Description | | ----------- | -------- | ----------------------------------- | | sourceToken | `IERC20` | The source token address to look up | **Returns:** | Type | Description | | ------- | -------------------------------------------- | | `IPool` | The token pool contract for the source token | ### _releaseOrMintTokens ```solidity function _releaseOrMintTokens( Client.EVMTokenAmount[] memory sourceTokenAmounts, bytes memory originalSender, address receiver, bytes[] memory sourceTokenData, bytes[] memory offchainTokenData ) internal returns (Client.EVMTokenAmount[] memory) ``` Handles the release or minting of tokens through token pools. This function includes safety measures against malicious tokens and rate limiting. **Parameters:** | Parameter | Type | Description | | ------------------ | ------------------------- | --------------------------------------------------------------- | | sourceTokenAmounts | `Client.EVMTokenAmount[]` | List of tokens and amount values to be released/minted | | originalSender | `bytes` | The message sender | | receiver | `address` | The address that will receive the tokens | | sourceTokenData | `bytes[]` | Array of token data returned by token pools on the source chain | | offchainTokenData | `bytes[]` | Array of token data fetched offchain by the DON | **Returns:** | Type | Description | | ------------------------- | ---------------------------------------------- | | `Client.EVMTokenAmount[]` | Array of processed token amounts and addresses | --- # MockOffchainAggregator v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/mock-offchain-aggregator ## MockOffchainAggregator [`MockOffchainAggregator`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/data-feeds/MockOffchainAggregator.sol) simulates a Chainlink Data Feed aggregator for testing purposes. This contract maintains price feed data and allows manual updates of answers and round data. ## Variables ### decimals ```solidity uint8 public decimals ``` The number of decimal places in the answer values. ### getAnswer ```solidity mapping(uint256 => int256) public getAnswer ``` Maps round IDs to their corresponding price answers. ### getTimestamp ```solidity mapping(uint256 => uint256) public getTimestamp ``` Maps round IDs to their update timestamps. ### latestAnswer ```solidity int256 public latestAnswer ``` The most recent price answer. ### latestRound ```solidity uint256 public latestRound ``` The most recent round ID. ### latestTimestamp ```solidity uint256 public latestTimestamp ``` The timestamp of the most recent update. ### maxAnswer ```solidity int192 public maxAnswer ``` The highest answer the system can report. Not exposed from the Proxy contract. ### minAnswer ```solidity int192 public minAnswer ``` The lowest answer the system can report. Not exposed from the Proxy contract. ## Functions ### constructor ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` Initializes the aggregator with decimal precision and an initial answer. **Parameters:** | Parameter | Type | Description | | --------------- | -------- | --------------------------------------- | | _decimals | `uint8` | The number of decimal places in answers | | _initialAnswer | `int256` | The initial price answer | ### getRoundData ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the complete round data for a specific round ID. **Parameters:** | Parameter | Type | Description | | --------- | -------- | --------------------- | | _roundId | `uint80` | The round ID to query | **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The price answer for this round | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### latestRoundData ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the latest round data from the aggregator. **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The latest price answer | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### updateAnswer ```solidity function updateAnswer(int256 _answer) public ``` Updates the latest answer and associated round data. **Parameters:** | Parameter | Type | Description | | --------- | -------- | -------------------- | | _answer | `int256` | The new price answer | ### updateMinAndMaxAnswers ```solidity function updateMinAndMaxAnswers(int192 _minAnswer, int192 _maxAnswer) external ``` Updates the minimum and maximum allowed answer values. **Parameters:** | Parameter | Type | Description | | ----------- | -------- | ------------------------------ | | _minAnswer | `int192` | The new minimum allowed answer | | _maxAnswer | `int192` | The new maximum allowed answer | ### updateRoundData ```solidity function updateRoundData( uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt ) public ``` Updates all data for a specific round. **Parameters:** | Parameter | Type | Description | | ----------- | --------- | ----------------------------- | | _roundId | `uint80` | The round ID to update | | _answer | `int256` | The new price answer | | _timestamp | `uint256` | The new update timestamp | | _startedAt | `uint256` | The new round start timestamp | --- # MockV3Aggregator v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/mock-v3-aggregator ## MockV3Aggregator [`MockV3Aggregator`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/data-feeds/MockV3Aggregator.sol) implements the AggregatorV2V3Interface for testing purposes. This contract acts as a proxy to a MockOffchainAggregator instance and supports aggregator upgrades. ## Variables ### aggregator ```solidity address public aggregator ``` The address of the current MockOffchainAggregator implementation. ### proposedAggregator ```solidity address public proposedAggregator ``` The address of the proposed new aggregator implementation. ### version ```solidity uint256 public constant override version = 0 ``` The version number of this aggregator implementation. ## Functions ### confirmAggregator ```solidity function confirmAggregator(address _aggregator) external ``` Confirms a proposed aggregator upgrade. **Parameters:** | Parameter | Type | Description | | ------------ | --------- | ------------------------------------------ | | _aggregator | `address` | The proposed aggregator address to confirm | ### constructor ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` Initializes the contract by deploying a new MockOffchainAggregator instance. **Parameters:** | Parameter | Type | Description | | --------------- | -------- | --------------------------------------- | | _decimals | `uint8` | The number of decimal places in answers | | _initialAnswer | `int256` | The initial price answer | ### decimals ```solidity function decimals() external view returns (uint8) ``` Retrieves the number of decimal places in the answer values. **Returns:** | Type | Description | | ------- | -------------------------------------- | | `uint8` | The number of decimals in the response | ### description ```solidity function description() external pure returns (string memory) ``` Returns the source file path of this contract. **Returns:** | Type | Description | | -------- | --------------------------------- | | `string` | The description of the price feed | ### getAnswer ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` Retrieves the answer for a specific round. **Parameters:** | Parameter | Type | Description | | --------- | --------- | --------------------- | | roundId | `uint256` | The round ID to query | **Returns:** | Type | Description | | -------- | ---------------------------------------- | | `int256` | The price answer for the specified round | ### getRoundData ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the complete round data for a specific round ID. **Parameters:** | Parameter | Type | Description | | --------- | -------- | --------------------- | | _roundId | `uint80` | The round ID to query | **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The price answer for this round | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### getTimestamp ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` Retrieves the timestamp for a specific round. **Parameters:** | Parameter | Type | Description | | --------- | --------- | --------------------- | | roundId | `uint256` | The round ID to query | **Returns:** | Type | Description | | --------- | ---------------------------------------- | | `uint256` | The timestamp when the round was updated | ### latestAnswer ```solidity function latestAnswer() external view returns (int256) ``` Retrieves the most recent answer. **Returns:** | Type | Description | | -------- | ---------------------------- | | `int256` | The latest price feed answer | ### latestRound ```solidity function latestRound() external view returns (uint256) ``` Retrieves the most recent round ID. **Returns:** | Type | Description | | --------- | ------------------- | | `uint256` | The latest round ID | ### latestRoundData ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` Retrieves the latest round data from the aggregator. **Returns:** | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------- | | roundId | `uint80` | The round ID from the aggregator for this round | | answer | `int256` | The latest price answer | | startedAt | `uint256` | Timestamp when the round started | | updatedAt | `uint256` | Timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### latestTimestamp ```solidity function latestTimestamp() external view returns (uint256) ``` Retrieves the timestamp of the latest answer. **Returns:** | Type | Description | | --------- | ------------------------------------------------ | | `uint256` | The timestamp when the latest answer was updated | ### proposeAggregator ```solidity function proposeAggregator(AggregatorV2V3Interface _aggregator) external ``` Proposes a new aggregator implementation. **Parameters:** | Parameter | Type | Description | | ------------ | ------------------------- | --------------------------------- | | _aggregator | `AggregatorV2V3Interface` | The new aggregator implementation | ### updateAnswer ```solidity function updateAnswer(int256 _answer) public ``` Updates the latest answer and associated round data. **Parameters:** | Parameter | Type | Description | | --------- | -------- | -------------------- | | _answer | `int256` | The new price answer | ### updateRoundData ```solidity function updateRoundData( uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt ) public ``` Updates all data for a specific round. **Parameters:** | Parameter | Type | Description | | ----------- | --------- | ----------------------------- | | _roundId | `uint80` | The round ID to update | | _answer | `int256` | The new price answer | | _timestamp | `uint256` | The new update timestamp | | _startedAt | `uint256` | The new round start timestamp | --- # Register v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/register ## Register [`Register`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/ccip/Register.sol) maintains a registry of CCIP network configurations for different blockchain networks. This contract provides pre-configured settings for various test networks and allows custom network configurations. ## Structs ### NetworkDetails ```solidity struct NetworkDetails { uint64 chainSelector; address routerAddress; address linkAddress; address wrappedNativeAddress; address ccipBnMAddress; address ccipLnMAddress; } ``` Configuration details for a CCIP-enabled network: • chainSelector - Unique identifier for the blockchain network in CCIP • routerAddress - Address of the CCIP Router contract • linkAddress - Address of the LINK token contract • wrappedNativeAddress - Address of the wrapped native token • ccipBnMAddress - Address of the CCIP BnM token • ccipLnMAddress - Address of the CCIP LnM token ## Variables ### s_networkDetails ```solidity mapping(uint256 chainId => NetworkDetails) internal s_networkDetails ``` Stores network configurations indexed by chain ID. Contains pre-configured settings for test networks including Sepolia, Mumbai, Fuji, and others. ## Functions ### constructor ```solidity constructor() public ``` Initializes the contract with pre-configured network details for various test networks including: - Ethereum Sepolia (11155111) - OP Sepolia (11155420) - Polygon Mumbai (80001) - Avalanche Fuji (43113) - BNB Chain Testnet (97) - Arbitrum Sepolia (421614) - Base Sepolia (84532) - Wemix Testnet (1112) - Kroma Sepolia (2358) - Gnosis Chiado (10200) ### getNetworkDetails ```solidity function getNetworkDetails(uint256 chainId) external view returns (NetworkDetails memory) ``` Retrieves the CCIP configuration for a specific blockchain network. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ------------------------------------ | | chainId | `uint256` | The blockchain network ID to look up | **Returns:** | Type | Description | | ---------------- | ----------------------------------------------- | | `NetworkDetails` | The network configuration details for the chain | ### setNetworkDetails ```solidity function setNetworkDetails(uint256 chainId, NetworkDetails memory networkDetails) external ``` Updates or adds CCIP configuration for a blockchain network. **Parameters:** | Parameter | Type | Description | | -------------- | ---------------- | -------------------------------------- | | chainId | `uint256` | The blockchain network ID to configure | | networkDetails | `NetworkDetails` | The network configuration to set | --- # WETH9 v0.2.1 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.1/weth9 ## WETH9 [`WETH9`](https://github.com/smartcontractkit/chainlink-local/blob/ba1f4636e657f161df634379a5057a5a394e2fbb/src/shared/WETH9.sol) implements the Wrapped Ether (WETH) token standard. This contract allows users to wrap ETH into an ERC20-compatible token and unwrap it back to ETH. ## Events ### Approval ```solidity event Approval(address indexed src, address indexed guy, uint256 wad) ``` Emitted when an approval is set. | Parameter | Type | Description | | --------- | --------- | ---------------------------------------- | | src | `address` | The address giving approval (indexed) | | guy | `address` | The address receiving approval (indexed) | | wad | `uint256` | The amount of tokens approved | ### Deposit ```solidity event Deposit(address indexed dst, uint256 wad) ``` Emitted when ETH is wrapped to WETH. | Parameter | Type | Description | | --------- | --------- | ------------------------------------ | | dst | `address` | The address receiving WETH (indexed) | | wad | `uint256` | The amount of ETH wrapped | ### Transfer ```solidity event Transfer(address indexed src, address indexed dst, uint256 wad) ``` Emitted when tokens are transferred. | Parameter | Type | Description | | --------- | --------- | --------------------------------- | | src | `address` | The sender's address (indexed) | | dst | `address` | The recipient's address (indexed) | | wad | `uint256` | The amount of tokens transferred | ### Withdrawal ```solidity event Withdrawal(address indexed src, uint256 wad) ``` Emitted when WETH is unwrapped back to ETH. | Parameter | Type | Description | | --------- | --------- | ------------------------------------- | | src | `address` | The address unwrapping WETH (indexed) | | wad | `uint256` | The amount of WETH unwrapped | ## Variables ### allowance ```solidity mapping(address => mapping(address => uint256)) public allowance ``` Maps owner addresses to spender addresses to approved amounts. ### balanceOf ```solidity mapping(address => uint256) public balanceOf ``` Maps addresses to their token balances. ### decimals ```solidity uint8 public decimals = 18 ``` The number of decimal places used by the token. ### name ```solidity string public name = "Wrapped Ether" ``` The name of the token. ### symbol ```solidity string public symbol = "WETH" ``` The symbol of the token. ## Functions ### _deposit ```solidity function _deposit() internal ``` Internal function to handle ETH deposits and mint corresponding WETH tokens. ### approve ```solidity function approve(address guy, uint256 wad) public returns (bool) ``` Approves another address to spend tokens. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ------------------------------- | | guy | `address` | The address to approve | | wad | `uint256` | The amount of tokens to approve | **Returns:** | Type | Description | | ------ | ------------------- | | `bool` | Always returns true | ### deposit ```solidity function deposit() external payable ``` Deposits ETH and mints WETH tokens. ### receive ```solidity receive() external payable ``` Fallback function to handle direct ETH transfers. Calls `_deposit()` to wrap received ETH into WETH. ### totalSupply ```solidity function totalSupply() public view returns (uint256) ``` Returns the total supply of WETH tokens. **Returns:** | Type | Description | | --------- | ---------------------------------------- | | `uint256` | The total amount of WETH in the contract | ### transfer ```solidity function transfer(address dst, uint256 wad) public returns (bool) ``` Transfers tokens to another address. **Parameters:** | Parameter | Type | Description | | --------- | --------- | -------------------------------- | | dst | `address` | The recipient's address | | wad | `uint256` | The amount of tokens to transfer | **Returns:** | Type | Description | | ------ | ------------------------------ | | `bool` | True if the transfer succeeded | ### transferFrom ```solidity function transferFrom(address src, address dst, uint256 wad) public returns (bool) ``` Transfers tokens from one address to another. **Parameters:** | Parameter | Type | Description | | --------- | --------- | -------------------------------- | | src | `address` | The source address | | dst | `address` | The destination address | | wad | `uint256` | The amount of tokens to transfer | **Returns:** | Type | Description | | ------ | ------------------------------ | | `bool` | True if the transfer succeeded | ### withdraw ```solidity function withdraw(uint256 wad) external ``` Withdraws ETH by burning WETH tokens. **Parameters:** | Parameter | Type | Description | | --------- | --------- | ---------------------- | | wad | `uint256` | The amount to withdraw | --- # AggregatorInterface v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/aggregator-interface ## AggregatorInterface An interface that defines the standard methods for accessing price feed data from an aggregator contract. [`AggregatorInterface`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/data-feeds/interfaces/AggregatorInterface.sol) ## Events ### AnswerUpdated ```solidity event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | --------------------------------------------- | | current | int256 | The updated answer | | roundId | uint256 | The round ID for which the answer was updated | | updatedAt | uint256 | The timestamp when the answer was updated | ### NewRound ```solidity event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------- | | roundId | uint256 | The round ID of the new round | | startedBy | address | The address of the account that started the round | | startedAt | uint256 | The timestamp when the round was started | ## Functions ### getAnswer Retrieves the price answer for a specific round. ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ---------------------------------- | | roundId | uint256 | The round ID to get the answer for | #### Returns | Parameter | Type | Description | | --------- | ------ | --------------------------------- | | (unnamed) | int256 | The answer for the given round ID | ### getTimestamp Retrieves the timestamp for a specific round. ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------- | | roundId | uint256 | The round ID to get the timestamp for | #### Returns | Parameter | Type | Description | | --------- | ------- | ------------------------------------ | | (unnamed) | uint256 | The timestamp for the given round ID | ### latestAnswer Retrieves the most recent price answer. ```solidity function latestAnswer() external view returns (int256) ``` #### Returns | Parameter | Type | Description | | --------- | ------ | ----------------- | | (unnamed) | int256 | The latest answer | ### latestRound Retrieves the most recent round ID. ```solidity function latestRound() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ------------------- | | (unnamed) | uint256 | The latest round ID | ### latestTimestamp Retrieves the timestamp of the most recent answer. ```solidity function latestTimestamp() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ---------------------------------- | | (unnamed) | uint256 | The timestamp of the latest answer | --- # AggregatorV2V3Interface v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/aggregator-v2-v3-interface ## AggregatorV2V3Interface A combined interface that inherits functionality from both AggregatorInterface and AggregatorV3Interface, providing a complete set of methods for accessing price feed data. [`AggregatorV2V3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/data-feeds/interfaces/AggregatorV2V3Interface.sol) ## Inheritance This interface inherits from: - [`AggregatorInterface`](/chainlink-local/api-reference/v0.2.2/aggregator-interface) - Provides basic price feed functionality - [`AggregatorV3Interface`](/chainlink-local/api-reference/v0.2.2/aggregator-v3-interface) - Provides extended price feed functionality --- # AggregatorV3Interface v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/aggregator-v3-interface ## AggregatorV3Interface An interface for accessing detailed price feed data and metadata from an aggregator contract, providing enhanced functionality for retrieving round data and contract information. [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/data-feeds/interfaces/AggregatorV3Interface.sol) ## Functions ### decimals Retrieves the number of decimal places used by the aggregator. ```solidity function decimals() external view returns (uint8) ``` #### Returns | Parameter | Type | Description | | --------- | ----- | ---------------------- | | (unnamed) | uint8 | The number of decimals | ### description Retrieves the description of the aggregator. ```solidity function description() external view returns (string memory) ``` #### Returns | Parameter | Type | Description | | --------- | ------ | --------------------------------- | | (unnamed) | string | The description of the aggregator | ### getRoundData Retrieves the complete round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------- | | _roundId | uint80 | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | ------- | --------------------------------------------- | | roundId | uint80 | The round ID | | answer | int256 | The answer for the round | | startedAt | uint256 | The timestamp when the round started | | updatedAt | uint256 | The timestamp when the round was updated | | answeredInRound | uint80 | The round ID in which the answer was computed | ### latestRoundData Retrieves the complete round data for the most recent round. ```solidity function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Returns | Parameter | Type | Description | | --------------- | ------- | ---------------------------------------------------- | | roundId | uint80 | The latest round ID | | answer | int256 | The latest answer | | startedAt | uint256 | The timestamp when the latest round started | | updatedAt | uint256 | The timestamp when the latest round was updated | | answeredInRound | uint80 | The round ID in which the latest answer was computed | ### version Retrieves the version number of the aggregator. ```solidity function version() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ----------------------------- | | (unnamed) | uint256 | The version of the aggregator | --- # BurnMintERC677Helper v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/burn-mint-erc677-helper ## BurnMintERC677Helper A helper contract that extends the BurnMintERC677 token contract with additional minting functionality for testing and development purposes. [`BurnMintERC677Helper`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/ccip/BurnMintERC677Helper.sol) ## Functions ### constructor Creates a new BurnMintERC677Helper token with the specified name and symbol. ```solidity constructor( string memory name, string memory symbol ) BurnMintERC677(name, symbol, 18, 0) ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | ----------------------- | | name | string | The name of the token | | symbol | string | The symbol of the token | ### drip Mints a single token to the specified address for testing purposes. ```solidity function drip(address to) external ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | --------------------------------------- | | to | address | The address to receive the minted token | --- # CCIPLocalSimulatorFork v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/ccip-local-simulator-fork-js ## CCIPLocalSimulatorFork A JavaScript module that provides utilities for simulating CCIP (Cross-Chain Interoperability Protocol) message routing in a local or forked environment. [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/scripts/CCIPLocalSimulatorFork.js) ## Types ### Evm2EvmMessage Represents a cross-chain message in the EVM-to-EVM communication protocol. Contains all necessary information for message routing and token transfers across chains. | Property | Type | Description | | ------------------- | ---------------------------------------- | ---------------------------------------------- | | sourceChainSelector | `bigint` | The identifier of the source chain | | sender | `string` | The address that sent the message | | receiver | `string` | The address that will receive the message | | sequenceNumber | `bigint` | The sequence number of the message | | gasLimit | `bigint` | The gas limit for executing the message | | strict | `boolean` | Whether the message requires strict execution | | nonce | `bigint` | The nonce of the message | | feeToken | `string` | The token used to pay fees | | feeTokenAmount | `bigint` | The amount of fee token to be paid | | data | `string` | The message payload data | | tokenAmounts | `Array<{token: string, amount: bigint}>` | Array of tokens and amounts being transferred | | sourceTokenData | `Array` | Array of token-specific data from source chain | | messageId | `string` | The unique identifier of the message | ## Functions ### getEvm2EvmMessage Extracts and parses a CCIP message from a transaction receipt by looking for the CCIPSendRequested event. ```javascript function getEvm2EvmMessage(receipt) => Evm2EvmMessage | null ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------ | | receipt | `object` | The transaction receipt from the `ccipSend` call | #### Returns | Type | Description | | ---------------------- | ---------------------------------------------------------------------------------------------- | | `Evm2EvmMessage\|null` | The parsed EVM-to-EVM message if found in the receipt logs, or null if no relevant event found | ### requestLinkFromTheFaucet Requests LINK tokens from a faucet contract for testing purposes. ```javascript async function requestLinkFromTheFaucet(linkAddress, to, amount) => Promise ``` #### Parameters | Parameter | Type | Description | | ----------- | -------- | ------------------------------------------------------- | | linkAddress | `string` | The address of the LINK contract on the current network | | to | `string` | The address to send LINK to | | amount | `bigint` | The amount of LINK to request | #### Returns | Type | Description | | ----------------- | -------------------------------------------------------------- | | `Promise` | Promise resolving to the transaction hash of the fund transfer | ### routeMessage Routes a cross-chain message on the destination network by finding the appropriate off-ramp and executing the message. ```javascript async function routeMessage(routerAddress, evm2EvmMessage) => Promise ``` #### Parameters | Parameter | Type | Description | | -------------- | ---------------- | --------------------------------- | | routerAddress | `string` | Address of the destination Router | | evm2EvmMessage | `Evm2EvmMessage` | Sent cross-chain message | #### Returns | Type | Description | | --------------- | ------------------------------------------------------------ | | `Promise` | Resolves with no value if the message is successfully routed | --- # CCIPLocalSimulatorFork v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/ccip-local-simulator-fork ## CCIPLocalSimulatorFork A contract that simulates CCIP (Cross-Chain Interoperability Protocol) message routing in forked network environments, specifically designed to work with Foundry for testing cross-chain applications. [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/ccip/CCIPLocalSimulatorFork.sol) ## Events ### CCIPSendRequested ```solidity event CCIPSendRequested(Internal.EVM2EVMMessage message) ``` #### Parameters | Parameter | Type | Description | | --------- | ----------------------- | --------------------------------- | | message | Internal.EVM2EVMMessage | The EVM2EVM message that was sent | ## Interfaces ### IEVM2EVMOffRampFork Interface for executing CCIP messages on the off-ramp. ```solidity interface IEVM2EVMOffRampFork { function executeSingleMessage( Internal.EVM2EVMMessage memory message, bytes[] memory offchainTokenData, uint32[] memory tokenGasOverrides ) external; } ``` ### IRouterFork Interface for accessing off-ramp configurations. ```solidity interface IRouterFork { struct OffRamp { uint64 sourceChainSelector; address offRamp; } function getOffRamps() external view returns (OffRamp[] memory); } ``` ## Structs ### OffRamp Configuration for a CCIP off-ramp. ```solidity struct OffRamp { uint64 sourceChainSelector; address offRamp; } ``` #### Fields | Field | Type | Description | | ------------------- | ------- | --------------------------------------- | | sourceChainSelector | uint64 | The chain selector for the source chain | | offRamp | address | The address of the offRamp contract | ## Variables ### i_register ```solidity Register immutable i_register ``` ### LINK_FAUCET ```solidity address constant LINK_FAUCET = 0x4281eCF07378Ee595C564a59048801330f3084eE ``` ### s_processedMessages ```solidity mapping(bytes32 messageId => bool isProcessed) internal s_processedMessages ``` ## Functions ### constructor Sets up the simulator environment by creating a persistent register instance and enabling event recording. ```solidity constructor() ``` ### getNetworkDetails Fetches the network configuration for a specified blockchain network ID. ```solidity function getNetworkDetails(uint256 chainId) external view returns (Register.NetworkDetails memory) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | --------------------------------------------------------------------------------------------------- | | chainId | uint256 | The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. Not CCIP chain selector | #### Returns | Parameter | Type | Description | | -------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- | | networkDetails | Register.NetworkDetails | The tuple containing: chainSelector, routerAddress, linkAddress, wrappedNativeAddress, ccipBnMAddress, ccipLnMAddress | ### requestLinkFromFaucet Transfers LINK tokens from the faucet to a specified recipient address. ```solidity function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ----------------------------------------------- | | to | address | The address to which LINK tokens are to be sent | | amount | uint256 | The amount of LINK tokens to send | #### Returns | Parameter | Type | Description | | --------- | ---- | ---------------------------------------------------------------------- | | success | bool | Returns true if the transfer of tokens was successful, otherwise false | ### setNetworkDetails Registers or updates the network configuration for a specific blockchain. ```solidity function setNetworkDetails(uint256 chainId, Register.NetworkDetails memory networkDetails) external ``` #### Parameters | Parameter | Type | Description | | -------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- | | chainId | uint256 | The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. Not CCIP chain selector | | networkDetails | Register.NetworkDetails | The tuple containing: chainSelector, routerAddress, linkAddress, wrappedNativeAddress, ccipBnMAddress, ccipLnMAddress | ### switchChainAndRouteMessage Routes a cross-chain message by finding it in the event logs, switching to the destination chain, and executing it through the appropriate off-ramp. ```solidity function switchChainAndRouteMessage(uint256 forkId) external ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------------------------------------------------------------------ | | forkId | uint256 | The ID of the destination network fork. This is the returned value of `createFork()` or `createSelectFork()` | --- # CCIPLocalSimulator v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/ccip-local-simulator ## CCIPLocalSimulator A contract that simulates local CCIP (Cross-Chain Interoperability Protocol) operations for testing and development purposes. [`CCIPLocalSimulator`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/ccip/CCIPLocalSimulator.sol) ## Errors ### CCIPLocalSimulator__MsgSenderIsNotTokenOwner ```solidity error CCIPLocalSimulator__MsgSenderIsNotTokenOwner() ``` Thrown when a caller attempts to add a token but is not the token owner or CCIP admin. ## Variables ### CHAIN_SELECTOR ```solidity uint64 constant CHAIN_SELECTOR = 16015286601757825753 ``` ### i_ccipBnM ```solidity BurnMintERC677Helper internal immutable i_ccipBnM ``` ### i_ccipLnM ```solidity BurnMintERC677Helper internal immutable i_ccipLnM ``` ### i_linkToken ```solidity LinkToken internal immutable i_linkToken ``` ### i_mockRouter ```solidity MockCCIPRouter internal immutable i_mockRouter ``` ### i_wrappedNative ```solidity WETH9 internal immutable i_wrappedNative ``` ### s_supportedTokens ```solidity address[] internal s_supportedTokens ``` ## Functions ### constructor Initializes the contract by deploying and configuring all required token instances. ```solidity constructor() ``` ### configuration Retrieves the complete configuration for local CCIP simulations. ```solidity function configuration() public view returns ( uint64 chainSelector_, IRouterClient sourceRouter_, IRouterClient destinationRouter_, WETH9 wrappedNative_, LinkToken linkToken_, BurnMintERC677Helper ccipBnM_, BurnMintERC677Helper ccipLnM_ ) ``` #### Returns | Parameter | Type | Description | | ------------------- | -------------------- | -------------------------------------------------------- | | chainSelector_ | uint64 | The unique CCIP Chain Selector | | sourceRouter_ | IRouterClient | The source chain Router contract | | destinationRouter_ | IRouterClient | The destination chain Router contract | | wrappedNative_ | WETH9 | The wrapped native token which can be used for CCIP fees | | linkToken_ | LinkToken | The LINK token | | ccipBnM_ | BurnMintERC677Helper | The ccipBnM token | | ccipLnM_ | BurnMintERC677Helper | The ccipLnM token | ### getSupportedTokens Retrieves the list of tokens supported for cross-chain transfers. ```solidity function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens) ``` #### Parameters | Parameter | Type | Description | | ------------- | ------ | ------------------------------ | | chainSelector | uint64 | The unique CCIP Chain Selector | #### Returns | Parameter | Type | Description | | --------- | ---------- | ----------------------------------------------------------------------------------------------- | | tokens | address[] | Returns a list of token addresses that are supported for cross-chain transfers by the simulator | ### isChainSupported Verifies if a given chain selector is supported by the simulator. ```solidity function isChainSupported(uint64 chainSelector) public pure returns (bool supported) ``` #### Parameters | Parameter | Type | Description | | ------------- | ------ | ------------------------------ | | chainSelector | uint64 | The unique CCIP Chain Selector | #### Returns | Parameter | Type | Description | | --------- | ---- | ------------------------------------------------------------- | | supported | bool | Returns true if `chainSelector` is supported by the simulator | ### requestLinkFromFaucet Transfers LINK tokens from the faucet to a specified recipient address. ```solidity function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ----------------------------------------------- | | to | address | The address to which LINK tokens are to be sent | | amount | uint256 | The amount of LINK tokens to send | #### Returns | Parameter | Type | Description | | --------- | ---- | ---------------------------------------------------------------------- | | success | bool | Returns true if the transfer of tokens was successful, otherwise false | ### supportNewTokenViaGetCCIPAdmin Adds support for a new token using CCIP admin verification. ```solidity function supportNewTokenViaGetCCIPAdmin(address tokenAddress) external ``` #### Parameters | Parameter | Type | Description | | ------------ | ------- | --------------------------------------------------------------- | | tokenAddress | address | The address of the token to add to the list of supported tokens | ### supportNewTokenViaOwner Adds support for a new token using owner verification. ```solidity function supportNewTokenViaOwner(address tokenAddress) external ``` #### Parameters | Parameter | Type | Description | | ------------ | ------- | --------------------------------------------------------------- | | tokenAddress | address | The address of the token to add to the list of supported tokens | --- # Chainlink Local v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2 ## CCIP These contracts provide Cross-Chain Interoperability Protocol (CCIP) functionality in a local testing environment: - [CCIPLocalSimulator](/chainlink-local/api-reference/v0.2.2/ccip-local-simulator) - Local CCIP message routing simulator - [CCIPLocalSimulatorFork](/chainlink-local/api-reference/v0.2.2/ccip-local-simulator-fork) - CCIP simulator for forked networks - [CCIPLocalSimulatorFork JS](/chainlink-local/api-reference/v0.2.2/ccip-local-simulator-fork-js) - JavaScript utilities for CCIP simulation - [Register](/chainlink-local/api-reference/v0.2.2/register) - CCIP network configuration registry ## Data Feeds Contracts for simulating Chainlink Data Feeds: - [AggregatorInterface](/chainlink-local/api-reference/v0.2.2/aggregator-interface) - Basic price feed interface - [AggregatorV2V3Interface](/chainlink-local/api-reference/v0.2.2/aggregator-v2-v3-interface) - Combined V2/V3 price feed interface - [AggregatorV3Interface](/chainlink-local/api-reference/v0.2.2/aggregator-v3-interface) - Extended price feed interface - [MockOffchainAggregator](/chainlink-local/api-reference/v0.2.2/mock-offchain-aggregator) - Mock implementation of off-chain aggregator - [MockV3Aggregator](/chainlink-local/api-reference/v0.2.2/mock-v3-aggregator) - Mock implementation of V3 aggregator ## Token Contracts Standard token implementations for testing: - [BurnMintERC677Helper](/chainlink-local/api-reference/v0.2.2/burn-mint-erc677-helper) - Helper contract for ERC677 token operations - [LinkToken](/chainlink-local/api-reference/v0.2.2/link-token) - LINK token implementation - [WETH9](/chainlink-local/api-reference/v0.2.2/weth9) - Wrapped Ether implementation --- # LinkToken v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/link-token ## LinkToken A contract that implements the ChainLink Token (LINK) using the ERC677 standard with a fixed total supply and standard token details. [`LinkToken`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/shared/LinkToken.sol) ## Functions ### constructor Initializes a new LINK token with standard token details. ```solidity constructor() ``` ### _onCreate Internal hook that handles the initial token supply minting. ```solidity function _onCreate() internal virtual ``` --- # MockOffchainAggregator v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/mock-offchain-aggregator ## MockOffchainAggregator A mock implementation of an offchain aggregator contract used for testing purposes, simulating price feed behavior with configurable parameters. [`MockOffchainAggregator`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/data-feeds/MockOffchainAggregator.sol) ## Variables ### decimals ```solidity uint8 public decimals ``` ### getAnswer ```solidity mapping(uint256 => int256) public getAnswer ``` ### getTimestamp ```solidity mapping(uint256 => uint256) public getTimestamp ``` ### latestAnswer ```solidity int256 public latestAnswer ``` ### latestRound ```solidity uint256 public latestRound ``` ### latestTimestamp ```solidity uint256 public latestTimestamp ``` ### maxAnswer ```solidity int192 public maxAnswer ``` ### minAnswer ```solidity int192 public minAnswer ``` ## Functions ### constructor Initializes a new mock aggregator with the specified decimal precision and starting price. ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` #### Parameters | Parameter | Type | Description | | --------------- | ------ | --------------------------------------------------- | | _decimals | uint8 | The number of decimals for the aggregator | | _initialAnswer | int256 | The initial answer to be set in the mock aggregator | ### getRoundData Retrieves the complete round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------- | | _roundId | uint80 | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | ------- | --------------------------------------------- | | roundId | uint80 | The round ID | | answer | int256 | The answer for the round | | startedAt | uint256 | The timestamp when the round started | | updatedAt | uint256 | The timestamp when the round was updated | | answeredInRound | uint80 | The round ID in which the answer was computed | ### latestRoundData Retrieves the complete round data for the most recent round. ```solidity function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Returns | Parameter | Type | Description | | --------------- | ------- | ---------------------------------------------------- | | roundId | uint80 | The latest round ID | | answer | int256 | The latest answer | | startedAt | uint256 | The timestamp when the latest round started | | updatedAt | uint256 | The timestamp when the latest round was updated | | answeredInRound | uint80 | The round ID in which the latest answer was computed | ### updateAnswer Updates the latest answer and associated data. ```solidity function updateAnswer(int256 _answer) public ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------ | | _answer | int256 | The new answer to be set | ### updateMinAndMaxAnswers Updates the minimum and maximum allowed answers for the aggregator. ```solidity function updateMinAndMaxAnswers(int192 _minAnswer, int192 _maxAnswer) external ``` #### Parameters | Parameter | Type | Description | | ----------- | ------ | ---------------------- | | _minAnswer | int192 | The new minimum answer | | _maxAnswer | int192 | The new maximum answer | ### updateRoundData Updates all data for a specific round. ```solidity function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public ``` #### Parameters | Parameter | Type | Description | | ----------- | ------- | ------------------------------------ | | _roundId | uint80 | The round ID to be updated | | _answer | int256 | The new answer to be set | | _timestamp | uint256 | The timestamp to be set | | _startedAt | uint256 | The timestamp when the round started | --- # MockV3Aggregator v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/mock-v3-aggregator ## MockV3Aggregator A mock implementation of the AggregatorV2V3Interface for testing purposes, providing a simulated price feed through interaction with a MockOffchainAggregator. [`MockV3Aggregator`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/data-feeds/MockV3Aggregator.sol) ## Inheritance This contract inherits from: - [`AggregatorV2V3Interface`](/chainlink-local/api-reference/v0.2.2/aggregator-v2-v3-interface) - Combined interface that includes both AggregatorInterface and AggregatorV3Interface functionality ## Variables ### aggregator ```solidity address public aggregator ``` ### proposedAggregator ```solidity address public proposedAggregator ``` ### version ```solidity uint256 public constant version ``` ## Functions ### confirmAggregator Confirms and sets a previously proposed aggregator as the current one. ```solidity function confirmAggregator(address _aggregator) external ``` #### Parameters | Parameter | Type | Description | | ------------ | ------- | -------------------------------------- | | _aggregator | address | The address of the proposed aggregator | ### constructor Initializes the mock aggregator with specified decimals and initial answer. ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` #### Parameters | Parameter | Type | Description | | --------------- | ------ | --------------------------------------------------- | | _decimals | uint8 | The number of decimals for the aggregator | | _initialAnswer | int256 | The initial answer to be set in the mock aggregator | ### decimals Retrieves the number of decimal places used by the aggregator. ```solidity function decimals() external view returns (uint8) ``` #### Returns | Parameter | Type | Description | | --------- | ----- | ---------------------- | | (unnamed) | uint8 | The number of decimals | ### description Returns the description of the aggregator. ```solidity function description() external pure returns (string memory) ``` #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------------- | | (unnamed) | string | The source file path of the mock aggregator | ### getAnswer Retrieves the answer for a specific round. ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ---------------------------------- | | roundId | uint256 | The round ID to get the answer for | #### Returns | Parameter | Type | Description | | --------- | ------ | --------------------------------- | | (unnamed) | int256 | The answer for the given round ID | ### getRoundData Retrieves the complete round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------- | | _roundId | uint80 | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | ------- | --------------------------------------------- | | roundId | uint80 | The round ID | | answer | int256 | The answer for the round | | startedAt | uint256 | The timestamp when the round started | | updatedAt | uint256 | The timestamp when the round was updated | | answeredInRound | uint80 | The round ID in which the answer was computed | ### getTimestamp Retrieves the timestamp for a specific round. ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------- | | roundId | uint256 | The round ID to get the timestamp for | #### Returns | Parameter | Type | Description | | --------- | ------- | ------------------------------------ | | (unnamed) | uint256 | The timestamp for the given round ID | ### latestAnswer Retrieves the most recent answer. ```solidity function latestAnswer() external view returns (int256) ``` #### Returns | Parameter | Type | Description | | --------- | ------ | ----------------- | | (unnamed) | int256 | The latest answer | ### latestRound Retrieves the most recent round ID. ```solidity function latestRound() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ------------------- | | (unnamed) | uint256 | The latest round ID | ### latestRoundData Retrieves the complete round data for the most recent round. ```solidity function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) ``` #### Returns | Parameter | Type | Description | | --------------- | ------- | ---------------------------------------------------- | | roundId | uint80 | The latest round ID | | answer | int256 | The latest answer | | startedAt | uint256 | The timestamp when the latest round started | | updatedAt | uint256 | The timestamp when the latest round was updated | | answeredInRound | uint80 | The round ID in which the latest answer was computed | ### latestTimestamp Retrieves the timestamp of the most recent answer. ```solidity function latestTimestamp() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ---------------------------------- | | (unnamed) | uint256 | The timestamp of the latest answer | ### proposeAggregator Proposes a new aggregator for future use. ```solidity function proposeAggregator(AggregatorV2V3Interface _aggregator) external ``` #### Parameters | Parameter | Type | Description | | ------------ | ----------------------- | -------------------------------------- | | _aggregator | AggregatorV2V3Interface | The address of the proposed aggregator | ### updateAnswer Updates the latest answer in the underlying mock aggregator. ```solidity function updateAnswer(int256 _answer) public ``` #### Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------ | | _answer | int256 | The new answer to be set | ### updateRoundData Updates all data for a specific round in the underlying mock aggregator. ```solidity function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public ``` #### Parameters | Parameter | Type | Description | | ----------- | ------- | ------------------------------------ | | _roundId | uint80 | The round ID to be updated | | _answer | int256 | The new answer to be set | | _timestamp | uint256 | The timestamp to be set | | _startedAt | uint256 | The timestamp when the round started | --- # Register v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/register ## Register A contract that manages network configuration details for various blockchain networks, storing and retrieving network-specific parameters. [`Register`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/ccip/Register.sol) ## Structs ### NetworkDetails Configuration details for a CCIP network. ```solidity struct NetworkDetails { uint64 chainSelector; address routerAddress; address linkAddress; address wrappedNativeAddress; address ccipBnMAddress; address ccipLnMAddress; address rmnProxyAddress; address registryModuleOwnerCustomAddress; address tokenAdminRegistryAddress; } ``` #### Fields | Field | Type | Description | | -------------------------------- | ------- | -------------------------------------------------------- | | chainSelector | uint64 | The unique identifier for the chain | | routerAddress | address | The address of the CCIP router contract | | linkAddress | address | The address of the LINK token | | wrappedNativeAddress | address | The address of the wrapped native token | | ccipBnMAddress | address | The address of the CCIP BnM token | | ccipLnMAddress | address | The address of the CCIP LnM token | | rmnProxyAddress | address | The address of the RMN proxy | | registryModuleOwnerCustomAddress | address | The address of the registry module owner custom contract | | tokenAdminRegistryAddress | address | The address of the token admin registry | ## Variables ### s_networkDetails ```solidity mapping(uint256 chainId => NetworkDetails) internal s_networkDetails ``` ## Functions ### constructor Initializes the contract with predefined network configurations for various test networks. ```solidity constructor() ``` ### getNetworkDetails Fetches the network configuration for a specified chain ID. ```solidity function getNetworkDetails(uint256 chainId) external view returns (NetworkDetails memory networkDetails) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------ | | chainId | uint256 | The ID of the chain to get the details for | #### Returns | Parameter | Type | Description | | -------------- | -------------- | ---------------------------------------------- | | networkDetails | NetworkDetails | The network details for the specified chain ID | ### setNetworkDetails Updates or adds network configuration details for a specific chain ID. ```solidity function setNetworkDetails(uint256 chainId, NetworkDetails memory networkDetails) external ``` #### Parameters | Parameter | Type | Description | | -------------- | -------------- | ----------------------------------------------------- | | chainId | uint256 | The ID of the chain to set the details for | | networkDetails | NetworkDetails | The network details to set for the specified chain ID | --- # WETH9 v0.2.2 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.2/weth9 ## WETH9 A contract that implements Wrapped Ether (WETH), allowing users to wrap and unwrap ETH to use it as an ERC20-compatible token. [`WETH9`](https://github.com/smartcontractkit/chainlink-local/blob/cd3bfb8c42716cfb791174314eba2c0d178551b9/src/shared/WETH9.sol) ## Variables ### allowance ```solidity mapping(address => mapping(address => uint256)) public allowance ``` ### balanceOf ```solidity mapping(address => uint256) public balanceOf ``` ### decimals ```solidity uint8 public decimals ``` ### name ```solidity string public name ``` ### symbol ```solidity string public symbol ``` ## Events ### Approval ```solidity event Approval(address indexed src, address indexed guy, uint256 wad) ``` Emitted when an approval is set. #### Parameters | Parameter | Type | Description | | --------- | ------- | ----------------------------- | | src | address | The owner of the tokens | | guy | address | The approved spender | | wad | uint256 | The amount of tokens approved | ### Deposit ```solidity event Deposit(address indexed dst, uint256 wad) ``` Emitted when ETH is wrapped to WETH. #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------- | | dst | address | The recipient of the WETH | | wad | uint256 | The amount of ETH wrapped | ### Transfer ```solidity event Transfer(address indexed src, address indexed dst, uint256 wad) ``` Emitted when tokens are transferred. #### Parameters | Parameter | Type | Description | | --------- | ------- | -------------------------------- | | src | address | The sender of the tokens | | dst | address | The recipient of the tokens | | wad | uint256 | The amount of tokens transferred | ### Withdrawal ```solidity event Withdrawal(address indexed src, uint256 wad) ``` Emitted when WETH is unwrapped back to ETH. #### Parameters | Parameter | Type | Description | | --------- | ------- | ---------------------------- | | src | address | The address unwrapping WETH | | wad | uint256 | The amount of WETH unwrapped | ## Functions ### approve Approves a spender to transfer tokens on behalf of the owner. ```solidity function approve(address guy, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------- | | guy | address | The address to approve | | wad | uint256 | The amount of tokens to approve | #### Returns | Parameter | Type | Description | | --------- | ---- | ------------------- | | (unnamed) | bool | Always returns true | ### deposit Wraps ETH to WETH by sending ETH to the contract. ```solidity function deposit() external payable ``` ### totalSupply Returns the total amount of WETH in circulation. ```solidity function totalSupply() public view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------- | | (unnamed) | uint256 | The total supply of WETH (contract's ETH balance) | ### transfer Transfers tokens to a specified address. ```solidity function transfer(address dst, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | -------------------------------- | | dst | address | The recipient address | | wad | uint256 | The amount of tokens to transfer | #### Returns | Parameter | Type | Description | | --------- | ---- | ------------------- | | (unnamed) | bool | Always returns true | ### transferFrom Transfers tokens from one address to another. ```solidity function transferFrom(address src, address dst, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | -------------------------------- | | src | address | The source address | | dst | address | The destination address | | wad | uint256 | The amount of tokens to transfer | #### Returns | Parameter | Type | Description | | --------- | ---- | ------------------- | | (unnamed) | bool | Always returns true | ### withdraw Unwraps WETH back to ETH. ```solidity function withdraw(uint256 wad) external ``` #### Parameters | Parameter | Type | Description | | --------- | ------- | ---------------------------- | | wad | uint256 | The amount of WETH to unwrap | --- # AggregatorInterface v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/aggregator-interface ## AggregatorInterface Interface for accessing price feed data from an aggregator contract. [`AggregatorInterface`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/data-feeds/interfaces/AggregatorInterface.sol) ## Events ### AnswerUpdated ```solidity event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | --------------------------------------------- | | current | `int256` | The updated answer | | roundId | `uint256` | The round ID for which the answer was updated | | updatedAt | `uint256` | The timestamp when the answer was updated | ### NewRound ```solidity event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------------------------- | | roundId | `uint256` | The round ID of the new round | | startedBy | `address` | The address of the account that started the round | | startedAt | `uint256` | The timestamp when the round was started | ## Functions ### getAnswer Gets the answer for a specific round ID. ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ---------------------------------- | | roundId | `uint256` | The round ID to get the answer for | #### Returns | Parameter | Type | Description | | --------- | -------- | --------------------------------- | | (unnamed) | `int256` | The answer for the given round ID | ### getTimestamp Gets the timestamp for a specific round ID. ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------------- | | roundId | `uint256` | The round ID to get the timestamp for | #### Returns | Parameter | Type | Description | | --------- | --------- | ------------------------------------ | | (unnamed) | `uint256` | The timestamp for the given round ID | ### latestAnswer Gets the latest answer from the aggregator. ```solidity function latestAnswer() external view returns (int256) ``` #### Returns | Parameter | Type | Description | | --------- | -------- | ----------------- | | (unnamed) | `int256` | The latest answer | ### latestRound Gets the latest round ID from the aggregator. ```solidity function latestRound() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ------------------- | | (unnamed) | `uint256` | The latest round ID | ### latestTimestamp Gets the timestamp of the latest answer from the aggregator. ```solidity function latestTimestamp() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ---------------------------------- | | (unnamed) | `uint256` | The timestamp of the latest answer | --- # AggregatorV2V3Interface v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/aggregator-v2-v3-interface ## AggregatorV2V3Interface Interface that combines functionality from both AggregatorInterface and AggregatorV3Interface. [`AggregatorV2V3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/data-feeds/interfaces/AggregatorV2V3Interface.sol) --- # AggregatorV3Interface v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/aggregator-v3-interface ## AggregatorV3Interface Interface for accessing detailed data from an aggregator contract, including round data and metadata. [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/data-feeds/interfaces/AggregatorV3Interface.sol) ## Functions ### decimals Gets the number of decimals used by the aggregator. ```solidity function decimals() external view returns (uint8) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ---------------------- | | (unnamed) | `uint8` | The number of decimals | ### description Gets the description of the aggregator. ```solidity function description() external view returns (string memory) ``` #### Returns | Parameter | Type | Description | | --------- | -------- | --------------------------------- | | (unnamed) | `string` | The description of the aggregator | ### getRoundData Gets the round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | -------------------------------- | | _roundId | `uint80` | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | --------- | --------------------------------------------- | | roundId | `uint80` | The round ID | | answer | `int256` | The answer for the round | | startedAt | `uint256` | The timestamp when the round started | | updatedAt | `uint256` | The timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### latestRoundData Gets the latest round data. ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Returns | Parameter | Type | Description | | --------------- | --------- | ---------------------------------------------------- | | roundId | `uint80` | The latest round ID | | answer | `int256` | The latest answer | | startedAt | `uint256` | The timestamp when the latest round started | | updatedAt | `uint256` | The timestamp when the latest round was updated | | answeredInRound | `uint80` | The round ID in which the latest answer was computed | ### version Gets the version of the aggregator. ```solidity function version() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ----------------------------- | | (unnamed) | `uint256` | The version of the aggregator | --- # BurnMintERC677Helper v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/burn-mint-erc677-helper ## BurnMintERC677Helper A helper contract that extends the BurnMintERC677 token contract to provide additional minting functionality. [`BurnMintERC677Helper`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/ccip/BurnMintERC677Helper.sol) ## Functions ### constructor Initializes the token with a name and symbol, setting fixed decimals and initial supply. ```solidity constructor(string memory name, string memory symbol) ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ----------------------- | | name | `string` | The name of the token | | symbol | `string` | The symbol of the token | ### drip Mints exactly one token (1e18 units) to a specified address. ```solidity function drip(address to) external ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | --------------------------------------- | | to | `address` | The address to receive the minted token | --- # CCIPLocalSimulatorFork v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork-js ## CCIPLocalSimulatorFork A JavaScript module that provides utilities for simulating CCIP (Cross-Chain Interoperability Protocol) message routing in a local or forked environment. [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/scripts/CCIPLocalSimulatorFork.js) ## Types ### Evm2EvmMessage Represents a cross-chain message in the EVM-to-EVM communication protocol. Contains all necessary information for message routing and token transfers across chains. | Property | Type | Description | | ------------------- | ---------------------------------------- | ---------------------------------------------- | | sourceChainSelector | `bigint` | The identifier of the source chain | | sender | `string` | The address that sent the message | | receiver | `string` | The address that will receive the message | | sequenceNumber | `bigint` | The sequence number of the message | | gasLimit | `bigint` | The gas limit for executing the message | | strict | `boolean` | Whether the message requires strict execution | | nonce | `bigint` | The nonce of the message | | feeToken | `string` | The token used to pay fees | | feeTokenAmount | `bigint` | The amount of fee token to be paid | | data | `string` | The message payload data | | tokenAmounts | `Array<{token: string, amount: bigint}>` | Array of tokens and amounts being transferred | | sourceTokenData | `Array` | Array of token-specific data from source chain | | messageId | `string` | The unique identifier of the message | ## Functions ### getEvm2EvmMessage Extracts and parses a CCIP message from a transaction receipt by looking for the CCIPSendRequested event. ```javascript function getEvm2EvmMessage(receipt) => Evm2EvmMessage | null ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------ | | receipt | `object` | The transaction receipt from the `ccipSend` call | #### Returns | Type | Description | | ---------------------- | ---------------------------------------------------------------------------------------------- | | `Evm2EvmMessage\|null` | The parsed EVM-to-EVM message if found in the receipt logs, or null if no relevant event found | ### requestLinkFromTheFaucet Requests LINK tokens from a faucet contract for testing purposes. ```javascript async function requestLinkFromTheFaucet(linkAddress, to, amount) => Promise ``` #### Parameters | Parameter | Type | Description | | ----------- | -------- | ------------------------------------------------------- | | linkAddress | `string` | The address of the LINK contract on the current network | | to | `string` | The address to send LINK to | | amount | `bigint` | The amount of LINK to request | #### Returns | Type | Description | | ----------------- | -------------------------------------------------------------- | | `Promise` | Promise resolving to the transaction hash of the fund transfer | ### routeMessage Routes a cross-chain message on the destination network by finding the appropriate off-ramp and executing the message. ```javascript async function routeMessage(routerAddress, evm2EvmMessage) => Promise ``` #### Parameters | Parameter | Type | Description | | -------------- | ---------------- | --------------------------------- | | routerAddress | `string` | Address of the destination Router | | evm2EvmMessage | `Evm2EvmMessage` | Sent cross-chain message | #### Returns | Type | Description | | --------------- | ------------------------------------------------------------ | | `Promise` | Resolves with no value if the message is successfully routed | --- # CCIPLocalSimulatorFork v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork ## CCIPLocalSimulatorFork A contract that simulates CCIP (Cross-Chain Interoperability Protocol) message routing in a Foundry test environment. [`CCIPLocalSimulatorFork`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/ccip/CCIPLocalSimulatorFork.sol) ## Interfaces ### IRouterFork Interface for interacting with the CCIP Router contract in a forked environment. #### OffRamp ```solidity struct OffRamp { uint64 sourceChainSelector; address offRamp; } ``` | Field | Type | Description | | ------------------- | --------- | --------------------------------------- | | sourceChainSelector | `uint64` | The chain selector for the source chain | | offRamp | `address` | The address of the offRamp contract | #### getOffRamps ```solidity function getOffRamps() external view returns (OffRamp[] memory) ``` #### Returns | Parameter | Type | Description | | --------- | ----------- | -------------------------------- | | (unnamed) | `OffRamp[]` | Array of off-ramp configurations | ### IEVM2EVMOffRampFork Interface for executing CCIP messages on an off-ramp contract in a forked environment. #### executeSingleMessage ```solidity function executeSingleMessage( Internal.EVM2EVMMessage memory message, bytes[] memory offchainTokenData, uint32[] memory tokenGasOverrides ) external ``` #### Parameters | Parameter | Type | Description | | ----------------- | ---------------- | --------------------------------------- | | message | `EVM2EVMMessage` | The CCIP message to be executed | | offchainTokenData | `bytes[]` | Additional off-chain token data | | tokenGasOverrides | `uint32[]` | Gas limit overrides for token transfers | ## Events ### CCIPSendRequested ```solidity event CCIPSendRequested(Internal.EVM2EVMMessage message) ``` #### Parameters | Parameter | Type | Description | | --------- | ---------------- | --------------------------------- | | message | `EVM2EVMMessage` | The EVM2EVM message that was sent | ## Variables ### i_register ```solidity Register immutable i_register ``` ### LINK_FAUCET ```solidity address constant LINK_FAUCET = 0x4281eCF07378Ee595C564a59048801330f3084eE ``` ### s_processedMessages ```solidity mapping(bytes32 messageId => bool isProcessed) internal s_processedMessages ``` ## Functions ### constructor Initializes the contract and sets up logging and persistence. ```solidity constructor() ``` ### switchChainAndRouteMessage Routes a cross-chain message on the destination network after switching to the specified fork. ```solidity function switchChainAndRouteMessage(uint256 forkId) external ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------------------------------------------------------------------- | | forkId | `uint256` | The ID of the destination network fork (returned by `createFork()` or `createSelectFork()`) | ### getNetworkDetails Returns the network configuration details for a specified chain ID. ```solidity function getNetworkDetails(uint256 chainId) external view returns (Register.NetworkDetails memory) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | --------------------------------------------------------------------- | | chainId | `uint256` | The blockchain network chain ID (e.g., 11155111 for Ethereum Sepolia) | #### Returns | Parameter | Type | Description | | --------- | ---------------- | --------------------------------------------------------- | | (unnamed) | `NetworkDetails` | The network configuration details for the specified chain | ### setNetworkDetails Updates or adds new network configuration details for a specified chain ID. ```solidity function setNetworkDetails(uint256 chainId, Register.NetworkDetails memory networkDetails) external ``` #### Parameters | Parameter | Type | Description | | -------------- | ---------------- | --------------------------------------------------------------------- | | chainId | `uint256` | The blockchain network chain ID (e.g., 11155111 for Ethereum Sepolia) | | networkDetails | `NetworkDetails` | The network configuration details to be stored | ### requestLinkFromFaucet Requests LINK tokens from the faucet for a specified address. ```solidity function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | -------------------------------------- | | to | `address` | The address to receive the LINK tokens | | amount | `uint256` | The amount of LINK tokens to transfer | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------- | | success | `bool` | Returns true if the token transfer was successful | --- # CCIPLocalSimulator v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/ccip-local-simulator ## CCIPLocalSimulator A contract that simulates local CCIP (Cross-Chain Interoperability Protocol) operations for testing and development purposes. [`CCIPLocalSimulator`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/ccip/CCIPLocalSimulator.sol) ## Variables ### CHAIN_SELECTOR ```solidity uint64 constant CHAIN_SELECTOR = 16015286601757825753 ``` ### i_ccipBnM ```solidity BurnMintERC677Helper internal immutable i_ccipBnM ``` ### i_ccipLnM ```solidity BurnMintERC677Helper internal immutable i_ccipLnM ``` ### i_linkToken ```solidity LinkToken internal immutable i_linkToken ``` ### i_mockRouter ```solidity MockCCIPRouter internal immutable i_mockRouter ``` ### i_wrappedNative ```solidity WETH9 internal immutable i_wrappedNative ``` ### s_supportedTokens ```solidity address[] internal s_supportedTokens ``` ## Errors ### CCIPLocalSimulator__MsgSenderIsNotTokenOwner ```solidity error CCIPLocalSimulator__MsgSenderIsNotTokenOwner() ``` ### CCIPLocalSimulator__RequiredRoleNotFound ```solidity error CCIPLocalSimulator__RequiredRoleNotFound(address account, bytes32 role, address token) ``` ## Functions ### configuration Returns the configuration details for pre-deployed contracts and services needed for local CCIP simulations. ```solidity function configuration() public view returns (uint64 chainSelector_, IRouterClient sourceRouter_, IRouterClient destinationRouter_, WETH9 wrappedNative_, LinkToken linkToken_, BurnMintERC677Helper ccipBnM_, BurnMintERC677Helper ccipLnM_) ``` #### Returns | Parameter | Type | Description | | ------------------- | ---------------------- | -------------------------------------------------------- | | chainSelector_ | `uint64` | The unique CCIP Chain Selector | | sourceRouter_ | `IRouterClient` | The source chain Router contract | | destinationRouter_ | `IRouterClient` | The destination chain Router contract | | wrappedNative_ | `WETH9` | The wrapped native token which can be used for CCIP fees | | linkToken_ | `LinkToken` | The LINK token | | ccipBnM_ | `BurnMintERC677Helper` | The ccipBnM token | | ccipLnM_ | `BurnMintERC677Helper` | The ccipLnM token | ### constructor Initializes the contract with pre-deployed token instances. ```solidity constructor() ``` ### getSupportedTokens Gets the list of supported token addresses for a given chain selector. ```solidity function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens) ``` #### Parameters | Parameter | Type | Description | | ------------- | -------- | ------------------------------ | | chainSelector | `uint64` | The unique CCIP Chain Selector | #### Returns | Parameter | Type | Description | | --------- | ----------- | ------------------------------------------------------------------------------ | | tokens | `address[]` | Returns a list of token addresses that are supported for cross-chain transfers | ### isChainSupported Checks if a given chain selector is supported. ```solidity function isChainSupported(uint64 chainSelector) public pure returns (bool supported) ``` #### Parameters | Parameter | Type | Description | | ------------- | -------- | ------------------------------ | | chainSelector | `uint64` | The unique CCIP Chain Selector | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------- | | supported | `bool` | Returns true if `chainSelector` is supported by the simulator | ### requestLinkFromFaucet Transfers LINK tokens from the faucet to a specified address. ```solidity function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ----------------------------------------------- | | to | `address` | The address to which LINK tokens are to be sent | | amount | `uint256` | The amount of LINK tokens to send | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------- | | success | `bool` | Returns `true` if the transfer of tokens was successful | ### supportNewTokenViaAccessControlDefaultAdmin Adds a new token to supported tokens list via AccessControl's DEFAULT_ADMIN_ROLE. ```solidity function supportNewTokenViaAccessControlDefaultAdmin(address tokenAddress) external ``` #### Parameters | Parameter | Type | Description | | ------------ | --------- | --------------------------------------------------------------- | | tokenAddress | `address` | The address of the token to add to the list of supported tokens | ### supportNewTokenViaGetCCIPAdmin Adds a new token to supported tokens list via CCIP admin role. ```solidity function supportNewTokenViaGetCCIPAdmin(address tokenAddress) external ``` #### Parameters | Parameter | Type | Description | | ------------ | --------- | --------------------------------------------------------------- | | tokenAddress | `address` | The address of the token to add to the list of supported tokens | ### supportNewTokenViaOwner Adds a new token to supported tokens list via token owner. ```solidity function supportNewTokenViaOwner(address tokenAddress) external ``` #### Parameters | Parameter | Type | Description | | ------------ | --------- | --------------------------------------------------------------- | | tokenAddress | `address` | The address of the token to add to the list of supported tokens | --- # Chainlink Local v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3 ## CCIP These contracts provide Cross-Chain Interoperability Protocol (CCIP) functionality in a local testing environment: - [CCIPLocalSimulator](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator) - Local CCIP message routing simulator - [CCIPLocalSimulatorFork](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork) - CCIP simulator for forked networks - [CCIPLocalSimulatorFork JS](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork-js) - JavaScript utilities for CCIP simulation - [Register](/chainlink-local/api-reference/v0.2.3/register) - CCIP network configuration registry ## Data Feeds Contracts for simulating Chainlink Data Feeds: - [AggregatorInterface](/chainlink-local/api-reference/v0.2.3/aggregator-interface) - Basic price feed interface - [AggregatorV2V3Interface](/chainlink-local/api-reference/v0.2.3/aggregator-v2-v3-interface) - Combined V2/V3 price feed interface - [AggregatorV3Interface](/chainlink-local/api-reference/v0.2.3/aggregator-v3-interface) - Extended price feed interface - [MockOffchainAggregator](/chainlink-local/api-reference/v0.2.3/mock-offchain-aggregator) - Mock implementation of off-chain aggregator - [MockV3Aggregator](/chainlink-local/api-reference/v0.2.3/mock-v3-aggregator) - Mock implementation of V3 aggregator ## Token Contracts Standard token implementations for testing: - [BurnMintERC677Helper](/chainlink-local/api-reference/v0.2.3/burn-mint-erc677-helper) - Helper contract for ERC677 token operations - [LinkToken](/chainlink-local/api-reference/v0.2.3/link-token) - LINK token implementation - [WETH9](/chainlink-local/api-reference/v0.2.3/weth9) - Wrapped Ether implementation --- # LinkToken v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/link-token ## LinkToken A contract that implements the ChainLink Token (LINK) using the ERC677 standard. [`LinkToken`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/shared/LinkToken.sol) ## Functions ### constructor Initializes the contract with fixed token details. ```solidity constructor() ``` ### _onCreate Internal hook called during contract creation. ```solidity function _onCreate() internal virtual ``` --- # MockOffchainAggregator v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/mock-offchain-aggregator ## MockOffchainAggregator A mock implementation of an offchain aggregator for testing purposes. [`MockOffchainAggregator`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/data-feeds/MockOffchainAggregator.sol) ## Variables ### decimals ```solidity uint8 public decimals ``` ### getAnswer ```solidity mapping(uint256 => int256) public getAnswer ``` ### getTimestamp ```solidity mapping(uint256 => uint256) public getTimestamp ``` ### latestAnswer ```solidity int256 public latestAnswer ``` ### latestRound ```solidity uint256 public latestRound ``` ### latestTimestamp ```solidity uint256 public latestTimestamp ``` ### maxAnswer ```solidity int192 public maxAnswer ``` ### minAnswer ```solidity int192 public minAnswer ``` ## Functions ### constructor Initializes the contract with decimals and initial answer. ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` #### Parameters | Parameter | Type | Description | | --------------- | -------- | ----------------------------------------- | | _decimals | `uint8` | The number of decimals for the aggregator | | _initialAnswer | `int256` | The initial answer to be set | ### getRoundData Gets the round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | -------------------------------- | | _roundId | `uint80` | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | --------- | --------------------------------------------- | | roundId | `uint80` | The round ID | | answer | `int256` | The answer for the round | | startedAt | `uint256` | The timestamp when the round started | | updatedAt | `uint256` | The timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### latestRoundData Gets the latest round data. ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Returns | Parameter | Type | Description | | --------------- | --------- | ---------------------------------------------------- | | roundId | `uint80` | The latest round ID | | answer | `int256` | The latest answer | | startedAt | `uint256` | The timestamp when the latest round started | | updatedAt | `uint256` | The timestamp when the latest round was updated | | answeredInRound | `uint80` | The round ID in which the latest answer was computed | ### updateAnswer Updates the answer in the mock aggregator. ```solidity function updateAnswer(int256 _answer) public ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------ | | _answer | `int256` | The new answer to be set | ### updateMinAndMaxAnswers Updates the minimum and maximum answers the aggregator can report. ```solidity function updateMinAndMaxAnswers(int192 _minAnswer, int192 _maxAnswer) external ``` #### Parameters | Parameter | Type | Description | | ----------- | -------- | ---------------------- | | _minAnswer | `int192` | The new minimum answer | | _maxAnswer | `int192` | The new maximum answer | #### Possible Reverts - Reverts if minAnswer is not less than maxAnswer with "minAnswer must be less than maxAnswer" - Reverts if minAnswer is too low with "minAnswer is too low" - Reverts if maxAnswer is too high with "maxAnswer is too high" ### updateRoundData Updates the round data in the mock aggregator. ```solidity function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public ``` #### Parameters | Parameter | Type | Description | | ----------- | --------- | ------------------------------------ | | _roundId | `uint80` | The round ID to be updated | | _answer | `int256` | The new answer to be set | | _timestamp | `uint256` | The timestamp to be set | | _startedAt | `uint256` | The timestamp when the round started | --- # MockV3Aggregator v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/mock-v3-aggregator ## MockV3Aggregator A mock implementation of the AggregatorV2V3Interface for testing purposes. [`MockV3Aggregator`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/data-feeds/MockV3Aggregator.sol) ## Variables ### aggregator ```solidity address public aggregator ``` ### proposedAggregator ```solidity address public proposedAggregator ``` ### version ```solidity uint256 public constant override version = 0 ``` ## Functions ### confirmAggregator Confirms the proposed aggregator. ```solidity function confirmAggregator(address _aggregator) external ``` #### Parameters | Parameter | Type | Description | | ------------ | --------- | -------------------------------------- | | _aggregator | `address` | The address of the proposed aggregator | #### Possible Reverts - Reverts if the provided aggregator address does not match the proposed aggregator with "Invalid proposed aggregator" ### constructor Initializes the contract with decimals and initial answer. ```solidity constructor(uint8 _decimals, int256 _initialAnswer) ``` #### Parameters | Parameter | Type | Description | | --------------- | -------- | ----------------------------------------- | | _decimals | `uint8` | The number of decimals for the aggregator | | _initialAnswer | `int256` | The initial answer to be set | ### decimals Gets the number of decimals used by the aggregator. ```solidity function decimals() external view returns (uint8) ``` #### Returns | Parameter | Type | Description | | --------- | ------- | ---------------------- | | (unnamed) | `uint8` | The number of decimals | ### description Gets the description of the aggregator. ```solidity function description() external pure returns (string memory) ``` #### Returns | Parameter | Type | Description | | --------- | -------- | ------------------------------------ | | (unnamed) | `string` | The contract path as the description | ### getAnswer Gets the answer for a specific round ID. ```solidity function getAnswer(uint256 roundId) external view returns (int256) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------ | | roundId | `uint256` | The round ID to get answer for | #### Returns | Parameter | Type | Description | | --------- | -------- | --------------------------------- | | (unnamed) | `int256` | The answer for the given round ID | ### getRoundData Gets the round data for a specific round ID. ```solidity function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | -------------------------------- | | _roundId | `uint80` | The round ID to get the data for | #### Returns | Parameter | Type | Description | | --------------- | --------- | --------------------------------------------- | | roundId | `uint80` | The round ID | | answer | `int256` | The answer for the round | | startedAt | `uint256` | The timestamp when the round started | | updatedAt | `uint256` | The timestamp when the round was updated | | answeredInRound | `uint80` | The round ID in which the answer was computed | ### getTimestamp Gets the timestamp for a specific round ID. ```solidity function getTimestamp(uint256 roundId) external view returns (uint256) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | --------------------------------- | | roundId | `uint256` | The round ID to get timestamp for | #### Returns | Parameter | Type | Description | | --------- | --------- | ------------------------------------ | | (unnamed) | `uint256` | The timestamp for the given round ID | ### latestAnswer Gets the latest answer from the aggregator. ```solidity function latestAnswer() external view returns (int256) ``` #### Returns | Parameter | Type | Description | | --------- | -------- | ----------------- | | (unnamed) | `int256` | The latest answer | ### latestRound Gets the latest round ID from the aggregator. ```solidity function latestRound() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ------------------- | | (unnamed) | `uint256` | The latest round ID | ### latestRoundData Gets the latest round data. ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` #### Returns | Parameter | Type | Description | | --------------- | --------- | ---------------------------------------------------- | | roundId | `uint80` | The latest round ID | | answer | `int256` | The latest answer | | startedAt | `uint256` | The timestamp when the latest round started | | updatedAt | `uint256` | The timestamp when the latest round was updated | | answeredInRound | `uint80` | The round ID in which the latest answer was computed | ### latestTimestamp Gets the timestamp of the latest answer. ```solidity function latestTimestamp() external view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ---------------------------------- | | (unnamed) | `uint256` | The timestamp of the latest answer | ### proposeAggregator Proposes a new aggregator. ```solidity function proposeAggregator(AggregatorV2V3Interface _aggregator) external ``` #### Parameters | Parameter | Type | Description | | ------------ | ------------------------- | -------------------------------------- | | _aggregator | `AggregatorV2V3Interface` | The address of the proposed aggregator | #### Possible Reverts - Reverts if the proposed aggregator is the zero address with "Proposed aggregator cannot be zero address" - Reverts if the proposed aggregator is the current aggregator with "Proposed aggregator cannot be current aggregator" ### updateAnswer Updates the answer in the mock aggregator. ```solidity function updateAnswer(int256 _answer) public ``` #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------ | | _answer | `int256` | The new answer to be set | ### updateRoundData Updates the round data in the mock aggregator. ```solidity function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public ``` #### Parameters | Parameter | Type | Description | | ----------- | --------- | ------------------------------------ | | _roundId | `uint80` | The round ID to be updated | | _answer | `int256` | The new answer to be set | | _timestamp | `uint256` | The timestamp to be set | | _startedAt | `uint256` | The timestamp when the round started | --- # Register v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/register ## Register A contract that stores and manages network configuration details for various blockchain networks supported by CCIP (Cross-Chain Interoperability Protocol). [`Register`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/ccip/Register.sol) ## Structs ### NetworkDetails Contains configuration details for a specific network/chain. | Field | Type | Description | | -------------------------------- | --------- | -------------------------------------------- | | chainSelector | `uint64` | Unique identifier for the chain in CCIP | | routerAddress | `address` | Address of the CCIP Router contract | | linkAddress | `address` | Address of the LINK token contract | | wrappedNativeAddress | `address` | Address of the wrapped native token contract | | ccipBnMAddress | `address` | Address of the CCIP BnM token contract | | ccipLnMAddress | `address` | Address of the CCIP LnM token contract | | rmnProxyAddress | `address` | Address of the RMN proxy contract | | registryModuleOwnerCustomAddress | `address` | Address of the registry module owner | | tokenAdminRegistryAddress | `address` | Address of the token admin registry contract | ## Variables ### s_networkDetails ```solidity mapping(uint256 chainId => NetworkDetails) internal s_networkDetails ``` ## Functions ### constructor Initializes the contract with predefined network details for various supported chains. ```solidity constructor() ``` ### getNetworkDetails Retrieves the network details for a specified chain ID. ```solidity function getNetworkDetails(uint256 chainId) external view returns (NetworkDetails memory networkDetails) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | -------------------------------------- | | chainId | `uint256` | The ID of the chain to get details for | #### Returns | Type | Description | | ---------------- | ----------------------------------------------- | | `NetworkDetails` | The network configuration details for the chain | ### setNetworkDetails Sets or updates the network details for a specified chain ID. ```solidity function setNetworkDetails(uint256 chainId, NetworkDetails memory networkDetails) external ``` #### Parameters | Parameter | Type | Description | | -------------- | ---------------- | ---------------------------------------------- | | chainId | `uint256` | The ID of the chain to set details for | | networkDetails | `NetworkDetails` | The network configuration details to be stored | --- # WETH9 v0.2.3 API Reference Source: https://docs.chain.link/chainlink-local/api-reference/v0.2.3/weth9 ## WETH9 A contract that implements Wrapped Ether (WETH), allowing users to wrap and unwrap ETH. [`WETH9`](https://github.com/smartcontractkit/chainlink-local/blob/7d8b2f888e1f10c8841ccd9e0f4af0f5baf11dab/src/shared/WETH9.sol) ## Variables ### allowance ```solidity mapping(address => mapping(address => uint256)) public allowance ``` ### balanceOf ```solidity mapping(address => uint256) public balanceOf ``` ### decimals ```solidity uint8 public decimals = 18 ``` ### name ```solidity string public name = "Wrapped Ether" ``` ### symbol ```solidity string public symbol = "WETH" ``` ## Events ### Approval ```solidity event Approval(address indexed src, address indexed guy, uint256 wad) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ----------------------------------- | | src | `address` | The address giving the approval | | guy | `address` | The address receiving the approval | | wad | `uint256` | The amount of tokens being approved | ### Deposit ```solidity event Deposit(address indexed dst, uint256 wad) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------- | | dst | `address` | The address receiving the WETH | | wad | `uint256` | The amount of ETH being wrapped | ### Transfer ```solidity event Transfer(address indexed src, address indexed dst, uint256 wad) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | -------------------------------------- | | src | `address` | The address sending the tokens | | dst | `address` | The address receiving the tokens | | wad | `uint256` | The amount of tokens being transferred | ### Withdrawal ```solidity event Withdrawal(address indexed src, uint256 wad) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ---------------------------------- | | src | `address` | The address unwrapping the WETH | | wad | `uint256` | The amount of WETH being unwrapped | ## Functions ### approve Approves another address to spend tokens. ```solidity function approve(address guy, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ------------------------------- | | guy | `address` | The address to approve | | wad | `uint256` | The amount of tokens to approve | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------- | | (unnamed) | `bool` | Always returns true | ### deposit Deposits ETH to receive WETH. ```solidity function deposit() external payable ``` ### totalSupply Gets the total supply of WETH. ```solidity function totalSupply() public view returns (uint256) ``` #### Returns | Parameter | Type | Description | | --------- | --------- | ------------------------ | | (unnamed) | `uint256` | The total supply of WETH | ### transfer Transfers tokens to another address. ```solidity function transfer(address dst, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | -------------------------------- | | dst | `address` | The recipient address | | wad | `uint256` | The amount of tokens to transfer | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------- | | (unnamed) | `bool` | Returns true if the transfer succeeds | ### transferFrom Transfers tokens from one address to another. ```solidity function transferFrom(address src, address dst, uint256 wad) public returns (bool) ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | -------------------------------- | | src | `address` | The source address | | dst | `address` | The destination address | | wad | `uint256` | The amount of tokens to transfer | #### Returns | Parameter | Type | Description | | --------- | ------ | ------------------------------------- | | (unnamed) | `bool` | Returns true if the transfer succeeds | #### Possible Reverts - Reverts if the source address has insufficient balance - Reverts if the caller has insufficient allowance (unless caller is source or has maximum allowance) ### withdraw Withdraws ETH by unwrapping WETH. ```solidity function withdraw(uint256 wad) external ``` #### Parameters | Parameter | Type | Description | | --------- | --------- | ---------------------------- | | wad | `uint256` | The amount of WETH to unwrap | #### Possible Reverts - Reverts if the caller has insufficient WETH balance --- # CCT - getCCIPAdmin() token with Burn and Mint Pool in forked environments Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/cct-burn-and-mint-fork This tutorial will guide you through the process of testing the procedure of enabling your own tokens in CCIP. We will use the CCT-compatible ERC-20 token with `getCCIPAdmin` function and burning & minting capabilities. We will use Burn & Mint Pool for transferring this token across different blockchains using Chainlink CCIP. ## Prerequisites Before we start with this guide, let's recap parts of the CCT standard that we will need for it. ### Requirements for Cross-Chain Tokens Before enabling an ERC20-compatible token in CCIP, it's important to understand the requirements it must fulfill to integrate with CCIP. - **Recommended Permissionless Token Administrator address registration methods**: A token can utilize either of these supported function signatures to register permissionlessly: - `owner()`: This function returns the token contract owner's address. - `getCCIPAdmin()`: This function returns the token administrator's address and is recommended for new tokens, as it allows for abstraction of the CCIP Token Administrator role from other common roles, like `owner()`. - **Requirements for CCIP token transfers**: The token's smart contract must meet minimum requirements to integrate with CCIP. - **Burn & Mint Requirements**: - The token smart contract must have the following functions: - `mint(address account, uint256 amount)`: This function is used to mint the `amount` of tokens to a given `account` on the destination blockchain. - `burn(uint256 amount)`: This function is used to burn the `amount` of tokens on the source blockchain. - `decimals()`: Returns the token's number of decimals. - `balanceOf(address account)`: Returns the current token balance of the specified `account`. - `burnFrom(address account, uint256 amount)`: This function burns a specified number of tokens from the provided account on the source blockchain. **Note**: This is an optional function. We generally recommend using the `burn` function, but if you use a tokenPool that calls `burnFrom`, your token contract will need to implement this function. - On the source and destination blockchains, the token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool. - **Lock & Mint Requirements**: - The token smart contract must have the following functions: - `decimals()`: Returns the token's number of decimals. - `balanceOf(address account)`: Returns the current token balance of the specified `account`. - On the destination blockchain, The token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool. If you don't have an existing token: For all blockchains where tokens need to be burned and minted (for example, the source or destination chain in the case of Burn and Mint, or the destination blockchain in the case of Lock and Mint), Chainlink provides a [BurnMintERC677](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.1/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol) contract that you can use to deploy your token in minutes. This token follows the [ERC677](https://github.com/ethereum/EIPs/issues/677) or [ERC777](https://ethereum.org/en/developers/docs/standards/tokens/erc-777/), allowing you to use it as-is or extend it to meet your specific requirements. ### Understanding the Procedure It is also important first to understand the overall procedure for enabling your tokens in CCIP. This procedure involves deploying tokens and token pools, registering administrative roles, and configuring token pools to enable secure token transfers using CCIP. The steps in the diagram below highlight the flow of actions needed to enable a token for cross-chain transfers. Whether you're working with an Externally Owned Account (EOA) or a **Smart Account** (such as one using a multisig scheme), the overall logic remains the same. You'll follow the same process to enable cross-chain token transfers, configure pools, and register administrative roles. The diagram below outlines the entire process: ## Before You Begin 1. **Install Foundry**: If you haven't already, follow the instructions in the [Foundry documentation](https://book.getfoundry.sh/getting-started/installation) to install Foundry. 2. **Create new Foundry project**: Create a new Foundry project by running the following command: ```bash forge init ``` 3. **Set up your environment**: Create a `.env` file, and fill in the required values: Example `.env` file: ```bash ETHEREUM_SEPOLIA_RPC_URL= BASE_SEPOLIA_RPC_URL= ``` ## Create the `getCCIPAdmin()` ERC-20 token Inside the `test` folder create the new Solidity file and name it `CCIPv1_5ForkBurnMintPoolFork.t.sol`. We will use only this file through out the rest of this guide. Create the CCT-compatible ERC-20 token with `getCCIPAdmin()` function and burning and minting capabilities. ```solidity // test/CCIPv1_5ForkBurnMintPoolFork.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { ERC20, ERC20Burnable, IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import { AccessControl } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControl.sol"; contract MockERC20BurnAndMintToken is IBurnMintERC20, ERC20Burnable, AccessControl { address internal immutable i_CCIPAdmin; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); constructor() ERC20("MockERC20BurnAndMintToken", "MTK") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); _grantRole(BURNER_ROLE, msg.sender); i_CCIPAdmin = msg.sender; } function mint(address account, uint256 amount) public onlyRole(MINTER_ROLE) { _mint(account, amount); } function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) { super.burn(amount); } function burnFrom( address account, uint256 amount ) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) { super.burnFrom(account, amount); } function burn(address account, uint256 amount) public virtual override { burnFrom(account, amount); } function getCCIPAdmin() public view returns (address) { return i_CCIPAdmin; } } ``` ## Test the CCT enabling procedure Expand the existing `CCIPv1_5ForkBurnMintPoolFork.t.sol` file to create and set up our basic test. ```solidity // test/CCIPv1_5ForkBurnMintPoolFork.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Test, Vm } from "forge-std/Test.sol"; import { CCIPLocalSimulatorFork, Register } from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; import { BurnMintTokenPool, TokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/BurnMintTokenPool.sol"; import { LockReleaseTokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/LockReleaseTokenPool.sol"; // not used in this test import { IBurnMintERC20 } from "@chainlink/contracts-ccip/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol"; import { RegistryModuleOwnerCustom } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; import { TokenAdminRegistry } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol"; import { RateLimiter } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol"; import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; /** * The token code part from previous section goes here... */ contract CCIPv1_5BurnMintPoolFork is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; MockERC20BurnAndMintToken public mockERC20TokenEthSepolia; MockERC20BurnAndMintToken public mockERC20TokenBaseSepolia; BurnMintTokenPool public burnMintTokenPoolEthSepolia; BurnMintTokenPool public burnMintTokenPoolBaseSepolia; Register.NetworkDetails ethSepoliaNetworkDetails; Register.NetworkDetails baseSepoliaNetworkDetails; uint256 ethSepoliaFork; uint256 baseSepoliaFork; address alice; function setUp() public { alice = makeAddr("alice"); string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); string memory BASE_SEPOLIA_RPC_URL = vm.envString("BASE_SEPOLIA_RPC_URL"); ethSepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); baseSepoliaFork = vm.createFork(BASE_SEPOLIA_RPC_URL); ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); } } ``` #### Step 1) Deploy token on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function setUp() public { // Code from previous section goes here... // Step 1) Deploy token on Ethereum Sepolia vm.startPrank(alice); mockERC20TokenEthSepolia = new MockERC20BurnAndMintToken(); vm.stopPrank(); } } ``` #### Step 2) Deploy token on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function setUp() public { // Code from previous section goes here... // Step 2) Deploy token on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia = new MockERC20BurnAndMintToken(); vm.stopPrank(); } } ``` #### Step 3) Deploy BurnMintTokenPool on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { // Code from previous section goes here... function test_forkSupportNewCCIPToken() public { // Step 3) Deploy BurnMintTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); address[] memory allowlist = new address[](0); uint8 localTokenDecimals = 18; vm.startPrank(alice); burnMintTokenPoolEthSepolia = new BurnMintTokenPool( IBurnMintERC20(address(mockERC20TokenEthSepolia)), localTokenDecimals, allowlist, ethSepoliaNetworkDetails.rmnProxyAddress, ethSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); } } ``` #### Step 4) Deploy BurnMintTokenPool on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 4) Deploy BurnMintTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); vm.startPrank(alice); burnMintTokenPoolBaseSepolia = new BurnMintTokenPool( IBurnMintERC20(address(mockERC20TokenBaseSepolia)), localTokenDecimals, allowlist, baseSepoliaNetworkDetails.rmnProxyAddress, baseSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); } } ``` #### Step 5) Grant Mint and Burn roles to BurnMintTokenPool on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 5) Grant Mint and Burn roles to BurnMintTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); mockERC20TokenEthSepolia.grantRole(mockERC20TokenEthSepolia.MINTER_ROLE(), address(burnMintTokenPoolEthSepolia)); mockERC20TokenEthSepolia.grantRole(mockERC20TokenEthSepolia.BURNER_ROLE(), address(burnMintTokenPoolEthSepolia)); vm.stopPrank(); } } ``` #### Step 6) Grant Mint and Burn roles to BurnMintTokenPool on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 6) Grant Mint and Burn roles to BurnMintTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia.grantRole(mockERC20TokenBaseSepolia.MINTER_ROLE(), address(burnMintTokenPoolBaseSepolia)); mockERC20TokenBaseSepolia.grantRole(mockERC20TokenBaseSepolia.BURNER_ROLE(), address(burnMintTokenPoolBaseSepolia)); vm.stopPrank(); } } ``` #### Step 7) Claim Admin role on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 7) Claim Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomEthSepolia = RegistryModuleOwnerCustom( ethSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomEthSepolia.registerAdminViaGetCCIPAdmin(address(mockERC20TokenEthSepolia)); vm.stopPrank(); } } ``` #### Step 8) Claim Admin role on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 8) Claim Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomBaseSepolia = RegistryModuleOwnerCustom( baseSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomBaseSepolia.registerAdminViaGetCCIPAdmin(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); } } ``` #### Step 9) Accept Admin role on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 9) Accept Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); TokenAdminRegistry tokenAdminRegistryEthSepolia = TokenAdminRegistry( ethSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryEthSepolia.acceptAdminRole(address(mockERC20TokenEthSepolia)); vm.stopPrank(); } } ``` #### Step 10) Accept Admin role on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 10) Accept Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); TokenAdminRegistry tokenAdminRegistryBaseSepolia = TokenAdminRegistry( baseSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.acceptAdminRole(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); } } ``` #### Step 11) Link token to pool on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 11) Link token to pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); tokenAdminRegistryEthSepolia.setPool(address(mockERC20TokenEthSepolia), address(burnMintTokenPoolEthSepolia)); vm.stopPrank(); } } ``` #### Step 12) Link token to pool on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 12) Link token to pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.setPool(address(mockERC20TokenBaseSepolia), address(burnMintTokenPoolBaseSepolia)); vm.stopPrank(); } } ``` #### Step 13) Configure Token Pool on Ethereum Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 13) Configure Token Pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1); remotePoolAddressesEthSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: baseSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesEthSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }) }); uint64[] memory remoteChainSelectorsToRemove = new uint64[](0); burnMintTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); } } ``` #### Step 14) Configure Token Pool on Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 14) Configure Token Pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1); remotePoolAddressesBaseSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: ethSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesBaseSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }) }); burnMintTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); } } ``` #### Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia ```solidity contract CCIPv1_5BurnMintPoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia vm.selectFork(ethSepoliaFork); address linkSepolia = ethSepoliaNetworkDetails.linkAddress; ccipLocalSimulatorFork.requestLinkFromFaucet(address(alice), 20 ether); uint256 amountToSend = 100; Client.EVMTokenAmount[] memory tokenToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ token: address(mockERC20TokenEthSepolia), amount: amountToSend }); tokenToSendDetails[0] = tokenAmount; vm.startPrank(alice); mockERC20TokenEthSepolia.mint(address(alice), amountToSend); mockERC20TokenEthSepolia.approve(ethSepoliaNetworkDetails.routerAddress, amountToSend); IERC20(linkSepolia).approve(ethSepoliaNetworkDetails.routerAddress, 20 ether); uint256 balanceOfAliceBeforeEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); IRouterClient routerEthSepolia = IRouterClient(ethSepoliaNetworkDetails.routerAddress); routerEthSepolia.ccipSend( baseSepoliaNetworkDetails.chainSelector, Client.EVM2AnyMessage({ receiver: abi.encode(address(alice)), data: "", tokenAmounts: tokenToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({ gasLimit: 0 })), feeToken: linkSepolia }) ); uint256 balanceOfAliceAfterEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); vm.stopPrank(); assertEq(balanceOfAliceAfterEthSepolia, balanceOfAliceBeforeEthSepolia - amountToSend); ccipLocalSimulatorFork.switchChainAndRouteMessage(baseSepoliaFork); uint256 balanceOfAliceAfterBaseSepolia = mockERC20TokenBaseSepolia.balanceOf(alice); assertEq(balanceOfAliceAfterBaseSepolia, amountToSend); } } ``` ## Final code - full example ```solidity // test/CCIPv1_5ForkBurnMintPoolFork.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Test, Vm } from "forge-std/Test.sol"; import { CCIPLocalSimulatorFork, Register } from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; import { BurnMintTokenPool, TokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/BurnMintTokenPool.sol"; import { LockReleaseTokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/LockReleaseTokenPool.sol"; // not used in this test import { IBurnMintERC20 } from "@chainlink/contracts-ccip/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol"; import { RegistryModuleOwnerCustom } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; import { TokenAdminRegistry } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol"; import { RateLimiter } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol"; import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; import { ERC20, ERC20Burnable, IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import { AccessControl } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControl.sol"; contract MockERC20BurnAndMintToken is IBurnMintERC20, ERC20Burnable, AccessControl { address internal immutable i_CCIPAdmin; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); constructor() ERC20("MockERC20BurnAndMintToken", "MTK") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); _grantRole(BURNER_ROLE, msg.sender); i_CCIPAdmin = msg.sender; } function mint(address account, uint256 amount) public onlyRole(MINTER_ROLE) { _mint(account, amount); } function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) { super.burn(amount); } function burnFrom( address account, uint256 amount ) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) { super.burnFrom(account, amount); } function burn(address account, uint256 amount) public virtual override { burnFrom(account, amount); } function getCCIPAdmin() public view returns (address) { return i_CCIPAdmin; } } contract CCIPv1_5BurnMintPoolFork is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; MockERC20BurnAndMintToken public mockERC20TokenEthSepolia; MockERC20BurnAndMintToken public mockERC20TokenBaseSepolia; BurnMintTokenPool public burnMintTokenPoolEthSepolia; BurnMintTokenPool public burnMintTokenPoolBaseSepolia; Register.NetworkDetails ethSepoliaNetworkDetails; Register.NetworkDetails baseSepoliaNetworkDetails; uint256 ethSepoliaFork; uint256 baseSepoliaFork; address alice; function setUp() public { alice = makeAddr("alice"); string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); string memory BASE_SEPOLIA_RPC_URL = vm.envString("BASE_SEPOLIA_RPC_URL"); ethSepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); baseSepoliaFork = vm.createFork(BASE_SEPOLIA_RPC_URL); ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); // Step 1) Deploy token on Ethereum Sepolia vm.startPrank(alice); mockERC20TokenEthSepolia = new MockERC20BurnAndMintToken(); vm.stopPrank(); // Step 2) Deploy token on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia = new MockERC20BurnAndMintToken(); vm.stopPrank(); } function test_forkSupportNewCCIPToken() public { // Step 3) Deploy BurnMintTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); address[] memory allowlist = new address[](0); uint8 localTokenDecimals = 18; vm.startPrank(alice); burnMintTokenPoolEthSepolia = new BurnMintTokenPool( IBurnMintERC20(address(mockERC20TokenEthSepolia)), localTokenDecimals, allowlist, ethSepoliaNetworkDetails.rmnProxyAddress, ethSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); // Step 4) Deploy BurnMintTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); vm.startPrank(alice); burnMintTokenPoolBaseSepolia = new BurnMintTokenPool( IBurnMintERC20(address(mockERC20TokenBaseSepolia)), localTokenDecimals, allowlist, baseSepoliaNetworkDetails.rmnProxyAddress, baseSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); // Step 5) Grant Mint and Burn roles to BurnMintTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); mockERC20TokenEthSepolia.grantRole(mockERC20TokenEthSepolia.MINTER_ROLE(), address(burnMintTokenPoolEthSepolia)); mockERC20TokenEthSepolia.grantRole(mockERC20TokenEthSepolia.BURNER_ROLE(), address(burnMintTokenPoolEthSepolia)); vm.stopPrank(); // Step 6) Grant Mint and Burn roles to BurnMintTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia.grantRole(mockERC20TokenBaseSepolia.MINTER_ROLE(), address(burnMintTokenPoolBaseSepolia)); mockERC20TokenBaseSepolia.grantRole(mockERC20TokenBaseSepolia.BURNER_ROLE(), address(burnMintTokenPoolBaseSepolia)); vm.stopPrank(); // Step 7) Claim Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomEthSepolia = RegistryModuleOwnerCustom( ethSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomEthSepolia.registerAdminViaGetCCIPAdmin(address(mockERC20TokenEthSepolia)); vm.stopPrank(); // Step 8) Claim Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomBaseSepolia = RegistryModuleOwnerCustom( baseSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomBaseSepolia.registerAdminViaGetCCIPAdmin(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); // Step 9) Accept Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); TokenAdminRegistry tokenAdminRegistryEthSepolia = TokenAdminRegistry( ethSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryEthSepolia.acceptAdminRole(address(mockERC20TokenEthSepolia)); vm.stopPrank(); // Step 10) Accept Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); TokenAdminRegistry tokenAdminRegistryBaseSepolia = TokenAdminRegistry( baseSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.acceptAdminRole(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); // Step 11) Link token to pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); tokenAdminRegistryEthSepolia.setPool(address(mockERC20TokenEthSepolia), address(burnMintTokenPoolEthSepolia)); vm.stopPrank(); // Step 12) Link token to pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.setPool(address(mockERC20TokenBaseSepolia), address(burnMintTokenPoolBaseSepolia)); vm.stopPrank(); // Step 13) Configure Token Pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1); remotePoolAddressesEthSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: baseSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesEthSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }) }); uint64[] memory remoteChainSelectorsToRemove = new uint64[](0); burnMintTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); // Step 14) Configure Token Pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1); remotePoolAddressesBaseSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: ethSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesBaseSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: 100_000, rate: 167 }) }); burnMintTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); // Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia vm.selectFork(ethSepoliaFork); address linkSepolia = ethSepoliaNetworkDetails.linkAddress; ccipLocalSimulatorFork.requestLinkFromFaucet(address(alice), 20 ether); uint256 amountToSend = 100; Client.EVMTokenAmount[] memory tokenToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ token: address(mockERC20TokenEthSepolia), amount: amountToSend }); tokenToSendDetails[0] = tokenAmount; vm.startPrank(alice); mockERC20TokenEthSepolia.mint(address(alice), amountToSend); mockERC20TokenEthSepolia.approve(ethSepoliaNetworkDetails.routerAddress, amountToSend); IERC20(linkSepolia).approve(ethSepoliaNetworkDetails.routerAddress, 20 ether); uint256 balanceOfAliceBeforeEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); IRouterClient routerEthSepolia = IRouterClient(ethSepoliaNetworkDetails.routerAddress); routerEthSepolia.ccipSend( baseSepoliaNetworkDetails.chainSelector, Client.EVM2AnyMessage({ receiver: abi.encode(address(alice)), data: "", tokenAmounts: tokenToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({ gasLimit: 0 })), feeToken: linkSepolia }) ); uint256 balanceOfAliceAfterEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); vm.stopPrank(); assertEq(balanceOfAliceAfterEthSepolia, balanceOfAliceBeforeEthSepolia - amountToSend); ccipLocalSimulatorFork.switchChainAndRouteMessage(baseSepoliaFork); uint256 balanceOfAliceAfterBaseSepolia = mockERC20TokenBaseSepolia.balanceOf(alice); assertEq(balanceOfAliceAfterBaseSepolia, amountToSend); } } ``` --- # CCT - owner() token with Lock and Release Pool in forked environments Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/cct-lock-and-release-fork This tutorial will guide you through the process of testing the procedure of enabling your own tokens in CCIP. We will use the CCT-compatible ERC-20 token with `owner()` function implemented. We will use Lock & Release Pool for transferring this token across different blockchains using Chainlink CCIP. ## Prerequisites Before we start with this guide, let's recap parts of the CCT standard that we will need for it. ### Requirements for Cross-Chain Tokens Before enabling an ERC20-compatible token in CCIP, it's important to understand the requirements it must fulfill to integrate with CCIP. - **Recommended Permissionless Token Administrator address registration methods**: A token can utilize either of these supported function signatures to register permissionlessly: - `owner()`: This function returns the token contract owner's address. - `getCCIPAdmin()`: This function returns the token administrator's address and is recommended for new tokens, as it allows for abstraction of the CCIP Token Administrator role from other common roles, like `owner()`. - **Requirements for CCIP token transfers**: The token's smart contract must meet minimum requirements to integrate with CCIP. - **Burn & Mint Requirements**: - The token smart contract must have the following functions: - `mint(address account, uint256 amount)`: This function is used to mint the `amount` of tokens to a given `account` on the destination blockchain. - `burn(uint256 amount)`: This function is used to burn the `amount` of tokens on the source blockchain. - `decimals()`: Returns the token's number of decimals. - `balanceOf(address account)`: Returns the current token balance of the specified `account`. - `burnFrom(address account, uint256 amount)`: This function burns a specified number of tokens from the provided account on the source blockchain. **Note**: This is an optional function. We generally recommend using the `burn` function, but if you use a tokenPool that calls `burnFrom`, your token contract will need to implement this function. - On the source and destination blockchains, the token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool. - **Lock & Mint Requirements**: - The token smart contract must have the following functions: - `decimals()`: Returns the token's number of decimals. - `balanceOf(address account)`: Returns the current token balance of the specified `account`. - On the destination blockchain, The token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool. If you don't have an existing token: For all blockchains where tokens need to be burned and minted (for example, the source or destination chain in the case of Burn and Mint, or the destination blockchain in the case of Lock and Mint), Chainlink provides a [BurnMintERC677](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.1/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol) contract that you can use to deploy your token in minutes. This token follows the [ERC677](https://github.com/ethereum/EIPs/issues/677) or [ERC777](https://ethereum.org/en/developers/docs/standards/tokens/erc-777/), allowing you to use it as-is or extend it to meet your specific requirements. ### Understanding the Procedure It is also important first to understand the overall procedure for enabling your tokens in CCIP. This procedure involves deploying tokens and token pools, registering administrative roles, and configuring token pools to enable secure token transfers using CCIP. The steps in the diagram below highlight the flow of actions needed to enable a token for cross-chain transfers. Whether you're working with an Externally Owned Account (EOA) or a **Smart Account** (such as one using a multisig scheme), the overall logic remains the same. You'll follow the same process to enable cross-chain token transfers, configure pools, and register administrative roles. The diagram below outlines the entire process: ## Before You Begin 1. **Install Foundry**: If you haven't already, follow the instructions in the [Foundry documentation](https://book.getfoundry.sh/getting-started/installation) to install Foundry. 2. **Create new Foundry project**: Create a new Foundry project by running the following command: ```bash forge init ``` 3. **Set up your environment**: Create a `.env` file, and fill in the required values: Example `.env` file: ```bash ETHEREUM_SEPOLIA_RPC_URL= BASE_SEPOLIA_RPC_URL= ``` ## Create the `owner()` ERC-20 token Inside the `test` folder create the new Solidity file and name it `CCIPv1_5LockReleasePoolFork.t.sol`. We will use only this file through out the rest of this guide. Create the CCT-compatible ERC-20 token with `owner()` function. ```solidity // test/CCIPv1_5LockReleasePoolFork.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { ERC20, IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; import { OwnerIsCreator } from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; contract MockERC20TokenOwner is ERC20, OwnerIsCreator { constructor() ERC20("MockERC20Token", "MTK") {} function mint(address account, uint256 amount) public onlyOwner { _mint(account, amount); } } ``` ## Test the CCT enabling procedure Expand the existing `CCIPv1_5LockReleasePoolFork.t.sol` file to create and set up our basic test. ```solidity // test/CCIPv1_5LockReleasePoolFork.t.sol import { Test, Vm } from "forge-std/Test.sol"; import { CCIPLocalSimulatorFork, Register } from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; import { LockReleaseTokenPool, TokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/LockReleaseTokenPool.sol"; import { RegistryModuleOwnerCustom } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; import { TokenAdminRegistry } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol"; import { RateLimiter } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol"; import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; /** * The token code part from previous section goes here... */ contract CCIPv1_5LockReleasePoolFork is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; MockERC20TokenOwner public mockERC20TokenEthSepolia; MockERC20TokenOwner public mockERC20TokenBaseSepolia; LockReleaseTokenPool public lockReleaseTokenPoolEthSepolia; LockReleaseTokenPool public lockReleaseTokenPoolBaseSepolia; Register.NetworkDetails ethSepoliaNetworkDetails; Register.NetworkDetails baseSepoliaNetworkDetails; uint256 ethSepoliaFork; uint256 baseSepoliaFork; address alice; function setUp() public { alice = makeAddr("alice"); string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); string memory BASE_SEPOLIA_RPC_URL = vm.envString("BASE_SEPOLIA_RPC_URL"); ethSepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); baseSepoliaFork = vm.createFork(BASE_SEPOLIA_RPC_URL); ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); } } ``` #### Step 1) Deploy token on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function setUp() public { // Code from previous section goes here... // Step 1) Deploy token on Ethereum Sepolia vm.startPrank(alice); mockERC20TokenEthSepolia = new MockERC20TokenOwner(); vm.stopPrank(); } } ``` #### Step 2) Deploy token on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function setUp() public { // Code from previous section goes here... // Step 2) Deploy token on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia = new MockERC20TokenOwner(); vm.stopPrank(); } } ``` #### Step 3) Deploy LockReleaseTokenPool on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { // Code from previous section goes here... function test_forkSupportNewCCIPToken() public { // Step 3) Deploy LockReleaseTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); address[] memory allowlist = new address[](0); uint8 localTokenDecimals = 18; vm.startPrank(alice); lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool( IERC20(address(mockERC20TokenEthSepolia)), localTokenDecimals, allowlist, ethSepoliaNetworkDetails.rmnProxyAddress, true, // acceptLiquidity ethSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); } } ``` #### Step 4) Deploy LockReleaseTokenPool on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 4) Deploy LockReleaseTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); vm.startPrank(alice); lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool( IERC20(address(mockERC20TokenBaseSepolia)), localTokenDecimals, allowlist, baseSepoliaNetworkDetails.rmnProxyAddress, true, // acceptLiquidity baseSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); } } ``` #### Step 5) Set the LiquidityManager address and Add liquidity to the pool on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 5) Set the LiquidityManager address and Add liquidity to the pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); uint256 amountToMint = 1_000_000; uint128 liquidityAmount = 100_000; vm.startPrank(alice); mockERC20TokenEthSepolia.mint(address(alice), amountToMint); mockERC20TokenEthSepolia.approve(address(lockReleaseTokenPoolEthSepolia), liquidityAmount); lockReleaseTokenPoolEthSepolia.setRebalancer(address(alice)); lockReleaseTokenPoolEthSepolia.provideLiquidity(liquidityAmount); vm.stopPrank(); } } ``` #### Step 6) Set the LiquidityManager address and Add liquidity to the pool on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 6) Set the LiquidityManager address and Add liquidity to the pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia.mint(address(alice), amountToMint); mockERC20TokenBaseSepolia.approve(address(lockReleaseTokenPoolBaseSepolia), liquidityAmount); lockReleaseTokenPoolBaseSepolia.setRebalancer(address(alice)); lockReleaseTokenPoolBaseSepolia.provideLiquidity(liquidityAmount); vm.stopPrank(); } } ``` #### Step 7) Claim Admin role on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 7) Claim Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomEthSepolia = RegistryModuleOwnerCustom( ethSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomEthSepolia.registerAdminViaOwner(address(mockERC20TokenEthSepolia)); vm.stopPrank(); } } ``` #### Step 8) Claim Admin role on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 8) Claim Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomBaseSepolia = RegistryModuleOwnerCustom( baseSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomBaseSepolia.registerAdminViaOwner(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); } } ``` #### Step 9) Accept Admin role on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 9) Accept Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); TokenAdminRegistry tokenAdminRegistryEthSepolia = TokenAdminRegistry( ethSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryEthSepolia.acceptAdminRole(address(mockERC20TokenEthSepolia)); vm.stopPrank(); } } ``` #### Step 10) Accept Admin role on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 10) Accept Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); TokenAdminRegistry tokenAdminRegistryBaseSepolia = TokenAdminRegistry( baseSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.acceptAdminRole(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); } } ``` #### Step 11) Link token to pool on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 11) Link token to pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); tokenAdminRegistryEthSepolia.setPool(address(mockERC20TokenEthSepolia), address(lockReleaseTokenPoolEthSepolia)); vm.stopPrank(); } } ``` #### Step 12) Link token to pool on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 12) Link token to pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.setPool(address(mockERC20TokenBaseSepolia), address(lockReleaseTokenPoolBaseSepolia)); vm.stopPrank(); } } ``` #### Step 13) Configure Token Pool on Ethereum Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 13) Configure Token Pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1); remotePoolAddressesEthSepolia[0] = abi.encode(address(lockReleaseTokenPoolBaseSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: baseSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesEthSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }) }); uint64[] memory remoteChainSelectorsToRemove = new uint64[](0); lockReleaseTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); } } ``` #### Step 14) Configure Token Pool on Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 14) Configure Token Pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1); remotePoolAddressesBaseSepolia[0] = abi.encode(address(lockReleaseTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: ethSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesBaseSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }) }); lockReleaseTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); } } ``` #### Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia ```solidity contract CCIPv1_5LockReleasePoolFork is Test { function test_forkSupportNewCCIPToken() public { // Code from previous section goes here... // Step 15) Transfer tokens from Ethereum Sepolia to Base Sepolia vm.selectFork(ethSepoliaFork); address linkEthSepoliaAddress = ethSepoliaNetworkDetails.linkAddress; address routerEthSepoliaAddress = ethSepoliaNetworkDetails.routerAddress; ccipLocalSimulatorFork.requestLinkFromFaucet(address(alice), 20 ether); uint256 amountToSend = 100; Client.EVMTokenAmount[] memory tokenToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ token: address(mockERC20TokenEthSepolia), amount: amountToSend }); tokenToSendDetails[0] = tokenAmount; vm.startPrank(alice); mockERC20TokenEthSepolia.approve(routerEthSepoliaAddress, amountToSend); IERC20(linkEthSepoliaAddress).approve(routerEthSepoliaAddress, 20 ether); uint256 balanceOfAliceBeforeEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); uint64 destinationChainSelector = baseSepoliaNetworkDetails.chainSelector; IRouterClient routerEthSepolia = IRouterClient(routerEthSepoliaAddress); routerEthSepolia.ccipSend( destinationChainSelector, Client.EVM2AnyMessage({ receiver: abi.encode(address(alice)), data: "", tokenAmounts: tokenToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({ gasLimit: 0 })), feeToken: linkEthSepoliaAddress }) ); uint256 balanceOfAliceAfterEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); vm.stopPrank(); assertEq(balanceOfAliceAfterEthSepolia, balanceOfAliceBeforeEthSepolia - amountToSend); ccipLocalSimulatorFork.switchChainAndRouteMessage(baseSepoliaFork); uint256 balanceOfAliceAfterBaseSepolia = mockERC20TokenBaseSepolia.balanceOf(alice); assertEq(balanceOfAliceAfterBaseSepolia, balanceOfAliceBeforeEthSepolia + amountToSend); } } ``` ## Final code - full example ```solidity // test/CCIPv1_5LockReleasePoolFork.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Test, Vm } from "forge-std/Test.sol"; import { CCIPLocalSimulatorFork, Register } from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; import { LockReleaseTokenPool, TokenPool } from "@chainlink/contracts-ccip/src/v0.8/ccip/pools/LockReleaseTokenPool.sol"; import { RegistryModuleOwnerCustom } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; import { TokenAdminRegistry } from "@chainlink/contracts-ccip/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol"; import { RateLimiter } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol"; import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; import { ERC20, IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; import { OwnerIsCreator } from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; contract MockERC20TokenOwner is ERC20, OwnerIsCreator { constructor() ERC20("MockERC20Token", "MTK") {} function mint(address account, uint256 amount) public onlyOwner { _mint(account, amount); } } contract CCIPv1_5LockReleasePoolFork is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; MockERC20TokenOwner public mockERC20TokenEthSepolia; MockERC20TokenOwner public mockERC20TokenBaseSepolia; LockReleaseTokenPool public lockReleaseTokenPoolEthSepolia; LockReleaseTokenPool public lockReleaseTokenPoolBaseSepolia; Register.NetworkDetails ethSepoliaNetworkDetails; Register.NetworkDetails baseSepoliaNetworkDetails; uint256 ethSepoliaFork; uint256 baseSepoliaFork; address alice; function setUp() public { alice = makeAddr("alice"); string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); string memory BASE_SEPOLIA_RPC_URL = vm.envString("BASE_SEPOLIA_RPC_URL"); ethSepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); baseSepoliaFork = vm.createFork(BASE_SEPOLIA_RPC_URL); ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); // Step 1) Deploy token on Ethereum Sepolia vm.startPrank(alice); mockERC20TokenEthSepolia = new MockERC20TokenOwner(); vm.stopPrank(); // Step 2) Deploy token on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia = new MockERC20TokenOwner(); vm.stopPrank(); } function test_forkSupportNewCCIPToken() public { // Step 3) Deploy LockReleaseTokenPool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); address[] memory allowlist = new address[](0); uint8 localTokenDecimals = 18; vm.startPrank(alice); lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool( IERC20(address(mockERC20TokenEthSepolia)), localTokenDecimals, allowlist, ethSepoliaNetworkDetails.rmnProxyAddress, true, // acceptLiquidity ethSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); // Step 4) Deploy LockReleaseTokenPool on Base Sepolia vm.selectFork(baseSepoliaFork); baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); vm.startPrank(alice); lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool( IERC20(address(mockERC20TokenBaseSepolia)), localTokenDecimals, allowlist, baseSepoliaNetworkDetails.rmnProxyAddress, true, // acceptLiquidity baseSepoliaNetworkDetails.routerAddress ); vm.stopPrank(); // Step 5) Set the LiquidityManager address and Add liquidity to the pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); uint256 amountToMint = 1_000_000; uint128 liquidityAmount = 100_000; vm.startPrank(alice); mockERC20TokenEthSepolia.mint(address(alice), amountToMint); mockERC20TokenEthSepolia.approve(address(lockReleaseTokenPoolEthSepolia), liquidityAmount); lockReleaseTokenPoolEthSepolia.setRebalancer(address(alice)); lockReleaseTokenPoolEthSepolia.provideLiquidity(liquidityAmount); vm.stopPrank(); // Step 6) Set the LiquidityManager address and Add liquidity to the pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); mockERC20TokenBaseSepolia.mint(address(alice), amountToMint); mockERC20TokenBaseSepolia.approve(address(lockReleaseTokenPoolBaseSepolia), liquidityAmount); lockReleaseTokenPoolBaseSepolia.setRebalancer(address(alice)); lockReleaseTokenPoolBaseSepolia.provideLiquidity(liquidityAmount); vm.stopPrank(); // Step 7) Claim Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomEthSepolia = RegistryModuleOwnerCustom( ethSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomEthSepolia.registerAdminViaOwner(address(mockERC20TokenEthSepolia)); vm.stopPrank(); // Step 8) Claim Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); RegistryModuleOwnerCustom registryModuleOwnerCustomBaseSepolia = RegistryModuleOwnerCustom( baseSepoliaNetworkDetails.registryModuleOwnerCustomAddress ); vm.startPrank(alice); registryModuleOwnerCustomBaseSepolia.registerAdminViaOwner(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); // Step 9) Accept Admin role on Ethereum Sepolia vm.selectFork(ethSepoliaFork); TokenAdminRegistry tokenAdminRegistryEthSepolia = TokenAdminRegistry( ethSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryEthSepolia.acceptAdminRole(address(mockERC20TokenEthSepolia)); vm.stopPrank(); // Step 10) Accept Admin role on Base Sepolia vm.selectFork(baseSepoliaFork); TokenAdminRegistry tokenAdminRegistryBaseSepolia = TokenAdminRegistry( baseSepoliaNetworkDetails.tokenAdminRegistryAddress ); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.acceptAdminRole(address(mockERC20TokenBaseSepolia)); vm.stopPrank(); // Step 11) Link token to pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); tokenAdminRegistryEthSepolia.setPool(address(mockERC20TokenEthSepolia), address(lockReleaseTokenPoolEthSepolia)); vm.stopPrank(); // Step 12) Link token to pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); tokenAdminRegistryBaseSepolia.setPool(address(mockERC20TokenBaseSepolia), address(lockReleaseTokenPoolBaseSepolia)); vm.stopPrank(); // Step 13) Configure Token Pool on Ethereum Sepolia vm.selectFork(ethSepoliaFork); vm.startPrank(alice); TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1); remotePoolAddressesEthSepolia[0] = abi.encode(address(lockReleaseTokenPoolBaseSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: baseSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesEthSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }) }); uint64[] memory remoteChainSelectorsToRemove = new uint64[](0); lockReleaseTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); // Step 14) Configure Token Pool on Base Sepolia vm.selectFork(baseSepoliaFork); vm.startPrank(alice); chains = new TokenPool.ChainUpdate[](1); bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1); remotePoolAddressesBaseSepolia[0] = abi.encode(address(lockReleaseTokenPoolEthSepolia)); chains[0] = TokenPool.ChainUpdate({ remoteChainSelector: ethSepoliaNetworkDetails.chainSelector, remotePoolAddresses: remotePoolAddressesBaseSepolia, remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)), outboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }), inboundRateLimiterConfig: RateLimiter.Config({ isEnabled: true, capacity: liquidityAmount, rate: 167 }) }); lockReleaseTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains); vm.stopPrank(); // Step 15) Transfer tokens from Ethereum Sepolia to Base Sepolia vm.selectFork(ethSepoliaFork); address linkEthSepoliaAddress = ethSepoliaNetworkDetails.linkAddress; address routerEthSepoliaAddress = ethSepoliaNetworkDetails.routerAddress; ccipLocalSimulatorFork.requestLinkFromFaucet(address(alice), 20 ether); uint256 amountToSend = 100; Client.EVMTokenAmount[] memory tokenToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ token: address(mockERC20TokenEthSepolia), amount: amountToSend }); tokenToSendDetails[0] = tokenAmount; vm.startPrank(alice); mockERC20TokenEthSepolia.approve(routerEthSepoliaAddress, amountToSend); IERC20(linkEthSepoliaAddress).approve(routerEthSepoliaAddress, 20 ether); uint256 balanceOfAliceBeforeEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); uint64 destinationChainSelector = baseSepoliaNetworkDetails.chainSelector; IRouterClient routerEthSepolia = IRouterClient(routerEthSepoliaAddress); routerEthSepolia.ccipSend( destinationChainSelector, Client.EVM2AnyMessage({ receiver: abi.encode(address(alice)), data: "", tokenAmounts: tokenToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({ gasLimit: 0 })), feeToken: linkEthSepoliaAddress }) ); uint256 balanceOfAliceAfterEthSepolia = mockERC20TokenEthSepolia.balanceOf(alice); vm.stopPrank(); assertEq(balanceOfAliceAfterEthSepolia, balanceOfAliceBeforeEthSepolia - amountToSend); ccipLocalSimulatorFork.switchChainAndRouteMessage(baseSepoliaFork); uint256 balanceOfAliceAfterBaseSepolia = mockERC20TokenBaseSepolia.balanceOf(alice); assertEq(balanceOfAliceAfterBaseSepolia, balanceOfAliceBeforeEthSepolia + amountToSend); } } ``` --- # Using the CCIP Local Simulator to fork mainnets Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/forking-mainnets This guide explains how to use the CCIP Local Simulator to test cross-chain transactions in forked mainnet environments using Foundry. You'll learn how to leverage the `Register.sol` contract to streamline the simulation process and properly configure mainnet details that aren't included by default. ## Understanding Network Registration The `Register.sol` smart contract contains pre-configured details for test networks to help accelerate cross-chain transaction simulations in Foundry. However, mainnet details are intentionally excluded and must be manually configured. Here's an example of how to verify network details for a test network such as Sepolia: ```solidity pragma solidity ^0.8.0; import { Test, console } from "forge-std/Test.sol" import { CCIPLocalSimulatorFork, Register } from "@chainlink/local/src/ccip/CCIPLocalSimulatorFork.sol" contract ExampleTest is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; uint256 public ethSepoliaFork; Register.NetworkDetails public ethSepoliaNetworkDetails; function setUp() public { // Create a fork of the Sepolia network string memory SEPOLIA_RPC_URL = vm.envString("SEPOLIA_RPC_URL"); ethSepoliaFork = vm.createFork(SEPOLIA_RPC_URL); // Initialize the CCIP simulator ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); // Select and verify the fork vm.selectFork(ethSepoliaFork); assertEq(block.chainid, 11155111); // Verify network details match the CCIP Directory ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); assertEq(ethSepoliaNetworkDetails.chainSelector, 16015286601757825753); assertEq(ethSepoliaNetworkDetails.routerAddress, 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59); } } ``` ## Working with Mainnet Forks The CCIP Local Simulator supports any forked environment where CCIP contracts exist. Since mainnet details aren't included in `Register.sol`, you'll need to configure them manually. Let's walk through a complete example that demonstrates how to simulate cross-chain messages between Ethereum and Polygon mainnets. This example shows: 1. Setting up mainnet forks 2. Configuring network details 3. Creating and sending a cross-chain message 4. Routing the message between chains Here's the complete implementation: ```solidity pragma solidity ^0.8.0; import { Test, console } from "forge-std/Test.sol"; import { CCIPLocalSimulatorFork, Register } from "@chainlink/local/src/ccip/CCIPLocalSimulatorFork.sol"; import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; contract ExampleTest is Test { CCIPLocalSimulatorFork public ccipLocalSimulatorFork; uint256 public ethereumMainnetForkId; uint256 public polygonMainnetForkId; function setUp() public { // Create forks of both networks string memory ETHEREUM_MAINNET_RPC_URL = vm.envString("ETHEREUM_MAINNET_RPC_URL"); string memory POLYGON_MAINNET_RPC_URL = vm.envString("POLYGON_MAINNET_RPC_URL"); ethereumMainnetForkId = vm.createFork(ETHEREUM_MAINNET_RPC_URL); polygonMainnetForkId = vm.createFork(POLYGON_MAINNET_RPC_URL); address ethereumMainnetCcipRouterAddress = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D; uint64 polygonMainnetChainSelector = 4051577828743386545; ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); ccipLocalSimulatorFork.setNetworkDetails( polygonMainnetForkId, Register.NetworkDetails({ chainSelector: polygonMainnetChainSelector, routerAddress: polygonMainnetCcipRouterAddress, linkAddress: address(0), // not needed for this test wrappedNativeAddress: address(0), // not needed for this test ccipBnMAddress: address(0), // not needed for this test ccipLnMAddress: address(0), // not needed for this test rmnProxyAddress: address(0), // not needed for this test registryModuleOwnerCustomAddress: address(0), // not needed for this test tokenAdminRegistryAddress: address(0) // not needed for this test }) ); vm.makePersistent(address(ccipLocalSimulatorFork)); } function test_example() public { // Set up the source chain (Ethereum) vm.selectFork(ethereumMainnetForkId); Register.NetworkDetails memory polygonMainnetNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails( polygonMainnetForkId ); address alice = makeAddr("alice"); vm.deal(alice, 1 ether); // Prepare the cross-chain message vm.startPrank(alice); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(ccipReceiverAddress), data: abi.encode("Hello world"), tokenAmounts: new Client.EVMTokenAmount[](0), extraArgs: "", feeToken: address(0) }); // Send the message using CCIP IRouterClient(ethereumMainnetCcipRouterAddress).ccipSend(polygonMainnetNetworkDetails.routerAddress, message); vm.stopPrank(); // Route the message to Polygon ccipLocalSimulatorFork.switchChainAndRouteMessage(polygonMainnetForkId); } } ``` **Code explanation**: - **Network Setup**: The `setUp()` function creates forks of both Ethereum and Polygon mainnets and initializes the CCIP simulator. - **Network Configuration**: We configure Polygon mainnet details using [`setNetworkDetails()`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork#setnetworkdetails). This step is crucial because mainnet details aren't included in `Register.sol`. The configuration includes: - `chainSelector`: The unique CCIP identifier for the Polygon network. Check the [CCIP Directory](/ccip/directory/mainnet) for the correct value. - `routerAddress`: The address of the CCIP router on Polygon. Check the [CCIP Directory](/ccip/directory/mainnet) for the correct value. - Parameters that can be set to `address(0)` because they are optional for messaging. - **Message Transfer**: The `test_example()` function demonstrates: - Setting up a test user (alice) with funds - Creating a cross-chain message - Sending the message through CCIP - Routing the message to the destination chain After this configuration, you can simulate cross-chain messages between mainnet forks, enabling thorough testing of your cross-chain applications in a local environment. --- # Using Chainlink Local to Test CCIP in Your Foundry Project Source: https://docs.chain.link/chainlink-local/build/ccip/foundry There are two comprehensive guides to help you test Chainlink CCIP in your Foundry project using Chainlink Local - one for each mode (with and without forking). We recommend starting with the first guide (without forking) for initial development before advancing to the guide that demonstrates how to test in forked environments. ## Guides ### Using the CCIP Local Simulator in your Foundry project This [guide](/chainlink-local/build/ccip/foundry/local-simulator) helps you set up and run CCIP in a localhost environment within your Foundry project. It provides step-by-step instructions on: - Cloning the [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry) - Installing necessary dependencies - Running tests for token transfers between two accounts Using the local simulator without forking is ideal for initial development and testing in an isolated environment. ### Using the CCIP Local Simulator in your Foundry project with forked environments This [guide](/chainlink-local/build/ccip/foundry/local-simulator-fork) extends your testing capabilities by demonstrating how to use the CCIP Local Simulator in a forked environment. It includes instructions on: - Setting up forks of real blockchain networks (such as Arbitrum Sepolia and Ethereum Sepolia) - Writing test cases - Running tests for cross-chain token transfers between two accounts Using the local simulator with forked environments is ideal for testing in a more realistic yet controlled setting. --- # Using the CCIP Local Simulator in your Foundry project with forked environments Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/local-simulator-fork You can use Chainlink Local to run CCIP in forked environments within your Foundry project. To get started quickly, you will use the [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry). This project includes the `Example01ForkTest` file located in the `./test/fork` directory, demonstrating how to set up and run token transfer tests between two accounts using CCIP in forked environments. Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains. In this example, you will fork *Arbitrum Sepolia* and *Ethereum Sepolia*. ## Prerequisites This guide assumes that you are familiar with the guide [Using the CCIP Local Simulator in your Foundry project](/chainlink-local/build/ccip/foundry/local-simulator). If not, please get familiar with it and run all the prerequisites. Set up an `.env` file with the following data: - `ARBITRUM_SEPOLIA_RPC_URL`: The Remote Procedure Call (RPC) URL for the Arbitrum Sepolia network. You can obtain one by creating an account on [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/) and setting up an Arbitrum Sepolia project. - `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL for the Ethereum Sepolia testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service. ## Test tokens transfers You will run a test to transfer tokens between two accounts in a forked environment. The test file `Example01ForkTest.t.sol` is located in the `./test/fork` directory. This file contains two test cases: 1. **Transfer with LINK fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in LINK. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. 2. **Transfer with native gas fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in native gas. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. In these tests, we simulate transfers from a source blockchain (which is a fork of *Arbitrum Sepolia*) to a destination blockchain (which is a fork of *Ethereum Sepolia*). Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains, providing a realistic testing scenario. For a detailed explanation of the test file, refer to the [Examine the code](#examine-the-code) section. In your terminal, run the following command to execute the test: ```shell forge test --match-contract Example01ForkTest ``` Example output: ```text $ forge test --match-contract Example01ForkTest [⠊] Compiling... No files changed, compilation skipped Ran 2 tests for test/fork/Example01Fork.t.sol:Example01ForkTest [PASS] test_transferTokensFromEoaToEoaPayFeesInLink() (gas: 475199) [PASS] test_transferTokensFromEoaToEoaPayFeesInNative() (gas: 451096) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.71s (17.29s CPU time) Ran 1 test suite in 11.90s (11.71s CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) ``` ## Examine the code ### Setup To transfer tokens using CCIP in a forked environment, we need the following: - Destination chain selector - Source CCIP router - LINK token for paying CCIP fees - A test token contract (such as CCIP-BnM) on both source and destination chains - A sender account (Alice) - A receiver account (Bob) The `setUp()` function is invoked before each test case to reinitialize all the variables, ensuring a clean state for each test: 1. Initialize the source and destination forks: ```solidity string memory DESTINATION_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); string memory SOURCE_RPC_URL = vm.envString("ARBITRUM_SEPOLIA_RPC_URL"); destinationFork = vm.createSelectFork(DESTINATION_RPC_URL); sourceFork = vm.createFork(SOURCE_RPC_URL); ``` 2. Initialize the sender and receiver accounts: ```solidity bob = makeAddr("bob"); alice = makeAddr("alice"); ``` 3. Initialize the [fork CCIP local simulator](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork#cciplocalsimulatorfork). **Note**: `vm.makePersistent` is used to make the `ccipLocalSimulatorFork` address persistent across forks: ```solidity ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); vm.makePersistent(address(ccipLocalSimulatorFork)); ``` 4. Retrieve and set up the network details for the destination chain. **Note**: [`Register.NetworkDetails`](/chainlink-local/api-reference/v0.2.3/register#networkdetails) is a struct that stores network details (such as chain selector, router address, link address, wrapped native address, or CCIP test tokens), and [`getNetworkDetails`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork#getnetworkdetails) pulls network details based on chain IDs: ```solidity Register.NetworkDetails memory destinationNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); destinationCCIPBnMToken = BurnMintERC677Helper(destinationNetworkDetails.ccipBnMAddress); destinationChainSelector = destinationNetworkDetails.chainSelector; ``` 5. Switch to the source fork and retrieve the network details for the source chain: ```solidity vm.selectFork(sourceFork); Register.NetworkDetails memory sourceNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); sourceCCIPBnMToken = BurnMintERC677Helper(sourceNetworkDetails.ccipBnMAddress); sourceLinkToken = IERC20(sourceNetworkDetails.linkAddress); sourceRouter = IRouterClient(sourceNetworkDetails.routerAddress); ``` 6. All the variables are stored in the contract state for use in the test cases. ### Prepare scenario (helper function) The `prepareScenario()` function is invoked at the beginning of each test case. It performs the following actions: 1. Select the source fork and request CCIP-BnM tokens for Alice: ```solidity vm.selectFork(sourceFork); vm.startPrank(alice); sourceCCIPBnMToken.drip(alice); ``` 2. Approve the source router to spend tokens on behalf of Alice: ```solidity uint256 amountToSend = 100; sourceCCIPBnMToken.approve(address(sourceRouter), amountToSend); ``` 3. Create an array [Client.EVMTokenAmount](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount)[] to specify the token transfer details. This array and the amount to send are returned by the `prepareScenario()` function for use in the calling test case: ```solidity tokensToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenToSendDetails = Client.EVMTokenAmount({token: address(ccipBnMToken), amount: amountToSend}); tokensToSendDetails[0] = tokenToSendDetails; ``` 4. Stop impersonating Alice (sender): ```solidity vm.stopPrank(); ``` ### Test case 1: Transfer with LINK fees The `test_transferTokensFromEoaToEoaPayFeesInLink` function tests the transfer of tokens between two externally owned accounts (EOA) while paying fees in LINK. Here are the steps involved in this test case: 1. Invoke the `prepareScenario()` function to set up the necessary variables: ```solidity (Client.EVMTokenAmount[] memory tokensToSendDetails, uint256 amountToSend) = prepareScenario(); ``` 2. Select the destination fork and record the initial token balance of Bob receiver: ```solidity vm.selectFork(destinationFork); uint256 balanceOfBobBefore = destinationCCIPBnMToken.balanceOf(bob); ``` 3. Select the source fork and record the initial token balance of Alice (sender): ```solidity vm.selectFork(sourceFork); uint256 balanceOfAliceBefore = sourceCCIPBnMToken.balanceOf(alice); ``` 4. Request 10 LINK tokens from the CCIP local simulator faucet for Alice (sender): ```solidity ccipLocalSimulatorFork.requestLinkFromFaucet(alice, 10 ether); ``` 5. Construct the [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) structure with the receiver, token amounts, and other necessary details. - Set the `data` parameter to an empty string because you are not sending any arbitrary data, only tokens. - In `extraArgs`, set the gas limit to `0`. This gas limit is for execution of receiver logic, which doesn't apply here because you're sending tokens to an EOA. ```solidity Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(bob), data: abi.encode(""), tokenAmounts: tokensToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0})), feeToken: address(sourceLinkToken) }); ``` 6. Calculate the required fees for the transfer and approve the router to spend LINK tokens for these fees: ```solidity uint256 fees = sourceRouter.getFee(destinationChainSelector, message); sourceLinkToken.approve(address(sourceRouter), fees); ``` 7. Send the CCIP transfer request to the router: ```solidity sourceRouter.ccipSend(destinationChainSelector, message); ``` 8. Stop impersonating Alice (sender): ```solidity vm.stopPrank(); ``` 9. Record Alice's final token balance and verify that it has decreased by the amount sent: ```solidity uint256 balanceOfAliceAfter = sourceCCIPBnMToken.balanceOf(alice); assertEq(balanceOfAliceAfter, balanceOfAliceBefore - amountToSend); ``` 10. Call the [`switchChainAndRouteMessage`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork#switchchainandroutemessage) function to switch to the destination fork and route the message to complete the transfer: ```solidity ccipLocalSimulatorFork.switchChainAndRouteMessage(destinationFork); ``` 11. Record Bob's final token balance and verify that it has increased by the amount sent: ```solidity uint256 balanceOfBobAfter = destinationCCIPBnMToken.balanceOf(bob); assertEq(balanceOfBobAfter, balanceOfBobBefore + amountToSend); ``` ### Test case 2: Transfer with native gas fees The `test_transferTokensFromEoaToEoaPayFeesInNative` function tests the transfer of tokens between two externally owned accounts (EOA) while paying fees in native gas. Here are the steps involved in this test case: 1. Invoke the `prepareScenario()` function to set up the necessary variables. (This function is the same as in the previous test case.) 2. Select the destination fork and record Bob's initial token balance. (This step is the same as in the previous test case.) 3. Select the source fork and record Alice's initial token balance. (This step is the same as in the previous test case.) 4. Begin impersonating Alice (sender) and provide her with native gas: ```solidity vm.startPrank(alice); deal(alice, 5 ether); ``` 5. Construct the [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) structure. This step is the same as in the previous test case. The main difference is that the `feeToken` is set with `address(0)` to indicate that the fees are paid in native gas: ```solidity Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(bob), data: abi.encode(""), tokenAmounts: tokensToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0})), feeToken: address(0) }); ``` 6. Calculate the required fees for the transfer and send the CCIP transfer request along with the necessary native gas: ```solidity uint256 fees = sourceRouter.getFee(destinationChainSelector, message); sourceRouter.ccipSend{value: fees}(destinationChainSelector, message, fees); ``` 7. Stop impersonating Alice (sender). (This step is the same as in the previous test case.) 8. Verify Alice's (sender) balance. (This step is the same as in the previous test case.) 9. Call the [`switchChainAndRouteMessage`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork#switchchainandroutemessage) function to switch to the destination fork and route the message to complete the transfer. (This step is the same as in the previous test case.) 10. Verify Bob's (receiver) balance. (This step is the same as in the previous test case.) --- # Using CCIP local simulator in your Foundry project Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/local-simulator You can use Chainlink Local to run CCIP in a localhost environment within your Foundry project. To get started quickly, you will use the [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry). This project is a Foundry boilerplate that includes the Chainlink Local package and several CCIP examples. ## Prerequisites 1. In a terminal, clone the [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry) repository and change directories. ```shell git clone https://github.com/smartcontractkit/ccip-starter-kit-foundry.git && \ cd ./ccip-starter-kit-foundry/ ``` 2. Run `npm install` to install the dependencies. This command installs the [Chainlink Local](https://github.com/smartcontractkit/chainlink-local) package and other required packages: ```shell npm install ``` 3. Run `forge install` to install packages: ```shell forge install ``` 4. Run `forge build` to compile the contracts: ```shell forge build ``` ## Test tokens transfers You will run a test to transfer tokens between two accounts. The test file `Example01.t.sol` is located in the `./test/no-fork` directory. This file contains two test cases: 1. **Transfer with LINK fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in LINK. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. 2. **Transfer with native gas fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in native gas. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. For a detailed explanation of the test file, refer to the [Examine the code](#examine-the-code) section. In your terminal, run the following command to execute the test: ```shell forge test --match-contract Example01Test ``` Example output: ```text $ forge test --match-contract Example01Test [⠊] Compiling... No files changed, compilation skipped Ran 2 tests for test/no-fork/Example01.t.sol:Example01Test [PASS] test_transferTokensFromEoaToEoaPayFeesInLink() (gas: 167576) [PASS] test_transferTokensFromEoaToEoaPayFeesInNative() (gas: 122348) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.79ms (976.54µs CPU time) Ran 1 test suite in 201.00ms (8.79ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) ``` ## Examine the code ### Setup To transfer tokens using CCIP, we need the following: - Destination chain selector - Source CCIP router - LINK token for paying CCIP fees - A test token contract (such as CCIP-BnM) - A sender account (Alice) - A receiver account (Bob) The `setUp()` function is invoked before each test case to reinitialize all the variables, ensuring a clean state for each test: 1. Initialize the [CCIP local simulator](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#cciplocalsimulator) contract: ```solidity ccipLocalSimulator = new CCIPLocalSimulator(); ``` 2. Invoke the [`configuration`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#configuration) function to retrieve the configuration details for the pre-deployed contracts and services needed for local CCIP simulations: ```solidity ( uint64 chainSelector, IRouterClient sourceRouter, , , LinkToken linkToken, BurnMintERC677Helper ccipBnM, ) = ccipLocalSimulator.configuration(); ``` **Note**: The [`configuration`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#configuration) function also returns the destination router, WETH9, and CCIP-LnM contracts, but we are not using them in these test cases. Hence, there are commas in the return values. 3. Initialize the sender and receiver accounts: ```solidity alice = makeAddr("alice") bob = makeAddr("bob") ``` 4. All the variables are stored in the contract state for use in the test cases. ### Prepare scenario (helper function) The `prepareScenario()` function is invoked at the beginning of each test case. It performs the following actions: 1. Request CCIP-BnM tokens for Alice (sender): ```solidity ccipBnMToken.drip(alice); ``` 2. Approve the source router to spend tokens on behalf of Alice (sender): ```solidity amountToSend = 100; ccipBnMToken.approve(address(router), amountToSend); ``` 3. Create an array [Client.EVMTokenAmount](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount)[] to specify the token transfer details. This array and the amount to send are returned by the prepareScenario() function for use in the calling test case: ```solidity tokensToSendDetails = new Client.EVMTokenAmount[](1); Client.EVMTokenAmount memory tokenToSendDetails = Client.EVMTokenAmount({token: address(ccipBnMToken), amount: amountToSend}); tokensToSendDetails[0] = tokenToSendDetails; ``` ### Test case 1: Transfer with LINK fees The `test_transferTokensFromEoaToEoaPayFeesInLink` function tests the transfer of tokens between two externally owned accounts (EOA) while paying fees in LINK. Here are the steps involved in this test case: 1. Invoke the `prepareScenario()` function to set up the necessary variables: ```solidity (Client.EVMTokenAmount[] memory tokensToSendDetails, uint256 amountToSend) = prepareScenario(); ``` 2. Record the initial token balances for Alice (sender) and Bob (receiver): ```solidity uint256 balanceOfAliceBefore = ccipBnMToken.balanceOf(alice); uint256 balanceOfBobBefore = ccipBnMToken.balanceOf(bob); ``` 3. Begin impersonating Alice (sender) to perform the subsequent actions: ```solidity vm.startPrank(alice); ``` 4. Request 5 LINK tokens from the CCIP Local Simulator faucet for Alice (sender): ```solidity ccipLocalSimulator.requestLinkFromFaucet(alice, 5 ether); ``` 5. Construct the [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) structure with the receiver, token amounts, and other necessary details. - Set the `data` parameter to an empty string because you are not sending any arbitrary data, only tokens. - In `extraArgs`, set the gas limit to `0`. This gas limit is for execution of receiver logic, which doesn't apply here because you're sending tokens to an EOA. ```solidity Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(bob), data: abi.encode(""), tokenAmounts: tokensToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0})), feeToken: address(linkToken) }); ``` 6. Calculate the required fees for the transfer and approve the router to spend LINK tokens for these fees: ```solidity uint256 fees = router.getFee(destinationChainSelector, message); linkToken.approve(address(router), fees); ``` 7. Send the CCIP transfer request to the router: ```solidity router.ccipSend(destinationChainSelector, message); ``` 8. Stop impersonating (sender): ```solidity vm.stopPrank(); ``` 9. Record the final token balances for Alice (sender) and Bob (receiver): ```solidity uint256 balanceOfAliceAfter = ccipBnMToken.balanceOf(alice); uint256 balanceOfBobAfter = ccipBnMToken.balanceOf(bob); ``` 10. Verify that Alice's balance has decreased by the amount sent and Bob's balance has increased by the same amount: ```solidity assertEq(balanceOfAliceAfter, balanceOfAliceBefore - amountToSend); assertEq(balanceOfBobAfter, balanceOfBobBefore + amountToSend); ``` ### Test case 2: Transfer with native gas fees The `test_transferTokensFromEoaToEoaPayFeesInNative` function tests the transfer of tokens between two externally owned accounts (EOA) while paying fees in native gas. Here are the steps involved in this test case: 1. Invoke the `prepareScenario()` function to set up the necessary variables. (This step is the same as in the previous test case.) 2. Record the initial token balances of Alice (sender) and Bob (receiver). (This step is the same as in the previous test case.) 3. Begin impersonating Alice (sender) and provide her with native gas to pay for the fees: ```solidity vm.startPrank(alice); deal(alice, 5 ether); ``` 4. Construct the [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) structure. This step is the same as in the previous test case. The main difference is that the `feeToken` is set with `address(0)` to indicate that the fees are paid in native gas: ```solidity Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(bob), data: abi.encode(""), tokenAmounts: tokensToSendDetails, extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0})), feeToken: address(0) }); ``` 5. Calculate the required fees for the transfer and send the CCIP transfer request along with the necessary native gas: ```solidity uint256 fees = router.getFee(destinationChainSelector, message); router.ccipSend{value: fees}(destinationChainSelector, message); ``` 6. Stop impersonating Alice (sender) and verify the token balances for Alice (sender) and Bob (receiver). (This step is the same as in the previous test case.) ## Next steps For more advanced scenarios, please refer to other test files in the `./test/no-fork` directory. To learn how to use Chainlink Local in forked environments, refer to the guide on [Using the CCIP Local Simulator in your Foundry project with forked environments](/chainlink-local/build/ccip/foundry/local-simulator-fork). --- # Using Chainlink Local to Test CCIP in Your Hardhat Project Source: https://docs.chain.link/chainlink-local/build/ccip/hardhat There are two comprehensive guides to help you test Chainlink CCIP in your Hardhat project using Chainlink Local - one for each mode (with and without forking). We recommend starting with the first guide (without forking) for initial development before advancing to the guide that demonstrates how to test in forked environments. ## Guides ### Using CCIP Local Simulator in your Hardhat project This [guide](/chainlink-local/build/ccip/hardhat/local-simulator) helps you set up and run CCIP in a localhost environment within your Hardhat project. It provides step-by-step instructions on : - Cloning the [CCIP Hardhat Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-hardhat) - Installing necessary dependencies - Running tests for token transfers between two accounts Using the local simulator without forking is ideal for initial development and testing in an isolated environment. ### Using CCIP Local Simulator in your Hardhat project with forked environments This [guide](/chainlink-local/build/ccip/hardhat/local-simulator-fork) extends your testing capabilities by demonstrating how to use the CCIP Local Simulator in a forked environment. It includes instructions on: - Setting up forks of real blockchain networks (e.g., *Arbitrum Sepolia* and *Ethereum Sepolia*) - Writing test cases - Running tests for cross-chain token transfers between two accounts Using the local simulator with forked environments is ideal for testing in a more realistic yet controlled setting. --- # Using CCIP Local Simulator in Your Hardhat Project - Forked Environments Source: https://docs.chain.link/chainlink-local/build/ccip/hardhat/local-simulator-fork You can use Chainlink Local to run CCIP in forked environments within your Hardhat project. To get started quickly, you will use the [CCIP Hardhat Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-hardhat). This project includes the `Example1.spec.tst` file located in the `./test/fork` directory, demonstrating how to set up and run token transfer tests between two accounts using CCIP in forked environments. Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains. In this example, we will fork *Arbitrum Sepolia* and *Ethereum Sepolia*. ## Prerequisites This guide assumes that you are familiar with the guide [Using CCIP Local Simulator in Your Hardhat Project](/chainlink-local/build/ccip/hardhat/local-simulator). If not, please get familiar with it and run all the prerequisites. Set an environment variable file. For higher security, the examples repository imports [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc). Use this tool to encrypt your environment variables at rest. 1. Set an encryption password for your environment variables. ```shell npx env-enc set-pw ``` 2. Run `npx env-enc set` to configure a `.env.enc` file with: - `ARBITRUM_SEPOLIA_RPC_URL`: The Remote Procedure Call (RPC) URL for the Arbitrum Sepolia network. You can obtain one by creating an account on [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/) and setting up an Arbitrum Sepolia project. - `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL for the Ethereum Sepolia testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service. ## Test tokens transfers You will run a test to transfer tokens between two accounts in a forked environment. The test file `Example1.spec.ts` is located in the `./test/fork` directory. This file contains one test case: **Transfer with LINK fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in LINK. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. In this test, we simulate a transfer of tokens from an Externally Owned Account (EOA) on a source blockchain (which is a fork of *Arbitrum Sepolia*) to an EOA on a destination blockchain (which is a fork of *Ethereum Sepolia*). Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains, providing a realistic testing scenario. For a detailed explanation of the test file, refer to the [Examine the code](#examine-the-code) section. In your terminal, run the following command to execute the test: ```shell npx hardhat test test/fork/Example1.spec.ts ``` Example output: ```text $ npx hardhat test test/fork/Example1.spec.ts Example 1 - Fork ✔ Should transfer CCIP test tokens from EOA to EOA (10889ms) 1 passing (11s) ``` ## Examine the code To transfer tokens using CCIP in a forked environment, we need the following: - Destination chain selector - Source CCIP router - LINK token for paying CCIP fees - A test token contract (such as CCIP-BnM) on both source and destination chains - A sender account (Alice) - A receiver account (Bob) The `it("Should transfer CCIP test tokens from EOA to EOA")` function sets up the necessary environment and runs the test. Here are the steps involved: 1. Initialize the sender and receiver accounts: ```typescript const [alice, bob] = await hre.ethers.getSigners() ``` 2. Set the source and destination chains: ```typescript const [source, destination] = ["ethereumSepolia", "arbitrumSepolia"] ``` 3. Retrieve the necessary addresses and configurations (such as the LINK token address, source router address, and so on). 4. Fork the source network: ```typescript await hre.network.provider.request({ method: "hardhat_reset", params: [ { forking: { jsonRpcUrl: getProviderRpcUrl(source), }, }, ], }) ``` 5. Connect to the source router and CCIP-BnM contracts. 6. Call `sourceCCIPBnM.drip` to request CCIP-BnM tokens for Alice (sender). 7. Approve the source router to spend tokens on behalf of Alice (sender). 8. Construct the `Client.EVM2AnyMessage` structure. This step is similar to the non fork example. 9. Call the source router to estimate the fees. 10. Call the [`requestLinkFromFaucet`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork-js#requestlinkfromthefaucet) function to request LINK tokens for Alice (sender). 11. Connect to the LINK contract and approve the LINK token for paying CCIP fees. 12. Estimate Alice (sender) balance before the transfer. 13. Call the source router to send the CCIP request. 14. Wait for the transaction to be included in a block. 15. Call the [`getEvm2EvmMessage`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork-js#getevm2evmmessage) function to parse the transaction receipt and extract the `CCIPSendRequested` event and then decodes it to an object. 16. Verify that Alice's balance has decreased by the amount sent. 17. Fork and switch to the destination network: ```typescript await hre.network.provider.request({ method: "hardhat_reset", params: [ { forking: { jsonRpcUrl: getProviderRpcUrl(destination), }, }, ], }) ``` 18. Call the [`routeMessage`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator-fork-js#routemessage) function to route the message to the destination router. 19. Connect to the CCIP-BnM contract using the CCIP-BnM destination address. 20. Verify that Bob's balance has increased by the amount sent. --- # Using CCIP local simulator in your Hardhat project Source: https://docs.chain.link/chainlink-local/build/ccip/hardhat/local-simulator You can use Chainlink Local to run CCIP in a localhost environment within your Hardhat project. To get started quickly, you will use the [CCIP Hardhat Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-hardhat). This project is a Hardhat boilerplate that includes the Chainlink Local package and several CCIP examples. ## Prerequisites 1. In a terminal, clone the [CCIP Hardhat Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-hardhat) repository and change directories: ```shell git clone https://github.com/smartcontractkit/ccip-starter-kit-hardhat && \ cd ./ccip-starter-kit-hardhat/ ``` 2. Install the [Chainlink Local](https://github.com/smartcontractkit/chainlink-local) package and other required packages: ```shell npm install ``` 3. Compile the contracts: ```shell npm run compile ``` ## Test tokens transfers You will run a test to transfer tokens between two accounts. The test file `Example1.spec.ts` is located in the `./test/no-fork` directory. This file contains one test case: **Transfer with LINK fees**: This test case transfers tokens from the sender account to the receiver account, paying fees in LINK. At the end of the test, it verifies that the sender account was debited and the receiver account was credited. For a detailed explanation of the test file, refer to the [Examine the code](#examine-the-code) section. In your terminal, run the following command to execute the test: ```shell npx hardhat test test/no-fork/Example1.spec.ts ``` Example output: ```text $ npx hardhat test test/no-fork/Example1.spec.ts Example 1 ✔ Should transfer CCIP test tokens from EOA to EOA (1057ms) 1 passing (1s) ``` ## Examine the code ### Setup To transfer tokens using CCIP, we need the following: - Destination chain selector - Source CCIP router - LINK token for paying CCIP fees - A test token contract (such as CCIP-BnM) - A sender account (Alice) - A receiver account (Bob) The `deployFixture` function is used to set up the initial state for the tests. This function deploys the CCIP local simulator contract and initializes the sender and receiver accounts. 1. Initialize the [CCIP local simulator](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#cciplocalsimulator) contract: ```typescript const ccipLocalSimulatorFactory = await hre.ethers.getContractFactory("CCIPLocalSimulator") const ccipLocalSimulator: CCIPLocalSimulator = await ccipLocalSimulatorFactory.deploy() ``` 2. Initialize the sender and receiver accounts: ```typescript const [alice, bob] = await hre.ethers.getSigners() ``` ### Test case: Transfer with LINK fees The `it("Should transfer CCIP test tokens from EOA to EOA")` function tests the transfer of tokens between two externally owned accounts (EOA) while paying fees in LINK. Here are the steps involved in this test case: 1. Invoke the `deployFixture` function to set up the necessary variables: ```typescript const { ccipLocalSimulator, alice, bob } = await loadFixture(deployFixture) ``` 2. Invoke the [`configuration`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#configuration) function to retrieve the configuration details for the pre-deployed contracts and services needed for local CCIP simulations. 3. Connect to the source router and CCIP-BnM contracts. 4. Call `ccipBnM.drip` to request CCIP-BnM tokens for Alice (sender). 5. Create an array `Client.EVMTokenAmount[]` to specify the token transfer details: ```typescript const tokenAmounts = [ { token: config.ccipBnM_, amount: amountToSend, }, ] ``` 6. Construct the [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) structure with the receiver, token amounts, and other necessary details. - Use an empty string for the `data` parameter because you are not sending any arbitrary data (only tokens). - Set `gasLimit` to `0` because you are sending tokens to an EOA, which means you do not expect any execution of receiver logic (and therefore do not need gas for that). ```typescript const gasLimit = 0 const functionSelector = id("CCIP EVMExtraArgsV1").slice(0, 10) const defaultAbiCoder = AbiCoder.defaultAbiCoder() const extraArgs = defaultAbiCoder.encode(["uint256"], [gasLimit]) const encodedExtraArgs = `${functionSelector}${extraArgs.slice(2)}` const message = { receiver: defaultAbiCoder.encode(["address"], [bob.address]), data: defaultAbiCoder.encode(["string"], [""]), // no data tokenAmounts: tokenAmounts, feeToken: config.linkToken_, extraArgs: encodedExtraArgs, } ``` 7. Calculate the required fees for the transfer and approve the router to spend LINK tokens for these fees: ```typescript const fee = await mockCcipRouter.getFee(config.chainSelector_, message) await linkToken.connect(alice).approve(mockCcipRouterAddress, fee) ``` 8. Send the CCIP transfer request to the router: ```typescript await mockCcipRouter.connect(alice).ccipSend(config.chainSelector_, message) ``` 9. Verify that Alice's balance has decreased by the amount sent and Bob's balance has increased by the same amount: ```typescript expect(await ccipBnM.balanceOf(alice.address)).to.deep.equal(ONE_ETHER - amountToSend) expect(await ccipBnM.balanceOf(bob.address)).to.deep.equal(amountToSend) ``` ## Next steps For more advanced scenarios, please refer to other test files in the `./test/no-fork` directory. To learn how to use Chainlink local in forked environments, refer to the guide on [Using CCIP Local Simulator in your Hardhat project with forked environments](/chainlink-local/build/ccip/hardhat/local-simulator-fork). --- # Using Chainlink Local to Test CCIP in RemixIDE Source: https://docs.chain.link/chainlink-local/build/ccip/remix There is one comprehensive guide to help you test Chainlink CCIP in Remix IDE using Chainlink Local - without forking. Testing in forked environments is currently supported **only** for Foundry and Hardhat. ## Guides ### Using CCIP Local Simulator in Remix IDE This [guide](/chainlink-local/build/ccip/remix/local-simulator) helps you to test [Chainlink CCIP getting started guide](/ccip/getting-started) locally in Remix IDE. You will: - Deploy a CCIP sender contract - Deploy CCIP receiver contract - Use the local simulator to send data from the sender contract to the receiver contract --- # Using the CCIP local simulator in Remix IDE Source: https://docs.chain.link/chainlink-local/build/ccip/remix/local-simulator In this guide, you will test the [Chainlink CCIP getting started guide](/ccip/getting-started) locally in Remix IDE. You will: - Deploy a CCIP sender contract - Deploy a CCIP receiver contract - Use the local simulator to send data from the sender contract to the receiver contract ## Prerequisites [Remix IDE](https://remix.ethereum.org/) is an online development environment that allows you to write, deploy, and test smart contracts. By default Remix IDE does not persist the files that you open from an external source. To save files, you will need to manually create a workspace and copy the files into the workspace. 1. Open the [Remix IDE](https://remix.ethereum.org/) in your browser. 2. Create a [new workspace](https://remix-ide.readthedocs.io/en/latest/file_explorer.html#new-workspace). 3. Copy the content of the [Test CCIP Local Simulator contract](https://docs.chain.link/samples/CCIP/TestCCIPLocalSimulator.sol) into a new file in the workspace. 4. Copy the content of the [Sender contract](https://docs.chain.link/samples/CCIP/Sender.sol) into a new file in the workspace. 5. Copy the content of the [Receiver contract](https://docs.chain.link/samples/CCIP/Receiver.sol) into a new file in the workspace. At this point, you should have three files in your workspace: - **TestCCIPLocalSimulator.sol**: The file imports the Chainlink CCIP local simulator contract. - **Sender.sol**: The file contains the Sender contract that interacts with CCIP to send data to the Receiver contract. - **Receiver.sol**: The file contains the Receiver contract that receives data from the Sender contract. ## Deploy the contracts 1. Compile the contracts. 2. Under the **Deploy & Run Transactions** tab, make sure *Remix VM* is selected in the **Environment** drop-down list. Remix will use a sandbox blockchain in the browser to deploy the contracts. 3. Deploy the [CCIP local simulator](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#cciplocalsimulator): 1. Select the `TestCCIPLocalSimulator.sol` file in the file explorer. 2. In the *Contract* drop-down list, select `CCIPLocalSimulator`. 3. Click the **Deploy** button. 4. The `CCIPLocalSimulator` is shown in the **Deployed Contracts** section. 5. In the list of functions, click the [`configuration`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#configuration) function to retrieve the configuration details for the pre-deployed contracts and services needed for local CCIP simulations: 4. You will interact with the LINK token contract to fund the sender contract with LINK tokens. The LINK token contract is pre-deployed in the local simulator configuration, so you can simply load the LINK token contract instance: 1. Select `LinkToken` in the *Contract* drop-down list. 2. Fill in the *At Address* field with the address of the `LINK` token contract from the `CCIPLocalSimulator` configuration. 3. Click the **At Address** button. The `LinkToken` contract is shown in the **Deployed Contracts** section. 5. Deploy the `Sender.sol` contract: 1. Select the `Sender.sol` file in the file explorer. 2. In the *Contract* drop-down list, select `Sender`. 3. Under the *Deploy* section, fill in the constructor parameters: - `_router`: The address of the `sourceRouter` contract from the `CCIPLocalSimulator` configuration. - `_link`: The address of the `LINK` token contract from the `CCIPLocalSimulator` configuration. 4. Click the **Deploy** button. The `Sender` contract is shown in the **Deployed Contracts** section. 6. Deploy the `Receiver.sol` contract: 1. Select the `Receiver.sol` file in the file explorer. 2. In the *Contract* drop-down list, select `Receiver`. 3. Under the *Deploy* section, fill in the constructor parameters: - `_router`: The address of the `destinationRouter` contract from the `CCIPLocalSimulator` configuration. 4. Click the **Deploy** button. The `Receiver` contract is shown in the **Deployed Contracts** section. ## Transfer data from the sender to the receiver 1. Fund the sender contract with LINK tokens to pay for CCIP fees: 1. Copy the address of the `Sender` contract from the **Deployed Contracts** section. 2. In the `CCIPLocalSimulator` contract, fill in the [`requestLinkFromFaucet`](/chainlink-local/api-reference/v0.2.3/ccip-local-simulator#requestlinkfromfaucet) function with the following inputs: - `to`: The address of the `Sender` contract. - `amount`: The amount of LINK tokens to transfer. For instance: 1000000000000000000. 3. Click the **Transact** button. 2. Send data from the sender contract to the receiver contract: 1. Copy the address of the `Receiver` contract from the **Deployed Contracts** section. 2. In the `Sender` contract, fill in the `sendMessage` function with: - `destinationChainSelector`: The destination chain selector. You can find it in the `CCIPLocalSimulator` configuration. - `receiver`: The address of the `Receiver` contract. - `text`: The text to send. For instance Hello!. 3. Remix IDE fails to estimate the gas properly for the `sendMessage` function. To work around this, you need to set the gas limit manually to 3000000: 4. Click the **Transact** button. 3. Check the receiver contract to verify the data transfer: 1. In the `Receiver` contract, click on the `getLastReceivedMessageDetails` function. 2. The `getLastReceivedMessageDetails` function returns the text sent from the `Sender` contract: ## Next steps You have successfully tested the CCIP getting started guide within a few minutes using Remix IDE. Testing locally is useful to debug your contracts and fix any issues before testing them on testnets, saving you time and resources. As an exercise, you can try any of the [CCIP guides](/ccip/tutorials) using the local simulator in Remix IDE. --- # Chainlink Local Source: https://docs.chain.link/chainlink-local **Chainlink Local** is an installable package that allows you to run Chainlink services locally. You can import Chainlink Local into your preferred local development environment, such as Foundry projects, Hardhat scripts, or the Remix IDE. Chainlink Local enables rapid exploration, prototyping, local development, and iteration with Chainlink services before transitioning to a testnet. For instance, you can use Chainlink Local to execute CCIP token transfers and arbitrary messages on a local Hardhat or Anvil (Foundry) development node. Chainlink Local also supports forked nodes, allowing you to work with multiple locally running blockchain networks using historical network states. User contracts tested with Chainlink Local can be deployed to test networks without modifications, ensuring a seamless transition from local development to live testnets. **Key Features of Chainlink Local:** - **Local Simulation:** Run Chainlink services on a local development blockchain node, enabling fast and efficient testing and prototyping. - **Forked Networks:** Work with deployed Chainlink contracts using one or multiple forked networks, providing a more realistic testing environment. - **Seamless Integration:** Integrate with Foundry, Hardhat, and Remix IDE for a streamlined development process. To get started testing CCIP with Chainlink Local, follow the installation and setup steps in the CCIP guides for [Foundry](/chainlink-local/build/ccip/foundry), [Hardhat](/chainlink-local/build/ccip/hardhat), or [Remix IDE](/chainlink-local/build/ccip/remix). --- # Chainlink Local Architecture Source: https://docs.chain.link/chainlink-local/learn/architecture [Chainlink Local](https://github.com/smartcontractkit/chainlink-local) is a package that you can import into your development environment (such as Hardhat, Foundry, or Remix IDE) to use Chainlink services locally. It supports two primary modes: - Local testing without forking. - Local testing with forking. This enables you to test Chainlink smart contracts and services, such as CCIP, either in a clean local blockchain state or by using a forked state from a live blockchain. After testing with Chainlink Local, you can deploy your contracts to test networks without any modifications. ## Local testing without forking In this mode, you work with mock contracts on a locally running development blockchain node, such as Hardhat, Anvil (Foundry), or Remix VM. **How it works**: Import the [Chainlink Local](https://www.npmjs.com/package/@chainlink/local) package and deploy a set of Chainlink smart contracts to a blank Hardhat/Anvil network EVM state as part of your tests. You can then deploy your smart contracts and start testing with them. ## Local testing with forking In this mode, you work with deployed Chainlink smart contracts using one or more forked blockchains. This setup provides a more realistic testing environment by incorporating the state of a live blockchain. **How it works**: In your test scripts, you fork one or more blockchains to create locally running blockchain network(s). Chainlink Local provides the necessary interfaces to interact with the forked Chainlink services and utilities for testing in a forked environment. For example, in CCIP, Chainlink Local provides a utility function that helps you switch from one fork (as the source chain) to another fork (as the destination chain). --- # Contributing to Chainlink Local Source: https://docs.chain.link/chainlink-local/learn/contributing Contributions to Chainlink Local are welcome! These contributions can include bug fixes, new features, and documentation updates. This guide provides a list of repositories you can contribute to: - [Chainlink Local](https://github.com/smartcontractkit/chainlink-local): The main repository for Chainlink Local. - [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry): A Foundry starter kit for Chainlink CCIP. It uses Chainlink Local test different scenarios on a local environment with and without forking. - [CCIP Hardhat Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-hardhat): A Hardhat starter kit for Chainlink CCIP. It uses Chainlink Local test different scenarios on a local environment with and without forking. - [Chainlink Technical Documentation](https://github.com/smartcontractkit/documentation): You are welcome to write new guides or update existing guides in the Chainlink documentation.