# Get Started with CCIP (EVM)
Source: https://docs.chain.link/ccip/getting-started/evm
Last Updated: 2025-12-25

> For the complete documentation index, see [llms.txt](/llms.txt).

**Build and run a secure cross-chain messaging workflow between two EVM chains using Chainlink CCIP.**In this guide, you will: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> **NOTE: Please note**
>
> We use the same two smart contracts throughout the tutorial. You can check them out in the [Examine the example
> code](#examine-the-example-code) section.You will need:* Basic [Solidity](https://soliditylang.org/) and [smart contract deployment](/quickstarts/deploy-your-first-contract) experience

* 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.

* 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/)**## Examine the example codeThis 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.### 1. Sender codeThe 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.```sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

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/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.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

/// @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
  );

  IRouterClient private s_router;

  LinkTokenInterface private s_linkToken;

  /// @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);
  }

  /// @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)
    });

    // Get the fee required to send the message
    uint256 fees = s_router.getFee(destinationChainSelector, evm2AnyMessage);

    if (fees > s_linkToken.balanceOf(address(this))) {
      revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
    }

    // 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);

    // Send the message through the router and store the returned message ID
    messageId = s_router.ccipSend(destinationChainSelector, evm2AnyMessage);

    // Emit an event with message details
    emit MessageSent(messageId, destinationChainSelector, receiver, text, address(s_linkToken), fees);

    // Return the message ID
    return messageId;
  }
}
```#### Initializing the contractWhen deploying the contract, you define the router address and the LINK contract address of the blockchain where you choose to deploy the contract.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 dataThe `sendMessage` function completes several operations: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.

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.

4. Grant the router contract permission to deduct the fees from the contract's LINK balance.

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).> **CAUTION: Best Practices**
>
> This example is simplified for educational purposes. For production code, please adhere to the following best practices:
>
> - **Do Not Hardcode `extraArgs`**: In this example, `extraArgs` are hardcoded within the contract for simplicity. It is recommended to make `extraArgs` mutable. For instance, you can construct `extraArgs` off-chain and pass them into your function calls, or store them in a storage variable that can be updated as needed. This approach ensures that `extraArgs` remain backward compatible with future CCIP upgrades. Refer to the [Best Practices](/ccip/concepts/best-practices/evm) guide for more information.
>
> - **Validate the Destination Chain**: Always ensure that the destination chain is valid and supported before sending messages.
>
> - **Understand `allowOutOfOrderExecution` Usage**: This example sets `allowOutOfOrderExecution` to `true` (see [GenericExtraArgsV2](/ccip/api-reference/evm/v1.6.1/client#genericextraargsv2)). Read the [Best Practices: Setting `allowOutOfOrderExecution`](/ccip/concepts/best-practices/evm#setting-allowoutoforderexecution) to learn more about this parameter.
>
> - **Understand CCIP Service Limits**: Review the [CCIP Service Limits](/ccip/service-limits) for constraints on message data size, execution gas, and the number of tokens per transaction. If your requirements exceed these limits, you may need to [contact the Chainlink Labs Team](https://chain.link/ccip-contact).
>
> Following these best practices ensures that your contract is robust, future-proof, and compliant with CCIP standards.### 2. Receiver codeThe 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.```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";

/**
 * 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.
 */

/// @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
  );

  bytes32 private s_lastReceivedMessageId; // Store the last received messageId.
  string private s_lastReceivedText; // Store the last received text.

  /// @notice Constructor initializes the contract with the router address.
  /// @param router The address of the router contract.
  constructor(
    address router
  ) CCIPReceiver(router) {}

  /// 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))
    );
  }

  /// @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 contractWhen 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 dataOn the destination blockchain: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.> **CAUTION: \_ccipReceive**
