Send Arbitrary Data

In this tutorial, you will use Chainlink CCIP to send data between smart contracts on different blockchains. First, you will pay for the CCIP fees on the source blockchain using LINK. Then, you will use the same contract to pay CCIP fees in native gas tokens. For example, you would use ETH on Ethereum or AVAX on Avalanche.

Before you begin

Tutorial

In this tutorial, you will send a string text between smart contracts on Avalanche Fuji and Ethereum Sepolia using CCIP. First, you will pay CCIP fees in LINK, then you will pay CCIP fees in native gas.

undefined

Deploy your contracts

To use this contract:

  1. Open the contract in Remix.

  2. Compile your contract.

  3. Deploy your sender contract on Avalanche Fuji and enable sending messages to Ethereum Sepolia:

    1. Open MetaMask and select the network Avalanche Fuji.
    2. In Remix IDE, click on Deploy & Run Transactions and select Injected Provider - MetaMask from the environment list. Remix will then interact with your MetaMask wallet to communicate with Avalanche Fuji.
    3. Fill in the router address and the link address for your network. You can find the router address on the CCIP Directory and the LINK token address on the LINK Token contracts page. For Avalanche Fuji:
      • The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
      • The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
    4. Click on transact. After you confirm the transaction, the contract address appears on the Deployed Contracts list. Note your contract address.
    5. Enable your contract to send CCIP messages to Ethereum Sepolia:
      1. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Avalanche Fuji.
      2. Call the allowlistDestinationChain with 16015286601757825753 as the destination chain selector, and true as allowed. Each chain selector is found on the CCIP Directory.
  4. Deploy your receiver contract on Ethereum Sepolia and enable receiving messages from your sender contract:

    1. Open MetaMask and select the network Ethereum Sepolia.
    2. In Remix IDE, under Deploy & Run Transactions, make sure the environment is still Injected Provider - MetaMask.
    3. Fill in the router address and the LINK address for your network. You can find the router address on the CCIP Directory and the LINK contract address on the LINK token contracts page. For Ethereum Sepolia:
      • The router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59,
      • The LINK contract address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
    4. Click on transact. After you confirm the transaction, the contract address appears on the Deployed Contracts list. Note your contract address.
    5. Enable your contract to receive CCIP messages from Avalanche Fuji:
      1. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Ethereum Sepolia.
      2. Call the allowlistSourceChain with 14767482510784806043 as the source chain selector, and true as allowed. Each chain selector is found on the CCIP Directory.
    6. Enable your contract to receive CCIP messages from the contract that you deployed on Avalanche Fuji:
      1. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Ethereum Sepolia.
      2. Call the allowlistSender with the contract address of the contract that you deployed on Avalanche Fuji, and true as allowed.

At this point, you have one sender contract on Avalanche Fuji and one receiver contract on Ethereum Sepolia. As security measures, you enabled the sender contract to send CCIP messages to Ethereum Sepolia and the receiver contract to receive CCIP messages from the sender and Avalanche Fuji. Note: Another security measure enforces that only the router can call the _ccipReceive function. Read the explanation section for more details.

You will use CCIP to send a text. The CCIP fees for using CCIP will be paid in LINK. Read this explanation for a detailed description of the code example.

  1. Open MetaMask and connect to Avalanche Fuji. Fund your contract with LINK tokens. You can transfer 70 LINK to your contract. In this example, LINK is used to pay the CCIP fees.

    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 or use a supported testnet other than Sepolia.

  2. Send "Hello World!" from Avalanche Fuji:

    1. Open MetaMask and select the network Avalanche Fuji.

    2. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Avalanche Fuji.

    3. Fill in the arguments of the sendMessagePayLINK function:


      ArgumentDescriptionValue (Ethereum Sepolia)
      _destinationChainSelectorCCIP Chain identifier of the target blockchain. You can find each network's chain selector on the CCIP Directory16015286601757825753
      _receiverThe destination smart contract addressYour deployed receiver contract address
      _textany stringHello World!
    4. Click on transact and confirm the transaction on MetaMask.

    5. Once the transaction is successful, note the transaction hash. Here is an example of a transaction on Avalanche Fuji.

  3. Open the CCIP explorer and search your cross-chain transaction using the transaction hash.

  4. The CCIP transaction is completed once the status is marked as "Success". Note: In this example, the CCIP message ID is 0x28a804fa891bde8fb4f6617931187e1033a128c014aa76465911613588bc306f.


  5. Check the receiver contract on the destination chain:

    1. Open MetaMask and select the network Ethereum Sepolia.

    2. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Ethereum Sepolia.

    3. Call the getLastReceivedMessageDetails.


    4. Notice the received text is the one you sent, "Hello World!" and the message ID is the one you expect 0x28a804fa891bde8fb4f6617931187e1033a128c014aa76465911613588bc306f.

Note: These example contracts are designed to work bi-directionally. As an exercise, you can use them to send data from Avalanche Fuji to Ethereum Sepolia and from Ethereum Sepolia back to Avalanche Fuji.

Send data and pay in native

