Send Arbitrary Data and Receive Transfer Confirmation: A -> B -> A
This tutorial will teach you how to use Chainlink CCIP to send arbitrary data between smart contracts on different blockchains and how to track the status of each sent message in the sender contract on the source chain. Tracking the status of sent messages allows your smart contracts to execute actions after the receiver acknowledges it received the message. In this example, the sender contract emits an event after it receives acknowledgment from the receiver.
Note: For simplicity, this tutorial demonstrates this pattern for sending arbitrary data. However, you are not limited to this application. You can apply the same pattern to programmable token transfers.
Before you begin
- This tutorial assumes you have completed the Send Arbitrary Data tutorial.
- Your account must have some AVAX tokens on Avalanche Fuji and ETH tokens on Ethereum Sepolia.
- Learn how to Acquire testnet LINK and Fund your contract with LINK.
Tutorial
In this tutorial, you will deploy a message tracker contract on the source blockchain (Avalanche Fuji) and an acknowledger on the destination blockchain (Ethereum Sepolia). Throughout the tutorial, you will pay for CCIP fees using LINK tokens. Here is a step-by-step breakdown:
- Sending and building a CCIP message: Initiate and send a message from the message tracker contract on Avalanche Fuji to the acknowledger contract on Ethereum Sepolia. The message tracker contract constructs a CCIP message that encapsulates a text string and establishes a tracking status for this message before sending it off.
- Receiving and acknowledging the message: After the acknowledger contract receives the text on Ethereum Sepolia, it sends back a CCIP message to the message tracker contract as an acknowledgment of receipt.
- Updating tracking status: After the message tracker receives the acknowledgment, the contract updates the tracking status of the initial CCIP message and emits an event to signal completion.
Deploy the message tracker (sender) contract
Deploy the MessageTracker.sol contract on Avalanche Fuji and enable it to send and receive CCIP messages to and from Ethereum Sepolia. You must also enable your contract to receive CCIP messages from the acknowledger contract.
-
Open the MessageTracker.sol contract in Remix.
Note: The contract code is also available in the Examine the code section.
-
Compile the contract.
-
Deploy the contract on Avalanche Fuji:
-
Open MetaMask and select the Avalanche Fuji network.
-
On the Deploy & Run Transactions tab in Remix, select Injected Provider - MetaMask in the Environment list. Remix will use the MetaMask wallet to communicate with Avalanche Fuji.
-
Under the Deploy section, fill in the router address and the LINK token contract address for your specific blockchain. You can find both of these addresses on the CCIP Directory. The LINK token contract address is also listed on the LINK Token Contracts page. For Avalanche Fuji:
- The router address is
0xF694E193200268f9a4868e4Aa017A0118C9a8177 - The LINK token address is
0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
- The router address is
-
Click transact to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract on Avalanche Fuji.
-
After you confirm the transaction, the contract address appears in the Deployed Contracts list. Copy your contract address.
-
Open MetaMask and send
70LINK to the contract address 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 or use a supported testnet other than Sepolia.
-
-
Allow the Ethereum Sepolia chain selector for both destination and source chains.
- On the Deploy & Run Transactions tab in Remix, expand the message tracker contract in the Deployed Contracts section.
- Call the
allowlistDestinationChainfunction with16015286601757825753as the destination chain selector for Ethereum Sepolia andtrueas allowed. - Call the
allowlistSourceChainfunction with16015286601757825753as the source chain selector for Ethereum Sepolia andtrueas allowed. You can find each network's chain selector on the CCIP Directory.
Deploy the acknowledger (receiver) contract
Deploy the Acknowledger.sol contract on Ethereum Sepolia and enable it to send and receive CCIP messages to and from Avalanche Fuji. You must also enable your contract to receive CCIP messages from the message tracker contract.
-
Open the Acknowledger.sol contract in Remix.
Note: The contract code is also available in the Examine the code section.
-
Compile the contract.
-
Deploy the contract on Ethereum Sepolia:
-
Open MetaMask and select the Ethereum Sepolia network.
-
On the Deploy & Run Transactions tab in Remix, make sure the Environment is still set to Injected Provider - MetaMask.
-
Under the Deploy section, fill in the router address and the LINK token contract address for your specific blockchain. You can find both of these addresses on the CCIP Directory. The LINK token contract address is also listed on the LINK Token Contracts page. For Ethereum Sepolia:
- The Router address is
0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59. - The LINK token address is
0x779877A7B0D9E8603169DdbD7836e478b4624789.
- The Router address is
-
Click transact 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.
-
After you confirm the transaction, the contract address appears in the Deployed Contracts list. Copy this contract address.
-
Open MetaMask and send
70LINK 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 or use a supported testnet other than Sepolia.
-
-
Allow the Avalanche Fuji chain selector for both destination and source chains. You must also enable your acknowledger contract to receive CCIP messages from the message tracker you deployed on Avalanche Fuji.
-
On the Deploy & Run Transactions tab in Remix, expand the acknowledger contract in the Deployed Contracts section. Expand the
allowlistDestinationChain,allowlistSender, andallowlistSourceChainfunctions and fill in the following arguments:Function Description Value (Avalanche Fuji) allowlistDestinationChain CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the CCIP Directory 14767482510784806043,trueallowlistSender The address of the message tracker contract deployed on Avalanche Fuji Your deployed contract address, trueallowlistSourceChain CCIP Chain identifier of the source blockchain. You can find each network's chain selector on the CCIP Directory 14767482510784806043,true -
Open MetaMask and select the Ethereum Sepolia network.
-
For each function you expanded and filled in the arguments for, click the transact button to call the function. MetaMask prompts you to confirm the transaction. Wait for each transaction to succeed before calling the following function.
-
-
Finally, enable your message tracker contract to receive CCIP messages from the acknowledger contract you deployed on Ethereum Sepolia.
-
On the Deploy & Run Transactions tab in Remix, expand the message tracker contract in the Deployed Contracts section. Expand the
allowlistSenderfunction and fill in your acknowledger contract address andtrueas allowed. -
Open MetaMask and select the Avalanche Fuji network.
-
Click transact to call the function. MetaMask prompts you to confirm the transaction.
-
At this point, you have one message tracker (sender) contract on Avalanche Fuji and one acknowledger (receiver) contract on Ethereum Sepolia. You sent 70 LINK to the message tracker contract and 70 LINK to the acknowledger contract to pay the CCIP fees.
Note: This transaction fee is significantly higher than normal due to gas spikes on Sepolia.
Send data and track the message status
Initial message
-
Send a
Hello World!string from your message tracker contract on Avalanche Fuji to your acknowledger contract deployed on Ethereum Sepolia. You will track the status of this message during this tutorial.-
Open MetaMask and select the Avalanche Fuji network.
-
On the Deploy & Run Transactions tab in Remix, expand the message tracker contract in the Deployed Contracts section.
-
Expand the sendMessagePayLINK 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 16015286601757825753receiver The destination smart contract address Your deployed acknowledger contract address text Any stringHello World! -
Click transact to call the function. MetaMask prompts you to confirm the transaction.
-
Upon transaction success, expand the last transaction in the Remix log and copy the transaction hash. In this example, it is
0x1f88abc33a4ab426a5466e01d9e5fe8a2b96d6a6e5cedb643a674489c74126b4.
-
-
Open the CCIP Explorer and use the transaction hash that you copied to search for your cross-chain transaction.
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
ccipReceivefunction on your acknowledger contract. -
Copy the message ID from the CCIP Explorer transaction details. You will use this message ID to track your message status on the message tracker contract. In this example, it is
0xdd8be2f5f5d5cf3b8640c62924025b311ae83c6144f0f2ed5c24637436d6aab8. -
On the Deploy & Run Transactions tab in Remix, expand your message tracker contract in the Deployed Contracts section.
-
Paste the message ID you copied from the CCIP explorer as the argument in the messagesInfo getter function. Click messagesInfo to read the message status.
Note the returned
status 1. This value indicates that the message tracker contract has updated your message status to theSentstatus as defined by theMessageStatusenumin the message tracker contract.// Enum is used to track the status of messages sent via CCIP. // `NotSent` indicates a message has not yet been sent. // `Sent` indicates that a message has been sent to the Acknowledger contract but not yet acknowledged. // `ProcessedOnDestination` indicates that the Acknowledger contract has processed the message and that // the Message Tracker contract has received the acknowledgment from the Acknowledger contract. enum MessageStatus { NotSent, // 0 Sent, // 1 ProcessedOnDestination // 2 } -
When the transaction is marked with a "Success" status on the CCIP Explorer, the CCIP transaction and the destination transaction are complete. The acknowledger contract has received the message from the message tracker contract.
Acknowledgment message
The acknowledger contract processes the message, sends an acknowledgment message containing the initial message ID back to the message tracker contract, and emits an AcknowledgmentSent event. Read this explanation for further description.
// Emitted when an acknowledgment message is successfully sent back to the sender contract.
// This event signifies that the Acknowledger contract has recognized the receipt of an initial message
// and has informed the original sender contract by sending an acknowledgment message,
// including the original message ID.
event AcknowledgmentSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address indexed receiver, // The address of the receiver on the destination chain.
bytes32 data, // The data being sent back, usually containing the message ID of the original message to acknowledge its receipt.
address feeToken, // The token address used to pay CCIP fees for sending the acknowledgment.
uint256 fees // The fees paid for sending the acknowledgment message via CCIP.
);
-
Copy your acknowledger contract address from Remix. Open the Ethereum Sepolia explorer and search for your deployed acknowledger contract. Click the Events tab to see the events log.
The first indexed topic (
topic1) in theAcknowledgmentSentevent is the acknowledgment message ID sent to the message tracker contract on Avalanche Fuji. In this example, the message ID is0xd4d4a5d0db05dc714f8150c1af654ed34eb8c9f7547401fa9bf072a815f56ac1. -
Copy your own message ID from the indexed
topic1and search for it in the CCIP explorer.When the transaction is marked with a "Success" status on the CCIP explorer, the CCIP transaction and the destination transaction are complete. The message tracker contract has received the message from the acknowledger contract.
Final status check
When the message tracker receives the acknowledgment message, the ccipReceive function updates the initial message status to 2, which corresponds to the ProcessedOnDestination status as defined by the MessageStatus enum. The function emits a MessageProcessedOnDestination event.
-
Open MetaMask and select the Avalanche Fuji network.
-
On the Deploy & Run Transactions tab in Remix, expand your message tracker contract in the Deployed Contracts section.
-
Copy the initial message ID from the CCIP explorer (transaction from Avalanche Fuji to Ethereum Sepolia) and paste it as the argument in the messagesInfo getter function. Click messagesInfo to read the message status. It returns
status 2and the acknowledgment message ID that confirms this status. -
Copy your message tracker contract address from Remix. Open the Avalanche Fuji explorer and search for your deployed message tracker contract. Then, click on the Events tab.
The
MessageProcessedOnDestinationevent is emitted with the acknowledged message ID0xdd8be2f5f5d5cf3b8640c62924025b311ae83c6144f0f2ed5c24637436d6aab8as indexedtopic2.// Event emitted when the sender contract receives an acknowledgment // that the receiver contract has successfully received and processed the message. event MessageProcessedOnDestination( bytes32 indexed messageId, // The unique ID of the CCIP acknowledgment message. bytes32 indexed acknowledgedMsgId, // The unique ID of the message acknowledged by the receiver. uint64 indexed sourceChainSelector, // The chain selector of the source chain. address sender // The address of the sender from the source chain. );
Explanation
The smart contracts featured in this tutorial are designed to interact with CCIP to send and receive messages with an acknowledgment of receipt mechanism. The contract code across both contracts contains supporting comments clarifying the functions, events, and underlying logic.
Refer to the Send Arbitrary Data tutorial for more explanation about initializing the contracts, sending data, paying in LINK, and receiving data.
Here, we will further explain the acknowledgment of receipt mechanism.
Message acknowledgment of receipt mechanism
This mechanism ensures that a message sent by the message tracker (sender) contract is received and acknowledged by the acknowledger (receiver) contract. The message status is tracked and stored in the message tracker contract.
// Enum is used to track the status of messages sent via CCIP.
// `NotSent` indicates a message has not yet been sent.
// `Sent` indicates that a message has been sent to the Acknowledger contract but not yet acknowledged.
// `ProcessedOnDestination` indicates that the Acknowledger contract has processed the message and that
// the Message Tracker contract has received the acknowledgment from the Acknowledger contract.
enum MessageStatus {
NotSent, // 0
Sent, // 1
ProcessedOnDestination // 2
}
// Struct to store the status and acknowledger message ID of a message.
struct MessageInfo {
MessageStatus status;
bytes32 acknowledgerMessageId;
}
// Mapping to keep track of message IDs to their info (status & acknowledger message ID).
mapping(bytes32 => MessageInfo) public messagesInfo;
Message tracker contract
The message tracker contract acts as the sender, initiating cross-chain communication. It performs the following operations:
-
Message sending: Constructs and sends messages to the acknowledger contract on another blockchain, using
sendMessagePayLINKfunction. On top of its five primary operations, thesendMessagePayLINKfunction also updates the message status upon sending. -
Status tracking:
-
Upon sending a message, the message tracker updates its internal state to mark the message as
Sent(status1). This status is pivotal for tracking the message lifecycle and awaiting acknowledgment.// Update the message status to `Sent` messagesInfo[messageId].status = MessageStatus.Sent; -
Upon receiving an acknowledgment message from the acknowledger contract, the message tracker contract updates the message status from
Sent(status1) toProcessedOnDestination(status2). This update indicates that the cross-chain communication cycle is complete, and the receiver successfully received and acknowledged the message.// Update the message status to `ProcessedOnDestination` messagesInfo[messageId].status = MessageStatus.ProcessedOnDestination;
-
Acknowledger contract
The acknowledger contract receives the message, sends back an acknowledgment message, and emits an event. It performs the following operations:
-
Message receipt: Upon receiving a message via CCIP, the
ccipReceivefunction decodes it and calls theacknowledgePayLINKfunction nested within theccipReceivefunction. -
Acknowledgment sending: The
acknowledgePayLINKfunction acts as a customsendMessagePayLINKfunction nested within theccipReceivefunction. It sends an acknowledgment (a CCIP message) to the message tracker contract upon the initial message receipt. The data transferred in this acknowledgment message is the initial message ID. It then emits anAcknowledgmentSentevent.
Security and integrity
Both contracts use allowlists to process only messages from and to allowed sources.
-
Allowlisting chains and senders:
- The
sendMessagePayLINKfunction is protected by theonlyAllowlistedDestinationChainmodifier, ensuring the contract owner has allowlisted a destination chain. - The
ccipReceivefunction is protected by theonlyAllowlistedmodifier, ensuring the contract owner has allowlisted a source chain and a sender.
- The
-
Ensuring the initial message authenticity: The message tracker contract first checks that the message awaiting acknowledgment was sent from the contract itself and is currently marked as
Sent. Once confirmed, the message status is updated toProcessedOnDestination.
Examine the code
MessageTracker.sol
undefined
Acknowledger.sol
undefined
Final note
In this example, the message tracker contract emits an event when it receives the acknowledgment message confirming the initial message reception and processing on the counterpart chain. However, you could think of any other logic to execute when the message tracker receives the acknowledgment. This tutorial demonstrates the pattern for sending arbitrary data, but you can apply the same pattern to programmable token transfers.