>
> - It is critical for the receiver contract to implement this function.
>
> - The CCIP router will look for this function to deliver the message to the receiver contract.
>
> - The message won't be delivered to the receiver contract if this
>   function is not implemented correctly.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).> **CAUTION: Recommendations Receiver contract**
>
> The example was simplified for learning purposes. For production code, use the following best practices:
>
> - Validate the source chain.
> - Depending on your use case, analyze whether you should validate the sender address.
>
> Note that the receiver contract in this example inherits from the base contract [CCIPReceiver.sol](/ccip/api-reference/evm/v1.6.1/ccip-receiver), which uses the `onlyRouter` [modifier](/ccip/api-reference/evm/v1.6.1/ccip-receiver#onlyrouter) to ensure that only the router can call the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.1/ccip-receiver#ccipreceive).## Send a cross-chain message using CCIPSend and verify a cross-chain message using CCIP in under 10 minutes, with your favorite development framework.### Hardhat 3Best for a `Typescript` based scripting workflow where you deploy contracts, send a CCIP message, and verify delivery from the command line.### 1. Bootstrap a new Hardhat project1) Open a new terminal in a directory of your choice and run this command:```bash filename="Terminal"
npx hardhat --init
```Create a project with the following options:- Hardhat Version: hardhat-3
- Initialize project: At root of the project
- Type of project: A minimal Hardhat project
- Install the necessary dependencies: Yes2. Install the additional dependencies required by this tutorial:```bash filename="Terminal"
npm install @chainlink/contracts-ccip @chainlink/contracts viem
npm install --save-dev @nomicfoundation/hardhat-viem @nomicfoundation/hardhat-keystore
```> **CAUTION: WE HIGHLY RECOMMEND NOT USING PLAINTEXT PRIVATE KEYS**
>
> 1. It is not recommended to store your sensitive keys in plaintext in `.env` files.
> 2. In this tutorial we use in-built keystores for both Hardhat and Foundry for their respective projects.3) Update `hardhat.config.ts` to use the `hardhat-viem` and `hardhat-keystore` plugins:```typescript filename="hardhat.config.ts"
import { configVariable, defineConfig } from "hardhat/config"
import hardhatKeystore from "@nomicfoundation/hardhat-keystore"
import hardhatViem from "@nomicfoundation/hardhat-viem"

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")],
    },
  },
})
```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:```bash filename="Terminal"
npx hardhat keystore set SEPOLIA_RPC_URL
npx hardhat keystore set FUJI_RPC_URL
npx hardhat keystore set PRIVATE_KEY
```The output of `npx hardhat keystore list ` should look like this:![Hardhat keystore list command output](/images/ccip/tutorials/ccip-getting-started-evm-1.png)### 2. Set up the contracts1) Create a new directory named `contracts` for your smart contracts if it doesn't already exist.
2) Create a new file named `Sender.sol` in this directory and paste the [sender contract code](#examine-the-example-code) inside it.
3) Create a new file named `Receiver.sol` in the same directory and paste the [receiver contract code](#examine-the-example-code) inside it.
4) Run the following command to compile the contracts:```bash filename="Terminal"
npx hardhat build
```### 3. Send a cross-chain message using CCIP1) 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:```typescript filename="scripts/send-cross-chain-message.ts"
import { network } from "hardhat"
import { getContract, parseAbi, parseUnits } from "viem"

// Avalanche Fuji configuration
const FUJI_ROUTER = "0xF694E193200268f9a4868e4Aa017A0118C9a8177"
const FUJI_LINK = "0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846"

// 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

// Connect to Avalanche Fuji
console.log("Connecting to Avalanche Fuji...")
const fujiNetwork = await network.connect("avalancheFuji")

// Connect to Ethereum Sepolia
console.log("Connecting to Ethereum Sepolia...")
const sepoliaNetwork = await network.connect("sepolia")

// Step 1: Deploy Sender on Fuji
console.log("\n[Step 1] Deploying Sender contract on Avalanche Fuji...")

const sender = await fujiNetwork.viem.deployContract("Sender", [FUJI_ROUTER, FUJI_LINK])
const fujiPublicClient = await fujiNetwork.viem.getPublicClient()

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...")

const [fujiWalletClient] = await fujiNetwork.viem.getWalletClients()
if (!fujiWalletClient) {
  throw new Error("No wallet client available. Check PRIVATE_KEY + network config in hardhat.config.ts.")
}

// We create a minimal interface for the LINK token to be able to call the transfer function.

const linkTokenInterfaceAbi = parseAbi(["function transfer(address to, uint256 value) returns (bool)"])

const link = getContract({
  address: FUJI_LINK,
  abi: linkTokenInterfaceAbi,
  client: { public: fujiPublicClient, wallet: fujiWalletClient },
})

const transferLinkToFujiContract = await link.write.transfer([sender.address, parseUnits("1", 18)])

console.log("LINK token transfer in progress, awaiting confirmation...")
await fujiPublicClient.waitForTransactionReceipt({ hash: transferLinkToFujiContract, confirmations: 1 })
console.log(`Funded Sender with 1 LINK`)

// Step 3: Deploy Receiver on Sepolia
console.log("\n[Step 3] Deploying Receiver on Ethereum Sepolia...")