You will use CCIP to send a text. The CCIP fees for using CCIP will be paid in native gas. Read this explanation for a detailed description of the code example.

  1. Open MetaMask and connect to Avalanche Fuji. Fund your contract with AVAX. You can transfer 1 AVAX to your contract. In this example, AVAX is used to pay the CCIP fees.

  2. Send "Hello World!" from Avalanche Fuji:

    1. Open MetaMask and select the network Avalanche Fuji.

    2. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Avalanche Fuji.

    3. Fill in the arguments of the sendMessagePayNative function:


      ArgumentDescriptionValue (Ethereum Sepolia)
      _destinationChainSelectorCCIP Chain identifier of the target blockchain. You can find each network's chain selector on the CCIP Directory16015286601757825753
      _receiverThe destination smart contract addressYour deployed receiver contract address
      _textany stringHello World!
    4. Click on transact and confirm the transaction on MetaMask.

    5. Once the transaction is successful, note the transaction hash. Here is an example of a transaction on Avalanche Fuji.

  3. Open the CCIP explorer and search your cross-chain transaction using the transaction hash.


  4. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is 0xb8cb414128f440e115dcd5d6ead50e14d250f9a47577c38af4f70deb14191457. Note that CCIP fees are denominated in LINK. Even if CCIP fees are paid using native gas tokens, node operators will be paid in LINK.


  5. Check the receiver contract on the destination chain:

    1. Open MetaMask and select the network Ethereum Sepolia.

    2. In Remix IDE, under Deploy & Run Transactions, open the list of transactions of your smart contract deployed on Ethereum Sepolia.

    3. Call the getLastReceivedMessageDetails.


    4. Notice the received text is the one you sent, "Hello World!" and the message ID is the one you expect 0xb8cb414128f440e115dcd5d6ead50e14d250f9a47577c38af4f70deb14191457.

Note: These example contracts are designed to work bi-directionally. As an exercise, you can use them to send data from Avalanche Fuji to Ethereum Sepolia and from Ethereum Sepolia back to Avalanche Fuji.

Explanation

The smart contract featured in this tutorial is designed to interact with CCIP to send and receive messages. The contract code contains supporting comments clarifying the functions, events, and underlying logic. Here we will further explain initializing the contract and sending and receiving data.

Initializing of the contract

When deploying the contract, we define the router address and LINK contract address of the blockchain we deploy the contract on. Defining the router address is useful for the following:

  • Sender part:

    • Calls the router's getFee function to estimate the CCIP fees.
    • Calls the router's ccipSend function to send CCIP messages.
  • Receiver part:

    • The contract inherits from CCIPReceiver, which serves as a base contract for receiver contracts. This contract requires that child contracts implement the _ccipReceive function. _ccipReceive is called by the ccipReceive function, which ensures that only the router can deliver CCIP messages to the receiver contract.

The sendMessagePayLINK function undertakes five primary operations:

  1. Call the _buildCCIPMessage private function to construct a CCIP-compatible message using the EVM2AnyMessage struct:

    • The _receiver address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through abi.encode.

    • The data is encoded from a string to bytes using abi.encode.

    • The tokenAmounts is an empty EVMTokenAmount struct array as no tokens are transferred.

    • The extraArgs specifies the gasLimit for relaying the message to the recipient contract on the destination blockchain. In this example, the gasLimit is set to 200000.

    • The _feeTokenAddress designates the token address used for CCIP fees. Here, address(linkToken) signifies payment in LINK.

  2. Computes the fees by invoking the router's getFee function.

  3. Ensures your contract balance in LINK is enough to cover the fees.

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

  5. Dispatches the CCIP message to the destination chain by executing the router's ccipSend function.

Note: As a security measure, the sendMessagePayLINK function is protected by the onlyAllowlistedDestinationChain, ensuring the contract owner has allowlisted a destination chain.

Sending data and pay in native

The sendMessagePayNative function undertakes four primary operations:

  1. Call the _buildCCIPMessage private function to construct a CCIP-compatible message using the EVM2AnyMessage struct:

    • The _receiver address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through abi.encode.

    • The data is encoded from a string to bytes using abi.encode.

    • The tokenAmounts is an empty EVMTokenAmount struct array as no tokens are transferred.

    • The extraArgs specifies the gasLimit for relaying the message to the recipient contract on the destination blockchain. In this example, the gasLimit is set to 200000.

    • The _feeTokenAddress designates the token address used for CCIP fees. Here, address(0) signifies payment in native gas tokens (ETH).

  2. Computes the fees by invoking the router's getFee function.

  3. Ensures your contract balance in native gas is enough to cover the fees.

  4. Dispatches the CCIP message to the destination chain by executing the router's ccipSend function. Note: msg.value is set because you pay in native gas.

Note: As a security measure, the sendMessagePayNative function is protected by the onlyAllowlistedDestinationChain, ensuring the contract owner has allowlisted a destination chain.

Receiving data

On the destination blockchain, the router invokes the ccipReceive function which expects an Any2EVMMessage struct that contains:

  • The CCIP messageId.
  • The sourceChainSelector.
  • The sender address in bytes format. Given that the sender is known to be a contract deployed on an EVM-compatible blockchain, the address is decoded from bytes to an Ethereum address using the ABI specifications.
  • The data, which is also in bytes format. Given a string is expected, the data is decoded from bytes to a string using the ABI specifications.

This example applies three important security measures:

  • _ccipReceive is called by the ccipReceive function, which ensures that only the router can deliver CCIP messages to the receiver contract. See the onlyRouter modifier for more information.
  • The modifier onlyAllowlisted ensures that only a call from an allowlisted source chain and sender is accepted.

What's next

Get the latest Chainlink content straight to your inbox.