本文介绍了如何使用 Chainlink CCIP 将数据从一个链发送到另一个链。具体来说,展示了如何使用 Foundry 设置项目,编写一个 Solidity 函数,该函数可以将简单的字符串“hello world”从源链发送到目标链,并解释了关键的 CCIP 概念和函数,例如 ccipSend
和 EVM2AnyMessage
结构体。
在上一篇文章中,我尝试解释 CCIP 的架构,我在这里对 "尝试" 这个词很宽容。我知道那个解释不是最好的。但是从这篇文章开始,我们就进入了我的区域:编写和解释代码。所以做好准备,我们将编写一些代码,我将引导你完成它的每个部分。
你可能知道,CCIP 让我们可以在一条链向另一条链发送数据或代币。我们使用 CCIP 构建的第一步是了解如何发送数据。更具体地说,我们将把一个简单的字符串 "hello world" 从源链发送到目标链。
让我们设置 Foundry,这样我们就可以编写发送此数据的函数。在 VS Code 或任何编辑器中打开你的项目文件夹,并在终端中运行以下命令:
## 创建一个新的 Foundry 项目
forge init
## 安装依赖
forge install smartcontractkit/chainlink-evm@contracts-v1.4.0
forge install smartcontractkit/chainlink-ccip@contracts-ccip-v1.6.0
这将创建一个新的 Foundry 项目并安装我们将用于 CCIP 的 Chainlink 包。接下来,我们需要更新重映射。创建一个 remappings.txt
文件并添加以下行:
@chainlink/contracts/=lib/chainlink-evm/contracts/
@chainlink/contracts-ccip/contracts/=lib/chainlink-ccip/chains/evm/contracts/
在完成设置之前,请确保删除 src
、test
和 script
文件夹中的所有 .sol
文件。然后,在 src
文件夹中,创建一个名为 CCIPMessenger.sol
的新文件。这就是我们的合约所在的地方。现在设置已经完成,让我们来谈谈到本文末尾我们的合约会是什么样子。
我们将编写一个将字符串发送到另一条链上的合约的函数。当我们发送消息时,我们将发出一个事件,其中包含消息 ID(当我们调用 ccipSend
时由 CCIP 返回)、用户的地址和目标链选择器(这些选择器是 CCIP 支持的链的唯一 ID)。
sendMessage
如何工作?要使用 CCIP 发送消息,我们使用 Router 合约上的 ccipSend
函数。这是函数定义:
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
当我们调用它时,我们需要两件事:目标链选择器和一个 Client.EVM2AnyMessage
结构体。这是该结构体的样子:
struct EVM2AnyMessage {
bytes receiver; // abi.encode(receiver address) for destination EVM chains.
bytes data; // Data payload.
EVMTokenAmount[] tokenAmounts; // Token transfers.
address feeToken; // Address of fee token. address(0) = native token.
bytes extraArgs; // Use _argsToBytes(EVMExtraArgsV2) to populate.
}
这些字段中的大多数要么很明显,要么用注释解释得很好,除了 extraArgs
。它用于设置 gas 限制或启用乱序执行等操作,但我们会在需要时再讨论它。现在先记住它。
所以,回到我们的 sendMessage
函数。它将接受三个输入:目标链选择器、目标合约地址和字符串消息。我们将所有这些打包到 EVM2AnyMessage
结构体中,并将其传递给 ccipSend
。
我们还没有讨论费用。由于 Chainlink CCIP 是一项服务,它会收取费用,可以使用原生代币或支持的 ERC20 代币支付。对于此示例,我们将使用原生代币支付。这意味着我们的 sendMessage
函数必须是 payable
,并且我们将检查用户是否发送了足够的 ETH 来支付费用。
现在我们知道它是如何工作的了,让我们开始编码。
我们的合约将写在 CCIPMessenger.sol 文件中。我们将从 Chainlink CCIP 包中导入两件事:IRouterClient
,它允许我们与 Router 合约交互,以及 Client,它让我们能够访问构建 CCIP 消息所需的 EVM2AnyMessage
结构体。
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
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";
contract CCIPMessenger {}
我们还希望在发送消息时发出一个事件。让我们定义 MessageSent
事件:
contract CCIPMessenger {
event MessageSent(
bytes32 indexed messageId,
address indexed sender,
uint64 indexed destinationChainSelector
);
}
现在我们可以开始构建 sendMessage
函数。它将是 payable
,并且将接受我们讨论过的三个参数:
function sendMessage(
uint64 destinationChainSelector,
address receiver,
string calldata message
) public payable {
// create router client instance
// build the message
// 获取费用
// 发送消息
// 发出事件
}
让我们填写每个步骤。
使用 Router 合约的地址创建 Router 客户端实例:
// 创建 Router 客户端实例
IRouterClient routerClient =
IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);
接下来,使用 EVM2AnyMessage
结构体构建消息。接收者和消息是用户输入。对于其余部分:
tokenAmounts
:空数组(未发送任何代币)
feeToken
:零地址(表示原生代币)
extraArgs
:空字节
// 构建消息
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: abi.encode(message),
tokenAmounts: new Client.EVMTokenAmount[](0),
feeToken: address(0),
extraArgs: bytes("")
});
使用 getFee
估算费用,并确保用户已发送足够的 ETH:
uint256 fees = routerClient.getFee(destinationChainSelector, evm2AnyMessage);
require(msg.value >= fees, "Insufficient fee");
最后,调用 ccipSend
发送消息并发出事件:
// 发送消息
bytes32 messageId = routerClient.ccipSend{value: msg.value}(
destinationChainSelector, evm2AnyMessage
);
// 发出事件
emit MessageSent(messageId, msg.sender, destinationChainSelector);
所以完整的函数如下所示:
function sendMessage(
uint64 destinationChainSelector,
address receiver,
string calldata message
) public payable {
// 创建 Router 客户端实例
IRouterClient routerClient =
IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);
// 构建消息
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: abi.encode(message),
tokenAmounts: new Client.EVMTokenAmount[](0),
feeToken: address(0),
extraArgs: bytes("")
});
// 获取费用
uint256 fees =
routerClient.getFee(destinationChainSelector, evm2AnyMessage);
require(msg.value >= fees, "Insufficient fee");
// 发送消息
bytes32 messageId = routerClient.ccipSend{value: msg.value}(
destinationChainSelector, evm2AnyMessage
);
// 发出事件
emit MessageSent(messageId, msg.sender, destinationChainSelector);
}
完成 sendMessage
函数后,我们已经涵盖了我们在本文中要构建的所有内容。此时你的合约应如下所示:
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
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";
contract CCIPMessenger {
event MessageSent(
bytes32 indexed messageId,
address indexed sender,
uint64 indexed destinationChainSelector
);
function sendMessage(
uint64 destinationChainSelector,
address receiver,
string calldata message
) public payable {
// 创建 Router 客户端实例
IRouterClient routerClient =
IRouterClient(0x881e3A65B4d4a04dD529061dd0071cf975F58bCD);
// 构建消息
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: abi.encode(message),
tokenAmounts: new Client.EVMTokenAmount[](0),
feeToken: address(0),
extraArgs: bytes("")
});
// 获取费用
uint256 fees =
routerClient.getFee(destinationChainSelector, evm2AnyMessage);
require(msg.value >= fees, "Insufficient fee");
// 发送消息
bytes32 messageId = routerClient.ccipSend{value: msg.value}(
destinationChainSelector, evm2AnyMessage
);
// 发出事件
emit MessageSent(messageId, msg.sender, destinationChainSelector);
}
}
现在我们可以发送消息了,下一个合乎逻辑的步骤是弄清楚如何接收消息,这正是我们将在下一部分中解决的问题。
当我们添加接收消息的功能时,我们还将稍微重构 sendMessage
函数。目前,我们使用的 Router 地址是为特定链(Base Mainnet)硬编码的。这需要更改,以便我们的合约可以部署在不同的链上,并且仍然可以使用 CCIP。
希望你喜欢这一部分。如果你正在关注,我会在下一部分中见到你。如果你渴望更深入地了解 CCIP,Chainlink CCIP 的官方文档是一个很好的探索场所。
嘿,我是 Nikhil,一名软件开发人员,喜欢撰写关于 Solidity、EVM 以及任何引起我注意的新技术的文章。你可以在此 Medium 个人资料上查看更多我的作品,并在此处找到我的其余作品和社交资料:https://linktr.ee/nikbhintade
- 原文链接: blog.blockmagnates.com/s...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!