const receiver = await sepoliaNetwork.viem.deployContract("Receiver", [SEPOLIA_ROUTER])
const sepoliaPublicClient = await sepoliaNetwork.viem.getPublicClient()

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`)

// Step 4: Send cross-chain message
console.log("\n[Step 4] Sending cross-chain message...")

const sendMessageTx = await sender.write.sendMessage([
  SEPOLIA_CHAIN_SELECTOR,
  receiver.address,
  "Hello World from Hardhat script!",
])

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."
)
```This script does the following:- 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.> **NOTE: LINK token Interface**
>
> You will notice that we create a minimal interface for the `LINK` token as part of our script. Another option could
> have been to generate a compiled artifact out of an empty contract that extends the `LinkTokenInterface` from the
> `@chainlink/contracts` package.
>
> Which approach do you prefer?3. Run the following command to send the cross-chain message:```bash filename="Terminal"
npx hardhat run scripts/send-cross-chain-message.ts
```### 4. Verify message delivery1. Wait for a few minutes for the message to be delivered to the receiver contract.

2. Create a new file named `verify-cross-chain-message.ts` in the `scripts` directory and paste the following code inside it:> **NOTE: Paste the Receiver contract address here**
>
> The second script will call the `getLastReceivedMessageDetails` function on the receiver contract to verify if the
> message has been received. It will need the correct address to call the function.```typescript filename="scripts/verify-cross-chain-message.ts"
import { network } from "hardhat"

// Paste the Receiver contract address
const RECEIVER_ADDRESS = ""

console.log("Connecting to Ethereum Sepolia...")
const sepoliaNetwork = await network.connect("sepolia")

console.log("Checking for received message...\n")
const receiver = await sepoliaNetwork.viem.getContractAt("Receiver", RECEIVER_ADDRESS)

const [messageId, text] = await receiver.read.getLastReceivedMessageDetails()

// A null hexadecimal value means no message has been received yet
const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"

if (messageId === ZERO_BYTES32) {
  console.log("No message received yet.")
  console.log("Please wait a bit longer and try again.")
  process.exit(1)
} else {
  console.log(`✅ Message ID: ${messageId}`)
  console.log(`Text: "${text}"`)
}
```This script does the following:- Connects to the Ethereum Sepolia network.
- Reads the last received message details from the receiver contract.
- Checks if any message has been received.
- Prints the message ID and text of the last received message.3. Run the following command to verify the cross-chain message:```bash filename="Terminal"
npx hardhat run scripts/verify-cross-chain-message.ts
```4. You should see the message ID and text of the last received message printed in the terminal.### FoundryBest for **Solidity-native** workflows that prefer a modular, powerful scripting framework.### 1. Bootstrap a new Foundry project1. Open a new terminal in a directory of your choice and run this command to initialize a new Foundry project at the root:```bash filename="Terminal"
forge init
```2. Install the required dependencies:```bash filename="Terminal"
forge install smartcontractkit/chainlink-ccip smartcontractkit/chainlink-evm
```> **NOTE: Note**
>
> `cast wallet import` will throw an error if an `env` variable with the same name already exists. Use `cast wallet
>   list` to check previously set variables.3. Use Foundry's `cast` command to create a new keystore for your `PRIVATE_KEY`:```bash filename="Terminal"
cast wallet import --interactive PRIVATE_KEY
```And use the `cast wallet list` command to verify:![Foundry keystore list command output](/images/ccip/tutorials/ccip-getting-started-evm-2.png)> **CAUTION: Note**
>
> You will notice that while we pulled in our RPC URLs via encrypted keystore variables in the Hardhat project, we only
> encrypt the `PRIVATE_KEY` in Foundry. This is because as of of writing, Foundry supports keystore encryption only for
> Private keys, and not generic strings.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"
```### 2. Set up the contracts1. Create a new directory named `src` at the root of the project if it doesn't already exist.
2. Create a new file named `Sender.sol` in this directory and paste the [sender contract code](#examine-the-example-code) inside it.
3. Create a new file named `Receiver.sol` in the same directory and paste the [receiver contract code](#examine-the-example-code) inside it.
4. Run the following command to compile the contracts:```bash filename="Terminal"
forge build
```### 3. Send a cross-chain message using CCIP1. 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");
    }
}
```3. Run the following command to send the cross-chain message:```bash filename="Terminal"
forge script script/SendCrossChainMessage.s.sol:SendCrossChainMessage --broadcast --multi --account PRIVATE_KEY
```> **NOTE**
>
> - The `--broadcast` flag is used to broadcast transactions to an actual network.
> - You can get detailed runtime logs by using the `-vvvv` verbosity flag. - The `--multi` flag is used to send transactions to multiple chains.### 4. Verify message delivery1) Create a new file named `VerifyCrossChainMessage.s.sol` in the `script` directory and paste the following code inside it:```solidity filename="script/VerifyCrossChainMessage.s.sol"
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import {Script, console} from "forge-std/Script.sol";
import {Receiver} from "../src/Receiver.sol";

