跳至主要内容
本页面使用机器翻译自英语,可能包含错误或不清楚的语言。如需最准确的信息,请参阅英文原文。由于更新频繁,部分内容可能与英文原文有出入。请加入我们在 Crowdin 上的努力,帮助我们改进本页面的翻译。 (Crowdin translation page, Contributing guide)

链锁 CCIP

导言

Chainlink Cross-Chain Interoperability Protocol (CCIP) 为开发者和去中心化应用程序(dApps)提供了一种安全、高效的跨区块链交互方式。 利用 CCIP,您可以发送代币和任意消息,以触发目标合约上的操作,如铸造 NFT、重新平衡指数或调用自定义函数。

在本教程中,您将学习如何使用 Chainlink CCIP 从 Kaia 智能合约向另一条链上的合约发送信息和代币,以及如何接收这些信息和代币。

先决条件

  • 代工厂安装
    • 使用 curl -L https://foundry.paradigm.xyz | bash 安装,然后运行 foundryup
    • 使用 forge --version, cast --versionanvil --version 进行验证。
  • MetaMask 钱包
    • 设置开发钱包
    • 在 MetaMask 中添加 Kaia Kairos 测试网络和以太坊 Sepolia 网络。
  • 从水龙头测试代币
    • KAIA:用于从 Kaia 部署和发送的气体。
    • LINK (testnet):用 LINK 支付 CCIP 费用。
    • 目标链上的本地代币(例如,Sepolia ETH:用于部署,如果选择,用于支付本地 CCIP 费用)。

入门

在本指南中,您将使用 Chainlink CCIP 在 Kaia(Kairos Testnet)和以太坊 Sepolia 之间收发跨链消息。

到最后,你会

  • 初始化为 Kairos 和 Sepolia 配置的 Foundry 项目
  • 将 Chainlink CCIP 合同和接口添加为依赖项
  • 执行信使合约,跨链收发信息
  • 部署到两个网络并验证往返信息

创建项目

在本节中,您将使用 Foundry 设置开发环境。 要创建一个新的 Foundry 项目,首先要创建一个新目录:


mkdir kaia-foundry-ccip-example

那就跑吧


cd kaia-foundry-ccip-example
forge init

这将创建一个具有以下基本布局的 Foundry 项目:


├── foundry.toml
├── script
├── src
└── test

要在 Foundry 项目中使用 Chainlink CCIP,需要使用 forge install 将 Chainlink CCIP 智能合约安装为项目依赖项。

要安装 Chainlink CCIP 智能合约,请运行


forge install smartcontractkit/chainlink-ccip@2114b90f39c82c052e05af7c33d42c1ae98f4180
forge install smartcontractkit/chainlink-evm@ff814eb0a01f89d9a215f825d243bf421e6434a9

安装完成后,创建一个 remapping.txt 文件:


forge remappings > remappings.txt

然后将以下内容粘贴到新创建的文件中:


@chainlink/contracts/=lib/chainlink-evm/contracts/
@chainlink/contracts-ccip/=lib/chainlink-ccip/chains/evm/contracts/

编写智能合约

在本节中,您将使用下面的代码跨链发送和接收信息。

