Example 02: Hello World to BasicMessageReceiverWithCCVs with Fast Transfers (FTF)
This example deploys a destination BasicMessageReceiverWithCCVs contract and sends a Hello World data message from an EOA using Fast Transfers (blockConfirmations > 0).
Tasks and modules used:
- Deploy receiver: Hardhat Ignition module
ignition/modules/BasicMessageReceiverWithCCVs.ts(deploy on destination chain). - Configure min block depth: Hardhat task
set-basic-message-receiver-with-ccvs-min-block-depth(implementation:tasks/helpers/SetBasicMessageReceiverWithCCVsMinBlockDepth.ts). - Send message: Hardhat task
example02(implementation:tasks/Example02.ts). - Read latest message: Hardhat task
basic-message-receiver-latest-message(implementation:tasks/helpers/BasicMessageReceiverLatestMessage.ts).
What You Will Do
- Deploy
BasicMessageReceiverWithCCVson the destination chain using Ignition. - Configure minimum block depth for the source chain.
- Send a Hello World CCIP data message from the source chain to
BasicMessageReceiverWithCCVsusing theexample02task. - (Optional) Verify the received message with the
basic-message-receiver-latest-messagetask.
Before You Start
Important: Keystore first
Use a local keystore account for task execution:
npx hardhat keystore set AMOY_PRIVATE_KEY npx hardhat keystore set SEPOLIA_PRIVATE_KEY npx hardhat keystore set AMOY_RPC_URL npx hardhat keystore set SEPOLIA_RPC_URL
Receiver Contract Context
Pre-v2.0
To receive CCIP messages (data or data+tokens), contracts implemented IAny2EVMMessageReceiver:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IAny2EVMMessageReceiver {
function ccipReceive(
Client.Any2EVMMessage calldata message
) external;
}
As a convenience, applications generally inherited CCIPReceiver.sol and implemented _ccipReceive.
CCIP v2.0
In v2.0, receivers expose Cross Chain Verifiers and finality requirements via getCCVsAndFinalityConfig (encoded with FinalityCodec as bytes4 allowedFinalityConfig):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IAny2EVMMessageReceiverV2 {
function ccipReceive(
Client.Any2EVMMessage calldata message
) external;
function getCCVsAndFinalityConfig(
uint64 sourceChainSelector,
bytes calldata sender
)
external
view
returns (
address[] memory requiredCCVs,
address[] memory optionalCCVs,
uint8 optionalThreshold,
bytes4 allowedFinalityConfig
);
}
The receiver controls two independent dimensions:
requiredCCVsandoptionalCCVsdefine additional verifiers required for acceptance.allowedFinalityConfigdefines which requested finality modes are accepted (seeFinalityCodecin chainlink-ccip). A zero value (WAIT_FOR_FINALITY_FLAG) means only fully finalized messages are accepted.
You can combine these independently (for example, add verifiers while requiring full finality, or allow Fast Transfers when policy allows).
In this starter kit, contracts/BasicMessageReceiverWithCCVs.sol adds configurable verifier sets and stores a per-chain minimum block depth, which it exposes via FinalityCodec._encodeBlockDepth in getCCVsAndFinalityConfig.
Step 1: Deploy BasicMessageReceiverWithCCVs on Destination Chain
Deploy the receiver on the destination chain using Hardhat Ignition.
npx hardhat ignition deploy ignition/modules/BasicMessageReceiverWithCCVs.ts --network <NETWORK_NAME> --parameters <PARAMETERS_FILE>
--parameters must be the path to the chain’s Ignition parameters JSON (e.g. ignition/paramsEthSepolia.json or ignition/paramsAmoy.json). That file supplies router and other addresses to the module.
Save the deployed address as <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS>.
By default, this receiver starts with minBlockDepth = 0 (default finality only) for all source chains until you configure it.
Step 2: Configure Minimum Block Depth for Your Source Chain
On the destination chain, run:
npx hardhat set-basic-message-receiver-with-ccvs-min-block-depth --network <NETWORK_NAME> \
--receiver <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> \
--source-chain-selector <SOURCE_CHAIN_SELECTOR> \
--min-block-depth <MIN_BLOCK_DEPTH>
For this chapter, use <MIN_BLOCK_DEPTH> = 1.
0means default finality-only behavior for that source chain.> 0enables Fast Transfers with that minimum depth.
Step 3: Send Hello World Data Message from EOA
From the source chain, run the example02 task. Pass the deployed receiver address as --receiver and your message as --message-text.
npx hardhat example02 --network <NETWORK_NAME> \
--source-router <SOURCE_ROUTER> \
--destination-chain-selector <DESTINATION_CHAIN_SELECTOR> \
--receiver <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> \
--message-text "Hello, World" \
--gas-limit <GAS_LIMIT> \
--block-confirmations <BLOCK_CONFIRMATIONS_GT_ZERO> \
--fee-token-address <FEE_TOKEN_ADDRESS>
Parameter notes:
--gas-limitmust be> 0because the destination receiver callback needs gas (e.g.200000).--block-confirmationsmust be> 0for Fast Transfers.--block-confirmationsshould be greater than or equal to<MIN_BLOCK_DEPTH>.- Executor may enforce a minimum block confirmations value and revert if too low.
- If requested confirmations exceed chain finality, default finality is used.
--fee-token-address: LINK token address on the source chain to pay fees in LINK, or0x0000000000000000000000000000000000000000to pay in native coin.
Defaults (can be omitted): --gas-limit 200000, --block-confirmations 1, --fee-token-address 0x0000000000000000000000000000000000000000.
Verify Result
The example02 task logs the CCIP message ID and a link to monitor.
Monitor message status: https://ccip.chain.link
Read the latest message on the receiver (destination chain). The task decodes the stored bytes as the string that was sent:
npx hardhat basic-message-receiver-latest-message --network <NETWORK_NAME> --basic-message-receiver <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS>
Output is the decoded string (e.g. Hello, World). If no message has been received yet, the task prints (empty).