contract VerifyCrossChainMessage is Script {

    bytes32 constant ZERO_BYTES32 = bytes32(0);

    function run() public {

        address receiverAddress = PASTE_RECEIVER_ADDRESS_HERE;
        require(receiverAddress != address(0), "Set RECEIVER_ADDRESS");

        console.log("Connecting to Ethereum Sepolia...");
        uint256 sepoliaFork = vm.createFork(vm.rpcUrl("sepolia"));
        vm.selectFork(sepoliaFork);

        console.log("Checking for received message...\n");
        Receiver receiver = Receiver(receiverAddress);

        (bytes32 messageId, string memory text) = receiver
            .getLastReceivedMessageDetails();

        if (messageId == ZERO_BYTES32) {
            console.log("No message received yet.");
            console.log("Please wait a bit longer and try again.");
            revert("No message received yet");
        }

        console.log("Received Message ID:");
        console.logBytes32(messageId);
        console.log(string.concat('Received Text: "', text, '"'));
    }
}
```2) Run the following command to verify the cross-chain message:```bash filename="Terminal"
forge script script/VerifyCrossChainMessage.s.sol:VerifyCrossChainMessage
```### RemixBest for **Web3-native** workflows that prefer a browser-based IDE.### 1. Deploy the sender contractDeploy the `Sender.sol` contract on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#sender-code) section.1) Open the Sender.sol contract in Remix.

2) Compile the contract.

3) Deploy the sender contract on *Avalanche Fuji*:
   1. Open MetaMask and select the *Avalanche Fuji* network.

   2. In Remix under the **Deploy & Run Transactions** tab, select *Injected Provider - MetaMask* in the **Environment** list. Remix will use the MetaMask wallet to communicate with *Avalanche Fuji*.

   3. Under the **Deploy** section, fill in the router address and the LINK token contract addresses for your specific blockchain. You can find both of these addresses on the [CCIP Directory](/ccip/directory). The LINK token contract address is also listed on the [LINK Token Contracts](/resources/link-token-contracts) page. For *Avalanche Fuji*, the router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177 and the LINK address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.

      (Image: Chainlink CCIP deploy sender Avalanche Fuji)

   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*.

   5. After you confirm the transaction, the contract address appears in the **Deployed Contracts** list. Copy your contract address.

      (Image: Chainlink CCIP Deployed sender Avalanche Fuji)

   6. Open MetaMask and send 70 LINK to the contract address that you copied. Your contract will pay CCIP fees in LINK.

      **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.### 2. Deploy the receiver contractDeploy 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.1) [Open the Receiver.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol) contract in Remix.

   [Open Receiver.sol in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol)

2) Compile the contract.

3) Deploy the receiver contract on *Ethereum Sepolia*:
   1. Open MetaMask and select the *Ethereum Sepolia* network.

   2. In Remix under the **Deploy & Run Transactions** tab, make sure the **Environment** is still set to *Injected Provider - MetaMask*.

   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).

      (Image: Chainlink CCIP Deploy receiver Sepolia)

   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.

      (Image: Chainlink CCIP deployed receiver Sepolia)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.### 3. Send dataSend a `Hello World!` string from your contract on *Avalanche Fuji* to the contract you deployed on *Ethereum Sepolia*:1. Open MetaMask and select the *Avalanche Fuji* network.

2. In Remix under the **Deploy & Run Transactions** tab, expand the first contract in the **Deployed Contracts** section.

3. Expand the **sendMessage** function and fill in the following arguments:

   | 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!                   |

   (Image: Chainlink CCIP Sepolia send message)

4. Click the **transact** button to run the function. MetaMask prompts you to confirm the transaction.> **NOTE: Gas price spikes**
>
> Under normal circumstances, transactions on the Ethereum Sepolia network require significantly fewer tokens to pay for gas. However, during exceptional periods of high gas price spikes, your transactions may fail if not sufficiently funded. In such cases, you may need to fund your contract with additional tokens. We recommend paying for your CCIP transactions in **LINK** tokens (rather than native tokens) as you can obtain extra LINK testnet tokens from [faucets.chain.link](https://faucets.chain.link/). If you encounter a transaction failure due to these gas price spikes, please add additional LINK tokens to your contract and try again.
> Alternatively, you can use a supported testnet other than Sepolia.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*.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.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.

   (Image: Chainlink CCIP Explorer transaction details)

7. When the status of the transaction is marked with a "Success" status, the CCIP transaction and the destination transaction are complete.

   (Image: Chainlink CCIP Explorer transaction details success)### 4. Read dataRead data stored by the receiver contract on *Ethereum Sepolia*: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 should be "Hello World!".

   (Image: Chainlink CCIP Sepolia message details)Congratulations! You just sent your first cross-chain data using CCIP. Next, examine the example code to learn how this contract works.