在项目的 src 目录下创建一个名为 Messenger.sol 的新文件,并将下面的代码复制到该文件中:


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IRouterClient } from "@chainlink/contracts-ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol";
import { Client } from "@chainlink/contracts-ccip/libraries/Client.sol";
import { CCIPReceiver } from "@chainlink/contracts-ccip/applications/CCIPReceiver.sol";
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.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 messenger contract for sending/receiving string data across chains.
contract Messenger is CCIPReceiver, OwnerIsCreator {
using SafeERC20 for IERC20;
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance.
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
error SourceChainNotAllowlisted(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner.
error SenderNotAllowlisted(address sender); // Used when the sender has not been allowlisted by the contract owner.
error InvalidReceiverAddress(); // Used when the receiver address is 0.
// Event emitted when a message is sent to another chain.
event MessageSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address receiver, // The address of the receiver on the destination chain.
string text, // The text being sent.
address feeToken, // the token address used to pay CCIP fees.
uint256 fees // The fees paid for sending the CCIP message.
);
// Event emitted when a message is received from another chain.
event MessageReceived(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed sourceChainSelector, // The chain selector of the source chain.
address sender, // The address of the sender from the source chain.
string text // The text that was received.
);
bytes32 private s_lastReceivedMessageId; // Store the last received messageId.
string private s_lastReceivedText; // Store the last received text.
// Mapping to keep track of allowlisted destination chains.
mapping(uint64 => bool) public allowlistedDestinationChains;
// Mapping to keep track of allowlisted source chains.
mapping(uint64 => bool) public allowlistedSourceChains;
// Mapping to keep track of allowlisted senders.
mapping(address => bool) public allowlistedSenders;
IERC20 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) CCIPReceiver(_router) {
s_linkToken = IERC20(_link);
}
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
modifier onlyAllowlistedDestinationChain(uint64 _destinationChainSelector) {
if (!allowlistedDestinationChains[_destinationChainSelector])
revert DestinationChainNotAllowlisted(_destinationChainSelector);
_;
}
/// @dev Modifier that checks if the chain with the given sourceChainSelector is allowlisted and if the sender is allowlisted.
/// @param _sourceChainSelector The selector of the destination chain.
/// @param _sender The address of the sender.
modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) {
if (!allowlistedSourceChains[_sourceChainSelector])
revert SourceChainNotAllowlisted(_sourceChainSelector);
if (!allowlistedSenders[_sender]) revert SenderNotAllowlisted(_sender);
_;
}
/// @dev Modifier that checks the receiver address is not 0.
/// @param _receiver The receiver address.
modifier validateReceiver(address _receiver) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
/// @dev Updates the allowlist status of a destination chain for transactions.
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedDestinationChains[_destinationChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a source chain for transactions.
function allowlistSourceChain(
uint64 _sourceChainSelector,
bool allowed
) external onlyOwner {
allowlistedSourceChains[_sourceChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a sender for transactions.
function allowlistSender(address _sender, bool allowed) external onlyOwner {
allowlistedSenders[_sender] = allowed;
}
/// @notice Sends data to receiver on the destination chain.
/// @notice Pay for fees in LINK.
/// @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 text to be sent.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayLINK(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
address(s_linkToken)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = 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(router), fees);
// Send the CCIP message through the router and store the returned CCIP message ID
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
address(s_linkToken),
fees
);
// Return the CCIP message ID
return messageId;
}
/// @notice Sends data to receiver on the destination chain.
/// @notice Pay for fees in native gas.
/// @dev Assumes your contract has sufficient native gas tokens.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _text The text to be sent.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayNative(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
address(0)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > address(this).balance)
revert NotEnoughBalance(address(this).balance, fees);
// Send the CCIP message through the router and store the returned CCIP message ID
messageId = router.ccipSend{value: fees}(
_destinationChainSelector,
evm2AnyMessage
);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
address(0),
fees
);
// Return the CCIP message ID
return messageId;
}
/// handle a received message
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
)
internal
override
onlyAllowlisted(
any2EvmMessage.sourceChainSelector,
abi.decode(any2EvmMessage.sender, (address))
) // Make sure source chain and sender are allowlisted
{
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 Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for sending a text.
/// @param _receiver The address of the receiver.
/// @param _text The string data to be sent.
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
function _buildCCIPMessage(
address _receiver,
string calldata _text,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return
Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: abi.encode(_text), // ABI-encoded string
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array as no tokens are transferred
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 to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
}
/// @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);
}
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is sent to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
/// It should only be callable by the owner of the contract.
/// @param _beneficiary The address to which the Ether should be sent.
function withdraw(address _beneficiary) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent, ) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
/// @param _beneficiary The address to which the tokens will be sent.
/// @param _token The contract address of the ERC20 token to be withdrawn.
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = IERC20(_token).balanceOf(address(this));
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}

上面的代码是一个双向 CCIP 合约,可在允许列表的链上发送和接收字符串信息,并带有所有者控制、LINK 或本地费用支付功能。 让我们来看看本合同中要用到的主要功能:

1. 所有列表

  • allowlistSourceChain(selector, allowed):控制允许哪些源链向此合约传递消息。
  • allowlistDestinationChain(selector, allowed):控制允许向哪些目标链发送此合约。
  • allowlistedSenders[address](通过 allowlistSender(addr, allowed)):当信息到达时,限制信任源链上的哪些发件人地址。
备注

测试前在两端都设置好。 信息源必须信任发送方和链。 目的地也必须列入允许发送列表。

2. 发送信息

