diff --git a/reports/llms-report.json b/reports/llms-report.json new file mode 100644 index 00000000000..d0a5bbb0cfa --- /dev/null +++ b/reports/llms-report.json @@ -0,0 +1,127 @@ +{ + "startedAt": "2026-01-16T22:27:39.379Z", + "siteBase": "https://docs.chain.link", + "sections": [ + { + "section": "cre-go", + "pagesProcessed": 84, + "outputPath": "src/content/cre/llms-full-go.txt", + "bytes": 690565, + "prevBytes": 690565, + "deltaBytes": 0 + }, + { + "section": "cre-ts", + "pagesProcessed": 79, + "outputPath": "src/content/cre/llms-full-ts.txt", + "bytes": 651635, + "prevBytes": 651635, + "deltaBytes": 0 + }, + { + "section": "vrf", + "pagesProcessed": 35, + "outputPath": "src/content/vrf/llms-full.txt", + "bytes": 396719, + "prevBytes": 396719, + "deltaBytes": 0 + }, + { + "section": "ccip", + "pagesProcessed": 260, + "outputPath": "src/content/ccip/llms-full.txt", + "bytes": 3017262, + "prevBytes": 3017262, + "deltaBytes": 0 + }, + { + "section": "data-feeds", + "pagesProcessed": 37, + "outputPath": "src/content/data-feeds/llms-full.txt", + "bytes": 329359, + "prevBytes": 329359, + "deltaBytes": 0 + }, + { + "section": "data-streams", + "pagesProcessed": 56, + "outputPath": "src/content/data-streams/llms-full.txt", + "bytes": 516904, + "prevBytes": 516904, + "deltaBytes": 0 + }, + { + "section": "dta-technical-standard", + "pagesProcessed": 7, + "outputPath": "src/content/dta-technical-standard/llms-full.txt", + "bytes": 32397, + "prevBytes": 32397, + "deltaBytes": 0 + }, + { + "section": "datalink", + "pagesProcessed": 20, + "outputPath": "src/content/datalink/llms-full.txt", + "bytes": 152656, + "prevBytes": 152656, + "deltaBytes": 0 + }, + { + "section": "chainlink-functions", + "pagesProcessed": 27, + "outputPath": "src/content/chainlink-functions/llms-full.txt", + "bytes": 328258, + "prevBytes": 328258, + "deltaBytes": 0 + }, + { + "section": "chainlink-automation", + "pagesProcessed": 25, + "outputPath": "src/content/chainlink-automation/llms-full.txt", + "bytes": 214589, + "prevBytes": 214589, + "deltaBytes": 0 + }, + { + "section": "resources", + "pagesProcessed": 12, + "outputPath": "src/content/resources/llms-full.txt", + "bytes": 342270, + "prevBytes": 342270, + "deltaBytes": 0 + }, + { + "section": "architecture-overview", + "pagesProcessed": 4, + "outputPath": "src/content/architecture-overview/llms-full.txt", + "bytes": 13086, + "prevBytes": 13086, + "deltaBytes": 0 + }, + { + "section": "getting-started", + "pagesProcessed": 1, + "outputPath": "src/content/getting-started/llms-full.txt", + "bytes": 11261, + "prevBytes": 11261, + "deltaBytes": 0 + }, + { + "section": "chainlink-nodes", + "pagesProcessed": 37, + "outputPath": "src/content/chainlink-nodes/llms-full.txt", + "bytes": 673481, + "prevBytes": 673481, + "deltaBytes": 0 + }, + { + "section": "chainlink-local", + "pagesProcessed": 55, + "outputPath": "src/content/chainlink-local/llms-full.txt", + "bytes": 304674, + "prevBytes": 304686, + "deltaBytes": -12 + } + ], + "finishedAt": "2026-01-16T22:27:43.202Z" +} \ No newline at end of file diff --git a/src/components/QuickLinks/data/productChainLinks.ts b/src/components/QuickLinks/data/productChainLinks.ts index f183b411ea3..4153f8ab7b4 100644 --- a/src/components/QuickLinks/data/productChainLinks.ts +++ b/src/components/QuickLinks/data/productChainLinks.ts @@ -137,6 +137,7 @@ export const productChainLinks: ProductChainLinks = { hyperevm: "/data-feeds/price-feeds/addresses?page=1&network=hyperevm#networks", linea: "/data-feeds/price-feeds/addresses?page=1&network=linea#networks", mantle: "/data-feeds/price-feeds/addresses?page=1&network=mantle#networks", + megaeth: "/data-feeds/price-feeds/addresses?page=1&network=megaeth#networks", metis: "/data-feeds/price-feeds/addresses?page=1&network=metis#networks", monad: "/data-feeds/price-feeds/addresses?page=1&network=monad#networks", moonbeam: "/data-feeds/price-feeds/addresses?page=1&network=moonbeam#networks", diff --git a/src/content/ccip/llms-full.txt b/src/content/ccip/llms-full.txt index f3c3cd38e3f..c92d0351ee3 100644 --- a/src/content/ccip/llms-full.txt +++ b/src/content/ccip/llms-full.txt @@ -84,524 +84,519 @@ To learn about tokens, token pools, and the token onboarding process, see the [C --- -# Getting Started (EVM) +# Get Started with CCIP (EVM) Source: https://docs.chain.link/ccip/getting-started/evm -Last Updated: 2025-05-19 +Last Updated: 2025-12-25 - +**Build and run a secure cross-chain messaging workflow between two EVM chains using Chainlink CCIP.** -A simple use case for Chainlink CCIP is sending data between smart contracts on different blockchains. This guide shows you how to deploy a CCIP sender contract and a CCIP receiver contract to two different blockchains and send data from the sender contract to the receiver contract. You pay the CCIP fees using LINK. +In this guide, you will: -Fees can also be paid in alternative assets, which currently include the native gas tokens of the source blockchain and their ERC20 wrapped version. For example, you can pay ETH or WETH when you send transactions to the CCIP router on Ethereum and AVAX or WAVAX when you send transactions to the CCIP router on Avalanche. +1. Deploy a sender on a source chain +2. Deploy a receiver on a destination chain +3. Send and verify a cross-chain message ## Before you begin -- If you are new to smart contract development, learn how to [Deploy Your First Smart Contract](/quickstarts/deploy-your-first-contract) so you are familiar with the tools that are necessary for this guide: - - The [Solidity](https://soliditylang.org/) programming language - - The [MetaMask](https://metamask.io) wallet - - The [Remix](https://remix.ethereum.org/) development environment -- Acquire testnet funds. This guide requires testnet AVAX and LINK on *Avalanche Fuji*. It also requires testnet ETH on *Ethereum Sepolia*. If you need to use different networks, you can find more faucets on the [LINK Token Contracts](/resources/link-token-contracts) page. - - Go to [faucets.chain.link](https://faucets.chain.link/) to get your testnet tokens. -- Learn how to [Fund your contract with LINK](/resources/fund-your-contract). + -## Deploy the sender contract +You will need: -Deploy the `Sender.sol` contract on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#sender-code) section. +- Basic [Solidity](https://soliditylang.org/) and [smart contract deployment](/quickstarts/deploy-your-first-contract) experience -1. [Open the Sender.sol contract](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Sender.sol) in Remix. +- One [wallet](https://metamask.io/) funded on two CCIP-supported EVM testnets: [Avalanche Fuji and Ethereum Sepolia](/ccip/directory/testnet). You will need some native tokens and `LINK` on both networks. - ```sol - // SPDX-License-Identifier: MIT - pragma solidity 0.8.24; +- Choose one of the following development environments: + - **[Hardhat 3](https://hardhat.org/docs/getting-started)** + - **[Foundry](https://book.getfoundry.sh/)** + - **[Remix](https://remix-ide.readthedocs.io/en/latest/)** - import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; +## Examine the example code - import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; - import {OwnerIsCreator} from "@chainlink/contracts@1.4.0/src/v0.8/shared/access/OwnerIsCreator.sol"; - import {LinkTokenInterface} from "@chainlink/contracts@1.4.0/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; +This section goes through the code for the `sender` and `receiver` contracts +needed to complete the tutorial. +We will use the same contracts for all three development environments. - /** - * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. - * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. - * DO NOT USE THIS CODE IN PRODUCTION. - */ + + The smart contract in this tutorial is designed to interact with CCIP to send data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below. - /// @title - A simple contract for sending string data across chains. - contract Sender is OwnerIsCreator { - // Custom errors to provide more descriptive revert messages. - error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough - // balance. + - // Event emitted when a message is sent to another chain. - // The chain selector of the destination chain. - // The address of the receiver on the destination chain. - // The text being sent. - // the token address used to pay CCIP fees. - // The fees paid for sending the CCIP message. - event MessageSent( // The unique ID of the CCIP message. - bytes32 indexed messageId, - uint64 indexed destinationChainSelector, - address receiver, - string text, - address feeToken, - uint256 fees - ); + #### Initializing the contract - IRouterClient private s_router; + When deploying the contract, you define the router address and the LINK contract address of the blockchain where you choose to deploy the contract. - LinkTokenInterface private s_linkToken; + The router address provides functions that are required for this example: - /// @notice Constructor initializes the contract with the router address. - /// @param _router The address of the router contract. - /// @param _link The address of the link contract. - constructor( - address _router, - address _link - ) { - s_router = IRouterClient(_router); - s_linkToken = LinkTokenInterface(_link); - } + - The `getFee` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#getfee) to estimate the CCIP fees. + - The `ccipSend` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#ccipsend) to send CCIP messages. - /// @notice Sends data to receiver on the destination chain. - /// @dev Assumes your contract has sufficient LINK. - /// @param destinationChainSelector The identifier (aka selector) for the destination blockchain. - /// @param receiver The address of the recipient on the destination blockchain. - /// @param text The string text to be sent. - /// @return messageId The ID of the message that was sent. - function sendMessage( - uint64 destinationChainSelector, - address receiver, - string calldata text - ) external onlyOwner returns (bytes32 messageId) { - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ - receiver: abi.encode(receiver), // ABI-encoded receiver address - data: abi.encode(text), // ABI-encoded string - tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent - extraArgs: Client._argsToBytes( - // Additional arguments, setting gas limit and allowing out-of-order execution. - // Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach - // where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages, - // and ensures compatibility with future CCIP upgrades. Read more about it here: - // https://docs.chain.link/ccip/concepts/best-practices/evm#using-extraargs - Client.GenericExtraArgsV2({ - gasLimit: 200_000, // Gas limit for the callback on the destination chain - allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages - // from - // the same sender - }) - ), - // Set the feeToken address, indicating LINK will be used for fees - feeToken: address(s_linkToken) - }); + #### Sending data - // Get the fee required to send the message - uint256 fees = s_router.getFee(destinationChainSelector, evm2AnyMessage); + The `sendMessage` function completes several operations: - if (fees > s_linkToken.balanceOf(address(this))) { - revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); - } + 1. Construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.1/client#evm2anymessage): + - The `receiver` address is encoded in bytes format to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html). + - The `data` is encoded from a string text to bytes using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html). + - The `tokenAmounts` is an array. Each element comprises a [struct](/ccip/api-reference/evm/v1.6.1/client#evmtokenamount) that contains the token address and amount. In this example, the array is empty because no tokens are sent. + - The `extraArgs` specify the `gasLimit` for relaying the CCIP message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `200000`. + - The `feeToken` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK. - // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK - s_linkToken.approve(address(s_router), fees); + 2. Compute the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#getfee). - // Send the message through the router and store the returned message ID - messageId = s_router.ccipSend(destinationChainSelector, evm2AnyMessage); + 3. Ensure that your contract balance in LINK is enough to cover the fees. - // Emit an event with message details - emit MessageSent(messageId, destinationChainSelector, receiver, text, address(s_linkToken), fees); + 4. Grant the router contract permission to deduct the fees from the contract's LINK balance. - // Return the message ID - return messageId; - } - } - ``` + 5. Dispatch the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#ccipsend). -2. Compile the contract. + + - 6. Open MetaMask and send 70 LINK to the contract address that you copied. Your contract will pay CCIP fees in LINK. +## Send a cross-chain message using CCIP - **Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK - from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia. +Send and verify a cross-chain message using CCIP in under 10 minutes, with your favorite development framework. -## Deploy the receiver contract +### Hardhat 3 -Deploy the receiver contract on *Ethereum Sepolia*. You will use this contract to receive data from the sender that you deployed on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#receiver-code) section. +Best for a `Typescript` based scripting workflow where you deploy contracts, send a CCIP message, and verify delivery from the command line. -1. [Open the Receiver.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol) contract in Remix. +{/* Ideal for getting a full end-to-end CCIP flow running quickly. */} - ```sol - // SPDX-License-Identifier: MIT - pragma solidity 0.8.24; + + 1. Open a new terminal in a directory of your choice and run this command: - import {CCIPReceiver} from "@chainlink/contracts-ccip/contracts/applications/CCIPReceiver.sol"; - import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; + ```bash filename="Terminal" + npx hardhat --init + ``` - /** - * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. - * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. - * DO NOT USE THIS CODE IN PRODUCTION. - */ + Create a project with the following options: - /// @title - A simple contract for receiving string data across chains. - contract Receiver is CCIPReceiver { - // Event emitted when a message is received from another chain. - event MessageReceived( // The unique ID of the message. - // The chain selector of the source chain. - // The address of the sender from the source chain. - // The text that was received. - bytes32 indexed messageId, - uint64 indexed sourceChainSelector, - address sender, - string text - ); + - Hardhat Version: hardhat-3 + - Initialize project: At root of the project + - Type of project: A minimal Hardhat project + - Install the necessary dependencies: Yes - bytes32 private s_lastReceivedMessageId; // Store the last received messageId. - string private s_lastReceivedText; // Store the last received text. + 2. Install the additional dependencies required by this tutorial: - /// @notice Constructor initializes the contract with the router address. - /// @param router The address of the router contract. - constructor( - address router - ) CCIPReceiver(router) {} + ```bash filename="Terminal" + npm install @chainlink/contracts-ccip @chainlink/contracts viem + npm install --save-dev @nomicfoundation/hardhat-viem @nomicfoundation/hardhat-keystore + ``` - /// handle a received message - function _ccipReceive( - Client.Any2EVMMessage memory any2EvmMessage - ) internal override { - s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId - s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text + - emit MessageReceived( - any2EvmMessage.messageId, - any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector) - abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address, - abi.decode(any2EvmMessage.data, (string)) - ); - } + 3. Update `hardhat.config.ts` to use the `hardhat-viem` and `hardhat-keystore` plugins: - /// @notice Fetches the details of the last received message. - /// @return messageId The ID of the last received message. - /// @return text The last received text. - function getLastReceivedMessageDetails() external view returns (bytes32 messageId, string memory text) { - return (s_lastReceivedMessageId, s_lastReceivedText); - } - } - ``` + ```typescript filename="hardhat.config.ts" + import { configVariable, defineConfig } from "hardhat/config" + import hardhatKeystore from "@nomicfoundation/hardhat-keystore" + import hardhatViem from "@nomicfoundation/hardhat-viem" -2. Compile the contract. + export default defineConfig({ + plugins: [hardhatViem, hardhatKeystore], + solidity: { + version: "0.8.24", + }, + networks: { + sepolia: { + type: "http", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("PRIVATE_KEY")], + }, + avalancheFuji: { + type: "http", + url: configVariable("FUJI_RPC_URL"), + accounts: [configVariable("PRIVATE_KEY")], + }, + }, + }) + ``` -3. Deploy the receiver contract on *Ethereum Sepolia*: - 1. Open MetaMask and select the *Ethereum Sepolia* network. + 4. Finally, set the environment variables being referenced in the `hardhat.config.ts` file using `hardhat-keystore`. \ + If you have followed the tutorial you have already installed the required package. + You will need to run the following three commands **in succession** to configure the environment variables, Hardhat will ask you to enter the password for the keystore + for each of the variables: - 2. In Remix under the **Deploy & Run Transactions** tab, make sure the **Environment** is still set to *Injected Provider - MetaMask*. + ```bash filename="Terminal" + npx hardhat keystore set SEPOLIA_RPC_URL + npx hardhat keystore set FUJI_RPC_URL + npx hardhat keystore set PRIVATE_KEY + ``` - 3. Under the **Deploy** section, fill in the router address field. For *Ethereum Sepolia*, the Router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59. You can find the addresses for each network on the [CCIP Directory](/ccip/directory). + The output of `npx hardhat keystore list ` should look like this: - 4. Click the **Deploy** button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to *Ethereum Sepolia*. + + - 5. After you confirm the transaction, the contract address appears as the second item in the **Deployed Contracts** list. Copy this contract address. + + 1. Create a new directory named `scripts` at the root of the project if it doesn't already exist. + 2. Create a new file named `send-cross-chain-message.ts` in this directory and paste the following code inside it: -You now have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. You sent `70` LINK to the *sender* contract to pay the CCIP fees. Next, send data from the sender contract to the receiver contract. + ```typescript filename="scripts/send-cross-chain-message.ts" + import { network } from "hardhat" + import { getContract, parseAbi, parseUnits } from "viem" -## Send data + // Avalanche Fuji configuration + const FUJI_ROUTER = "0xF694E193200268f9a4868e4Aa017A0118C9a8177" + const FUJI_LINK = "0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846" -Send a `Hello World!` string from your contract on *Avalanche Fuji* to the contract you deployed on *Ethereum Sepolia*: + // Ethereum Sepolia configuration + // Note that the contract on Sepolia doesn't need to have LINK to pay for CCIP fees. + const SEPOLIA_ROUTER = "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59" + const SEPOLIA_CHAIN_SELECTOR = 16015286601757825753n -1. Open MetaMask and select the *Avalanche Fuji* network. + // Connect to Avalanche Fuji + console.log("Connecting to Avalanche Fuji...") + const fujiNetwork = await network.connect("avalancheFuji") -2. In Remix under the **Deploy & Run Transactions** tab, expand the first contract in the **Deployed Contracts** section. + // Connect to Ethereum Sepolia + console.log("Connecting to Ethereum Sepolia...") + const sepoliaNetwork = await network.connect("sepolia") -3. Expand the **sendMessage** function and fill in the following arguments: + // Step 1: Deploy Sender on Fuji + console.log("\n[Step 1] Deploying Sender contract on Avalanche Fuji...") - | Argument | Description | Value (*Ethereum Sepolia*) | - | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | - | destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | 16015286601757825753 | - | receiver | The destination smart contract address | Your deployed contract address | - | text | Any `string` | Hello World! | + const sender = await fujiNetwork.viem.deployContract("Sender", [FUJI_ROUTER, FUJI_LINK]) + const fujiPublicClient = await fujiNetwork.viem.getPublicClient() -4. Click the **transact** button to run the function. MetaMask prompts you to confirm the transaction. + console.log(`Sender contract has been deployed to this address on the Fuji testnet: ${sender.address}`) + console.log(`View on Avascan: https://testnet.avascan.info/blockchain/all/address/${sender.address}`) - + // Step 2: Fund Sender with LINK + console.log("\n[Step 2] Funding Sender with 1 LINK...") -1. After the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x113933ec9f1b2e795a1e2f564c9d452db92d3e9a150545712687eb546916e633) of a successful transaction on *Avalanche Fuji*. + const [fujiWalletClient] = await fujiNetwork.viem.getWalletClients() + if (!fujiWalletClient) { + throw new Error("No wallet client available. Check PRIVATE_KEY + network config in hardhat.config.ts.") + } -After the transaction is finalized on the source chain, it will take a few minutes for CCIP to deliver the data to *Ethereum Sepolia* and call the `ccipReceive` function on your receiver contract. You can use the [CCIP explorer](https://ccip.chain.link/) to see the status of your CCIP transaction and then read data stored by your receiver contract. + // We create a minimal interface for the LINK token to be able to call the transfer function. -1. Open the [CCIP explorer](https://ccip.chain.link/) and use the transaction hash that you copied to search for your cross-chain transaction. The explorer provides several details about your request. + const linkTokenInterfaceAbi = parseAbi(["function transfer(address to, uint256 value) returns (bool)"]) -2. When the status of the transaction is marked with a "Success" status, the CCIP transaction and the destination transaction are complete. + const link = getContract({ + address: FUJI_LINK, + abi: linkTokenInterfaceAbi, + client: { public: fujiPublicClient, wallet: fujiWalletClient }, + }) -## Read data + const transferLinkToFujiContract = await link.write.transfer([sender.address, parseUnits("1", 18)]) -Read data stored by the receiver contract on *Ethereum Sepolia*: + console.log("LINK token transfer in progress, awaiting confirmation...") + await fujiPublicClient.waitForTransactionReceipt({ hash: transferLinkToFujiContract, confirmations: 1 }) + console.log(`Funded Sender with 1 LINK`) -1. Open MetaMask and select the *Ethereum Sepolia* network. -2. In Remix under the **Deploy & Run Transactions** tab, expand the receiver contract deployed on *Ethereum Sepolia*. -3. Click the **getLastReceivedMessageDetails** function button to read the stored data. In this example, it is "Hello World!". + // Step 3: Deploy Receiver on Sepolia + console.log("\n[Step 3] Deploying Receiver on Ethereum Sepolia...") -Congratulations! You just sent your first cross-chain data using CCIP. Next, examine the example code to learn how this contract works. + const receiver = await sepoliaNetwork.viem.deployContract("Receiver", [SEPOLIA_ROUTER]) + const sepoliaPublicClient = await sepoliaNetwork.viem.getPublicClient() -## Examine the example code + console.log(`Receiver contract has been deployed to this address on the Sepolia testnet: ${receiver.address}`) + console.log(`View on Etherscan: https://sepolia.etherscan.io/address/${receiver.address}`) + console.log(`\nšŸ“‹ Copy the receiver address since it will be needed to run the verification script šŸ“‹ \n`) -### Sender code + // Step 4: Send cross-chain message + console.log("\n[Step 4] Sending cross-chain message...") -The smart contract in this tutorial is designed to interact with CCIP to send data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below. + const sendMessageTx = await sender.write.sendMessage([ + SEPOLIA_CHAIN_SELECTOR, + receiver.address, + "Hello World from Hardhat script!", + ]) -```sol -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; + console.log("Cross-chain message sent, awaiting confirmation...") + console.log(`Message sent from source contract! āœ… \n Tx hash: ${sendMessageTx}`) + console.log(`View transaction status on CCIP Explorer: https://ccip.chain.link`) + console.log( + "Run the receiver script after 10 minutes to check if the message has been received on the destination contract." + ) + ``` -import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; + This script does the following: -import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; -import {OwnerIsCreator} from "@chainlink/contracts@1.4.0/src/v0.8/shared/access/OwnerIsCreator.sol"; -import {LinkTokenInterface} from "@chainlink/contracts@1.4.0/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; + - Connects to the Avalanche Fuji and Ethereum Sepolia networks. + - Deploys the sender contract on Avalanche Fuji. + - Funds the sender contract with 1 LINK. + - Deploys the receiver contract on Ethereum Sepolia. + - Sends a cross-chain message from the sender contract to the receiver contract. -/** - * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. - * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. - * DO NOT USE THIS CODE IN PRODUCTION. - */ + - IRouterClient private s_router; + 3. Run the following command to send the cross-chain message: - LinkTokenInterface private s_linkToken; + ```bash filename="Terminal" + npx hardhat run scripts/send-cross-chain-message.ts + ``` + - /// @notice Constructor initializes the contract with the router address. - /// @param _router The address of the router contract. - /// @param _link The address of the link contract. - constructor( - address _router, - address _link - ) { - s_router = IRouterClient(_router); - s_linkToken = LinkTokenInterface(_link); - } +### Foundry - /// @notice Sends data to receiver on the destination chain. - /// @dev Assumes your contract has sufficient LINK. - /// @param destinationChainSelector The identifier (aka selector) for the destination blockchain. - /// @param receiver The address of the recipient on the destination blockchain. - /// @param text The string text to be sent. - /// @return messageId The ID of the message that was sent. - function sendMessage( - uint64 destinationChainSelector, - address receiver, - string calldata text - ) external onlyOwner returns (bytes32 messageId) { - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ - receiver: abi.encode(receiver), // ABI-encoded receiver address - data: abi.encode(text), // ABI-encoded string - tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent - extraArgs: Client._argsToBytes( - // Additional arguments, setting gas limit and allowing out-of-order execution. - // Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach - // where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages, - // and ensures compatibility with future CCIP upgrades. Read more about it here: - // https://docs.chain.link/ccip/concepts/best-practices/evm#using-extraargs - Client.GenericExtraArgsV2({ - gasLimit: 200_000, // Gas limit for the callback on the destination chain - allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages - // from - // the same sender - }) - ), - // Set the feeToken address, indicating LINK will be used for fees - feeToken: address(s_linkToken) - }); +Best for **Solidity-native** workflows that prefer a modular, powerful scripting framework. - // Get the fee required to send the message - uint256 fees = s_router.getFee(destinationChainSelector, evm2AnyMessage); + + 1. Open a new terminal in a directory of your choice and run this command to initialize a new Foundry project at the root: - if (fees > s_linkToken.balanceOf(address(this))) { - revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); - } + ```bash filename="Terminal" + forge init + ``` - // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK - s_linkToken.approve(address(s_router), fees); + 2. Install the required dependencies: - // Send the message through the router and store the returned message ID - messageId = s_router.ccipSend(destinationChainSelector, evm2AnyMessage); + ```bash filename="Terminal" + forge install smartcontractkit/chainlink-ccip smartcontractkit/chainlink-evm + ``` - // Emit an event with message details - emit MessageSent(messageId, destinationChainSelector, receiver, text, address(s_linkToken), fees); + - // Return the message ID - return messageId; - } -} -``` + 3. Use Foundry's `cast` command to create a new keystore for your `PRIVATE_KEY`: -#### Initializing the contract + ```bash filename="Terminal" + cast wallet import --interactive PRIVATE_KEY + ``` -When deploying the contract, you define the router address and the LINK contract address of the blockchain where you choose to deploy the contract. + And use the `cast wallet list` command to verify: -The router address provides functions that are required for this example: + -- The `getFee` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#getfee) to estimate the CCIP fees. -- The `ccipSend` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#ccipsend) to send CCIP messages. + -#### Sending data + 4. Configure the remappings so that your `foundry.toml` file looks like this: + + ```toml filename="foundry.toml" + [profile.default] + solc = "0.8.24" + src = "src" + out = "out" + libs = ["lib"] + + remappings = [ + "forge-std/=lib/forge-std/src/", + "@chainlink/contracts-ccip/contracts/=lib/chainlink-ccip/chains/evm/contracts/", + "@chainlink/contracts/=lib/chainlink-evm/contracts/", + "@openzeppelin/contracts@5.0.2/utils/introspection/=lib/forge-std/src/interfaces/" + ] + + # RPC URLs will be fed to our script via Foundry's config file + [rpc_endpoints] + sepolia = "ENTER_YOUR_SEPOLIA_RPC_URL_HERE" + fuji = "ENTER_YOUR_FUJI_RPC_URL_HERE" + ``` + + + + 1. Create a new directory named `script` at the root of the project if it doesn't already exist. + 2. Create a new file named `SendCrossChainMessage.s.sol` in this directory and paste the following code inside it: + + ```solidity filename="script/SendCrossChainMessage.s.sol" + // SPDX-License-Identifier: UNLICENSED + pragma solidity 0.8.24; + + import {Script, console} from "forge-std/Script.sol"; + + import {Sender} from "../src/Sender.sol"; + import {Receiver} from "../src/Receiver.sol"; + import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; + + contract SendCrossChainMessage is Script { + // Avalanche Fuji configuration + address constant FUJI_ROUTER = 0xF694E193200268f9a4868e4Aa017A0118C9a8177; + address constant FUJI_LINK = 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846; + + // Ethereum Sepolia configuration + address constant SEPOLIA_ROUTER =0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59; + uint64 constant SEPOLIA_CHAIN_SELECTOR = 16015286601757825753; + + // Configuring decimal value for LINK token + uint256 ONE_LINK = 1e18; + + function run() public { + + // Load form configs from foundry.toml + uint256 fujiFork = vm.createFork(vm.rpcUrl("fuji")); + uint256 sepoliaFork = vm.createFork(vm.rpcUrl("sepolia")); + + // Step 1: Deploy Sender on Fuji + + // Connect to Fuji Network + console.log("Connecting to Avalanche Fuji..."); + vm.selectFork(fujiFork); + vm.startBroadcast(); + + // Deploy Sender contract + console.log("\n[Step 1] Deploying Sender contract on Avalanche Fuji..."); + Sender sender = new Sender(FUJI_ROUTER, FUJI_LINK); + console.log("Sender contract has been deployed to this address on the Fuji testnet:", address(sender)); + console.log( + string.concat( + "View on Avascan: https://testnet.avascan.info/blockchain/all/address/", + vm.toString(address(sender)) + ) + ); + + // Step 2: Fund Sender with 1 LINK + console.log("\n[Step 2] Funding Sender with 1 LINK on Avalanche Fuji..."); + LinkTokenInterface(FUJI_LINK).transfer(address(sender), ONE_LINK); + vm.stopBroadcast(); + console.log("Funded Sender with 1 LINK on Fuji"); + + // Step 3: Deploy Receiver on Sepolia + + // Connect to Sepolia Network + console.log("Connecting to Ethereum Sepolia..."); + vm.selectFork(sepoliaFork); + vm.startBroadcast(); + + // Deploy Receiver contract + + console.log("\n[Step 3] Deploying Receiver contract on Ethereum Sepolia..."); + Receiver receiver = new Receiver(SEPOLIA_ROUTER); + vm.stopBroadcast(); + console.log("Receiver deployed on Sepolia at this address:", address(receiver)); + console.log( + string.concat( + "View on Etherscan: https://sepolia.etherscan.io/address/", + vm.toString(address(receiver)) + ) + ); + console.log("\n .....Copy the receiver address since it will be needed to run the verification script.....\n"); + console.log(address(receiver)); + + // Step 4: Send cross-chain message (Fuji -> Sepolia) + vm.selectFork(fujiFork); + vm.startBroadcast(); + + // Send cross-chain message + console.log("Sending cross-chain message from Fuji to Sepolia..."); + bytes32 messageId = sender.sendMessage( + SEPOLIA_CHAIN_SELECTOR, + address(receiver), + "Hello World from Foundry script!" + ); + vm.stopBroadcast(); + + console.log("The message has been sent to the CCIP router on Fuji, check for successful delivery after 5 minutes..."); + console.log("CCIP messageId:"); + console.logBytes32(messageId); + console.log("View transaction status on CCIP Explorer: https://ccip.chain.link"); + } + } + ``` -The `sendMessage` function completes several operations: + 3. Run the following command to send the cross-chain message: -1. Construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.1/client#evm2anymessage): - - The `receiver` address is encoded in bytes format to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html). - - The `data` is encoded from a string text to bytes using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html). - - The `tokenAmounts` is an array. Each element comprises a [struct](/ccip/api-reference/evm/v1.6.1/client#evmtokenamount) that contains the token address and amount. In this example, the array is empty because no tokens are sent. - - The `extraArgs` specify the `gasLimit` for relaying the CCIP message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `200000`. - - The `feeToken` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK. + ```bash filename="Terminal" + forge script script/SendCrossChainMessage.s.sol:SendCrossChainMessage --broadcast --multi --account PRIVATE_KEY + ``` -2. Compute the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#getfee). + + -3. Ensure that your contract balance in LINK is enough to cover the fees. +### Remix -4. Grant the router contract permission to deduct the fees from the contract's LINK balance. +Best for **Web3-native** workflows that prefer a browser-based IDE. -5. Dispatch the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#ccipsend). + + Deploy the `Sender.sol` contract on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#sender-code) section. - + -### Receiver code + 4. Click the **transact** button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to *Avalanche Fuji*. -The smart contract in this tutorial is designed to interact with CCIP to receive data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below. + 5. After you confirm the transaction, the contract address appears in the **Deployed Contracts** list. Copy your contract address. -```sol -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; + -import {CCIPReceiver} from "@chainlink/contracts-ccip/contracts/applications/CCIPReceiver.sol"; -import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; + 6. Open MetaMask and send LINK to the contract address that you copied. Your contract will pay CCIP fees in LINK. -/** - * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. - * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. - * DO NOT USE THIS CODE IN PRODUCTION. - */ + **Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK + from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia. + -/// @title - A simple contract for receiving string data across chains. -contract Receiver is CCIPReceiver { - // Event emitted when a message is received from another chain. - event MessageReceived( // The unique ID of the message. - // The chain selector of the source chain. - // The address of the sender from the source chain. - // The text that was received. - bytes32 indexed messageId, - uint64 indexed sourceChainSelector, - address sender, - string text - ); + + Send a `Hello World!` string from your contract on *Avalanche Fuji* to the contract you deployed on *Ethereum Sepolia*: - bytes32 private s_lastReceivedMessageId; // Store the last received messageId. - string private s_lastReceivedText; // Store the last received text. + 1. Open MetaMask and select the *Avalanche Fuji* network. - /// @notice Constructor initializes the contract with the router address. - /// @param router The address of the router contract. - constructor( - address router - ) CCIPReceiver(router) {} + 2. In Remix under the **Deploy & Run Transactions** tab, expand the first contract in the **Deployed Contracts** section. - /// handle a received message - function _ccipReceive( - Client.Any2EVMMessage memory any2EvmMessage - ) internal override { - s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId - s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text + 3. Expand the **sendMessage** function and fill in the following arguments: - emit MessageReceived( - any2EvmMessage.messageId, - any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector) - abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address, - abi.decode(any2EvmMessage.data, (string)) - ); - } + | Argument | Description | Value (*Ethereum Sepolia*) | + | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | + | destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | | + | receiver | The destination smart contract address | Your deployed contract address | + | text | Any `string` | | - /// @notice Fetches the details of the last received message. - /// @return messageId The ID of the last received message. - /// @return text The last received text. - function getLastReceivedMessageDetails() external view returns (bytes32 messageId, string memory text) { - return (s_lastReceivedMessageId, s_lastReceivedText); - } -} -``` + -#### Initializing the contract + 4. Click the **transact** button to run the function. MetaMask prompts you to confirm the transaction. -When you deploy the contract, you define the router address. The receiver contract inherits from the [CCIPReceiver.sol](/ccip/api-reference/evm/v1.6.1/ccip-receiver) contract, which uses the router address. + -#### Receiving data + 5. After the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x113933ec9f1b2e795a1e2f564c9d452db92d3e9a150545712687eb546916e633) of a successful transaction on *Avalanche Fuji*. -On the destination blockchain: + After the transaction is finalized on the source chain, it will take a few minutes for CCIP to deliver the data to *Ethereum Sepolia* and call the `ccipReceive` function on your receiver contract. You can use the [CCIP explorer](https://ccip.chain.link/) to see the status of your CCIP transaction and then read data stored by your receiver contract. -1. The CCIP Router invokes the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.1/ccip-receiver#ccipreceive). **Note**: This function is protected by the `onlyRouter` [modifier](/ccip/api-reference/evm/v1.6.1/ccip-receiver#onlyrouter), which ensures that only the router can call the receiver contract. -2. The `ccipReceive` [function](/ccip/api-reference/evm/v1.6.1/ccip-receiver#ccipreceive) calls an internal function `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.1/ccip-receiver#_ccipreceive). The receiver contract implements this function. -3. This `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.1/ccip-receiver#_ccipreceive) expects an `Any2EVMMessage` [struct](/ccip/api-reference/evm/v1.6.1/client#any2evmmessage) that contains the following values: - - The CCIP `messageId`. - - The `sourceChainSelector`. - - The `sender` address in bytes format. The sender is a contract deployed on an EVM-compatible blockchain, so the address is decoded from bytes to an Ethereum address using the [ABI specification](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html). - - The `data` is also in bytes format. A `string` is expected, so the data is decoded from bytes to a string using the [ABI specification](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html). + 6. Open the [CCIP explorer](https://ccip.chain.link/) and use the transaction hash that you copied to search for your cross-chain transaction. The explorer provides several details about your request. - + + --- diff --git a/src/content/chainlink-local/llms-full.txt b/src/content/chainlink-local/llms-full.txt index af2568892e6..2a3a13dbf28 100644 --- a/src/content/chainlink-local/llms-full.txt +++ b/src/content/chainlink-local/llms-full.txt @@ -8536,8 +8536,8 @@ does not persist the files that you open from an external source. To save files, import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol"; import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol"; - import {OwnerIsCreator} from "@chainlink/contracts@1.4.0/src/v0.8/shared/access/OwnerIsCreator.sol"; - import {LinkTokenInterface} from "@chainlink/contracts@1.4.0/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; + import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol"; + import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. diff --git a/src/features/data/chains.ts b/src/features/data/chains.ts index 454b0aba77a..51a025fab83 100644 --- a/src/features/data/chains.ts +++ b/src/features/data/chains.ts @@ -453,6 +453,24 @@ export const CHAINS: Chain[] = [ }, ], }, + { + page: "MegaETH", + label: "MegaETH", + title: "MegaETH Data Feeds", + img: "/assets/chains/megaeth.svg", + networkStatusUrl: "https://uptime.megaeth.com/", + tags: ["default"], + supportedFeatures: ["feeds"], + networks: [ + { + name: "MegaETH Mainnet (Private)", + explorerUrl: "https://megaeth.blockscout.com/address/%s", + networkType: "mainnet", + rddUrl: "https://reference-data-directory.vercel.app/feeds-megaeth-mainnet.json", + queryString: "megaeth-mainnet", + }, + ], + }, { page: "monad", title: "Monad Data Feeds", diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 8ce64923af8..ed20e2b3c99 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -1425,6 +1425,24 @@ export const FeedList = ({ )} + {network.name === "MegaETH Mainnet (Private)" && ( +
+
+ Note +
+
+

Private Mainnet

+

+ MegaETH currently operates a private mainnet. Your address must be on the whitelist to + transact. For more information, visit{" "} + + megaeth.com + + . +

+
+
+ )}
{!isStreams && !isSmartData && (
diff --git a/src/features/feeds/components/Tables.module.css b/src/features/feeds/components/Tables.module.css index ad1d7f3e3cf..321f8c9b876 100644 --- a/src/features/feeds/components/Tables.module.css +++ b/src/features/feeds/components/Tables.module.css @@ -284,6 +284,53 @@ text-align: left; } +.infoCallout { + padding: var(--space-4x); + gap: var(--space-4x); + background-color: var(--color-background-info); + border: 1px solid #eee; + border-radius: var(--border-radius-10); + color: var(--color-text-info); + outline: 1px solid transparent; + display: flex; + margin-top: 12px; + margin-bottom: 12px; +} + +.infoCalloutIcon { + flex-shrink: 0; + width: 1.5em; +} + +.infoCalloutIcon img { + width: 1.5em; + height: 1.5em; +} + +.infoCalloutContent { + flex: 1; +} + +.infoCalloutTitle { + font-weight: bold; + text-transform: uppercase; + color: var(--theme-text); + margin-bottom: var(--space-1x); + font-size: 14px; +} + +.infoCalloutContent p { + color: var(--theme-text-light); + line-height: 1.5; + font-size: 14px; + margin: 0; +} + +.infoCalloutContent a { + color: var(--color-text-link); + text-decoration: underline; +} + .feedVariantBadge { display: inline-block; font-size: 0.75rem; diff --git a/src/scripts/data/detect-new-data.ts b/src/scripts/data/detect-new-data.ts index d1b0549c3ec..bb433462f86 100644 --- a/src/scripts/data/detect-new-data.ts +++ b/src/scripts/data/detect-new-data.ts @@ -49,6 +49,7 @@ const NETWORK_ENDPOINTS: Record = { bob: "https://reference-data-directory.vercel.app/feeds-bitcoin-mainnet-bob-1.json", plasma: "https://reference-data-directory.vercel.app/feeds-plasma-mainnet.json", hyperevm: "https://reference-data-directory.vercel.app/feeds-hyperliquid-mainnet.json", + megaeth: "https://reference-data-directory.vercel.app/feeds-megaeth-mainnet.json", } // Path to the baseline JSON file that contains known feed IDs