sendMessagePayLINK(selector, receiver, text):发送信息并在 LINK 中支付 CCIP 费用。 这将生成一条信息,报出费用,检查 LINK 余额,批准路由器,然后执行 ccipSend。 完成后,它会返回一个与发送信息相关的唯一 ID。

发送消息支付本地(选择器、接收器、文本):发送信息并以本地令牌支付 CCIP 费用。 这将生成一条信息,报出费用,检查本地余额,然后执行 ccipSend(值:费用)。 完成后,它会返回一个与发送信息相关的唯一 ID。

3. 建立信息

_buildCCIPMessage(receiver, text, feeTokenAddress) -> EVM2AnyMessage

  • 对接收器和文本进行编码
  • 不发送令牌(令牌数量为空)
  • 使用带有可配置 gasLimit 的 GenericExtraArgsV2 封装 extraArgs
  • 将 feeToken 设置为 LINK 或本地 address(0)。

4. 接收信息

CCIP 调用 _ccipReceive(...) 在目标链上。 合同:

  • 根据允许列表验证源链和发件人
  • 解码字符串
  • 将其存储为最后收到的有效载荷
  • 发射 MessageReceived
  • 通过以下方式读回最后一次接收的有效载荷: getLastReceivedMessageDetails() -> (messageId, text)

编译智能合约

要编译智能合约,请运行


forge build

部署智能合约

将钱包设置为部署器

在将智能合约部署到网络之前,您需要设置一个钱包作为部署器。 为此,您可以使用 cast wallet import 命令将钱包的私钥导入 Foundry 的安全加密密钥库:


cast wallet import deployer --interactive

运行上述命令后,系统会提示你输入私钥和用于签署交易的密码。

要确认钱包已作为部署者账户导入到 Foundry 项目中,请运行


cast wallet list

设置环境变量

要设置环境,请在项目主目录下创建 .env 文件,并为 Kairos Testnet 和 Ethereum Sepolia 添加 RPC URL、CCIP 链选择器CCIP 路由器地址LINK 令牌地址


KAIROS_RPC_URL="https://public-en-kairos.node.kaia.io"
ETH_SEPOLIA_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
KAIROS_CHAIN_SELECTOR=2624132734533621656
ETH_SEPOLIA_CHAIN_SELECTOR=16015286601757825753
KAIROS_ROUTER_ADDRESS="0x41477416677843fCE577748D2e762B6638492755"
ETH_SEPOLIA_ROUTER_ADDRESS="0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59"
KAIROS_LINK_ADDRESS="0xAF3243f975afe2269Da8Ffa835CA3A8F8B6A5A36"
ETH_SEPOLIA_LINK_ADDRESS="0x779877A7B0D9E8603169DdbD7836e478b4624789"

创建.env文件后,运行以下命令在当前命令行会话中加载环境变量:


source .env

完成合约编译和环境设置后,就可以部署智能合约了。

要使用 Foundry 部署智能合约,可以使用 forge create 命令。 该命令要求您指定要部署的智能合约、要部署到的网络的 RPC URL 和要部署的账户。

将发送方合同部署到 Kairos Testnet

要将 Sender 智能合约部署到 Kaia Kairos Testnet,请运行以下命令:


forge create --rpc-url $KAIROS_RPC_URL --account deployer --broadcast src/Messenger.sol:Messenger --constructor-args $KAIROS_ROUTER_ADDRESS $KAIROS_LINK_ADDRESS

出现提示时,输入之前导入钱包私钥时设置的密码。

运行上述命令后,合同将部署到 Kairos 测试网络上。 您可以使用 Kaiascan block explorer 查看部署状态和合同。

使您的合约能够向以太坊 Sepolia 上的接收方合约发送 CCIP 消息

首先,我们需要更新目标链的交易允许列表状态。 为此,请运行以下命令:


cast send `SENDER_DEPLOYED_ADDRESS` --rpc-url $KAIROS_RPC_URL "allowlistDestinationChain(uint64, bool)" $ETH_SEPOLIA_CHAIN_SELECTOR true --account deployer

上面的代码调用 allowlistDestinationChain() 来设置发送方合约允许的目标链选择器。 每个链选择器都可以在 [CCIP 目录] (https://docs.chain.link/ccip/directory) 中找到。

在以太坊 Sepolia 上部署接收器合约

要将 Receiver 智能合约部署到以太坊 Sepolia,请运行以下命令:


forge create --rpc-url $ETH_SEPOLIA_RPC_URL --account deployer --broadcast src/Messenger.sol:Messenger --constructor-args $ETH_SEPOLIA_ROUTER_ADDRESS $ETH_SEPOLIA_LINK_ADDRESS

出现提示时,输入之前导入钱包私钥时设置的密码。

运行上述命令后,合约将部署到以太坊 Sepolia 上。 您可以使用 ETH Sepolia 区块浏览器 查看部署状态和合约。

启用您的合同,以便从 Kairos Testnet 上的发件人合同接收 CCIP 信息

首先,我们需要更新交易源链的允许列表状态。 为此,请运行以下命令:


cast send `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $ETH_SEPOLIA_RPC_URL "allowlistSourceChain(uint64, bool)" $KAIROS_CHAIN_SELECTOR true --account deployer

上面的代码调用 allowlistSourceChain() 来设置接收器合约允许使用的源代码链选择器。 每个链选择器都可以在 [CCIP 目录] (https://docs.chain.link/ccip/directory) 中找到。

启用您的合同,以便从 Kairos Testnet 上的发件人合同接收 CCIP 信息

要更新发件人的交易允许列表状态,请运行以下命令:


cast send `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $ETH_SEPOLIA_RPC_URL "allowlistSender(address, bool)" 0x12798F1E2013A110E3C8B23aC1f36c00B8DFD4d9 true --account deployer

备注

此时,您在 Kairos Testnet 上有一个发送方合约,在 Ethereum Sepolia 上有一个接收方合约。 作为安全措施,您启用了发送方合约,以便向 Ethereum Sepolia 发送 CCIP 消息,并启用了接收方合约,以便从发送方和 Kairos Testnet 接收 CCIP 消息。

为智能合约提供资金

为了支付发送信息的相关费用,发送方合约需要持有 LINK 代币余额,如果发送数据并使用原生代币支付,则需要持有 ETH 和 KAIA。

链接

直接从您的钱包或通过运行以下指令为您的合约注资:


cast send $KAIROS_LINK_ADDRESS --rpc-url $KAIROS_RPC_URL "transfer(address,uint256)" `SENDER_DEPLOYED_ADDRESS` 5000000000000000000 --account deployer

上述命令将在 Kairos Testnet 上向发送方合约发送 5 个 LINK 令牌。

备注

在运行所提供的施放命令之前,请将 SENDER_DEPLOYED_ADDRESS 替换为已部署的发送方合同的合同地址。

与智能合约互动

在本节中,您将使用 Foundry cast 命令行工具与已部署的智能合约进行交互,并调用它们的功能。

在此步骤中,您将使用 CCIP 发送短信,使用 CCIP 的费用将在 LINK 中支付。

为此,您需要使用 cast 命令调用部署到 Kairos Testnet 的发送方合约上的 sendMessagePayLINK(uint64, address, string) 函数,以便向以太坊 Sepolia 上的接收方合约发送消息数据。

要调用发送方智能合约的_sendMessagePayLINK(uint64, address, string)_ 函数,请运行


cast send `SENDER_DEPLOYED_ADDRESS` --rpc-url $KAIROS_RPC_URL "sendMessagePayLINK(uint64, address, string)" $ETH_SEPOLIA_CHAIN_SELECTOR `RECEIVER_DEPLOYED_ADDRESS` "gKaia builders" --account deployer

上述命令调用 sendMessagePayLINK(uint64, address, string) 发送信息。 传递给该方法的参数包括目标链(Ethereum Sepolia)的链选择器、接收方合约地址以及要包含在消息中的文本数据(Hello Builders)。

运行该命令后,将返回一个唯一的 messageId。

交易完成后,CCIP 会花几分钟时间将数据传送到 Ethereum Sepolia,并调用 Receiver 合约上的 ccipReceive 函数。 要验证跨链交易,请打开 CCIP explorer 并使用交易哈希值进行搜索。

接下来要做的是检查目的地链上的接收器合同。 为此,您需要运行下面的命令调用 getLastReceivedMessageDetails()


cast call `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $ETH_SEPOLIA_RPC_URL "getLastReceivedMessageDetails()"

备注

在运行所提供的 cast 命令之前,请将 RECEIVER_DEPLOYED_ADDRESS 替换为已部署的 Receiver 合同的合同地址。

收到的文本和信息 ID 会以十六进制数据的形式返回,就像这样:


0x6fe4577cdbf2ebf73a9023b3dd9818f990879fec890ac92cf6b8d6f8bc5e59640000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e48656c6c6f206275696c64657273000000000000000000000000000000000000

要将十六进制数据转换为字符串,特别是接收到的文本,请运行下面的命令:


cast to-utf8 e48656c6c6f206275696c64657273000000000000000000000000000000000000

现在你应该看到 Hello builders 意味着我们的跨链操作成功了。

备注

这些示例合同旨在双向运行。 您可以使用它们将数据从 Kairos Testnet 发送到 Ethereum Sepolia,再从 Ethereum Sepolia 发送回 Kairos Testnet。

发送数据并以本地方式支付

在此部分,您将用 CCIP 发送短信,并用本地令牌支付费用。 您将从 Ethereum Sepolia 向 Kaia(Kairos Testnet)发送数据。 这就颠倒了先前的方向,因此 Sepolia 合约充当发送方,而 Kairos 合约充当接收方。

首先,我们需要用以太坊 Sepolia 上的 ETH 为发送方合约提供资金。 为此,请运行以下 cast 命令:


cast send --rpc-url $ETH_SEPOLIA_RPC_URL `SENDER_DEPLOYED_ADDRESS` --value 300000000000000000 --account deployer

这将在以太坊 Sepolia 上向您的发送者合约发送 0.3 ETH

备注

将 SENDER_DEPLOYED_ADDRESS 替换为发件人合同地址。

下一步是在以太坊 Sepolia 上允许发送者合约的目的链。 为此,请运行以下命令:


cast send `SENDER_DEPLOYED_ADDRESS` --rpc-url $ETH_SEPOLIA_RPC_URL "allowlistDestinationChain(uint64, bool)" $KAIROS_CHAIN_SELECTOR true --account deployer

下一步是允许从 Kairos Testnet 上的接收器合同中获取源链。 为此,请运行以下命令:


cast send `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $KAIROS_RPC_URL "allowlistSourceChain(uint64, bool)" $ETH_SEPOLIA_CHAIN_SELECTOR true --account deployer

然后运行以下命令,在 Kairos Testnet 的接收器合约上执行 allowlistSender:


cast send `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $KAIROS_RPC_URL "allowlistSender(address, bool)" 0x09a0CF7628c64c683B9d61a8B9EBc14BB984c65c true --account deployer

将合约连接起来后,就可以运行这条命令向接收合约发送数据了:


cast send `SENDER_DEPLOYED_ADDRESS` --rpc-url $ETH_SEPOLIA_RPC_URL "sendMessagePayNative(uint64, address, string)" $KAIROS_CHAIN_SELECTOR 0x12798F1E2013A110E3C8B23aC1f36c00B8DFD4d9 "gKaia Builders" --account deployer

上述命令调用 sendMessagePayNative(uint64, address, string) 发送信息。 传递给该方法的参数包括目标链(Kairos Testnet)的链选择器、接收器合同地址以及要包含在信息中的文本数据(gKaia Builders)。

运行该命令后,将返回一个唯一的 messageId。

交易完成后,CCIP 会花几分钟时间将数据传送到 Kairos Testnet,并调用接收器合同上的 ccipReceive 函数。 要验证跨链交易,请打开 CCIP explorer 并使用交易哈希值进行搜索。

接下来要做的是检查目的地链上的接收器合同。 为此,您需要运行下面的命令调用 getLastReceivedMessageDetails()


cast call `RECEIVER_DEPLOYED_ADDRESS` --rpc-url $KAIROS_RPC_URL "getLastReceivedMessageDetails()"

备注

将 RECEIVER_DEPLOYED_ADDRESS 替换为接收方合同的合同地址。

收到的文本和信息 ID 会以十六进制数据形式返回,就像这样:


0xb4c00b6de96488f16868a8c12878d491a85c64173078650a8ffad8f67e759f800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e674b616961206275696c64657273000000000000000000000000000000000000

要将十六进制数据转换为字符串,特别是接收到的文本,请运行下面的命令:


cast to-utf8 674b616961206275696c64657273000000000000000000000000000000000000

现在你应该看到 gKaia builders,这意味着我们的跨链操作成功了。

结论

在本教程中,您将学习如何使用 Chainlink CCIP 从 Kaia Kairos Testnet 向另一条链 Ethereum Sepolia 发送消息,反之亦然。 有关 Chainlink CCIP 及其工作原理的更多深入指南,请参阅 Chainlink CCIP Documentation

让这个页面变得更好