跳至主要內容
本頁面使用機器翻譯自英語,可能包含錯誤或不清楚的語言。如需最準確的信息,請參閱英文原文。由於更新頻繁,部分內容可能與英文原文有出入。請加入我們在 Crowdin 上的努力,幫助我們改進本頁面的翻譯。 (Crowdin translation page, Contributing guide)

鏈條 CCIP

導言​

Chainlink Cross-Chain Interoperability Protocol (CCIP) 為開發人員和分散式應用程式 (dApp) 提供安全、有效的跨區塊鏈互動方式。透過 CCIP,您可以傳送代幣和任意訊息,以觸發目的合約上的動作,例如鑄造 NFT、重新平衡指數或呼叫自訂函式。

在本教程中,您將學習如何使用 Chainlink CCIP 從 Kaia 智慧型契約傳送訊息和代幣到另一條鏈上的契約,以及如何將訊息和代幣接收回來。

先決條件。​

  • 代工安裝
    • 使用 curl -L https://foundry.paradigm.xyz | bash 安裝,然後執行 foundryup。
    • 使用 forge --version,``cast --version 和 anvil --version 進行驗證。
  • MetaMask 錢包
    • 設定開發人員錢包
    • 將 Kaia Kairos 測試網路和 Ethereum Sepolia 網路加入 MetaMask。
  • 從水龍頭測試代幣
    • KAIA:從 Kaia 部署和傳送的瓦斯。
    • LINK (testnet):用 LINK 付款時,適用 CCIP 費用。
    • 目的鏈上的原生代幣(例如,Sepolia ETH:用於部署,如果選擇,用於以原生代幣支付 CCIP 費用)。

開始使用​

在本指南中,您將使用 Chainlink CCIP 在 Kaia (Kairos Testnet) 和 Ethereum Sepolia 之間傳送和接收跨鏈訊息。

到最後,您將會

  • 初始化為 Kairos 和 Sepolia 設定的 Foundry 專案
  • 新增 Chainlink CCIP 契約和介面為依賴項目
  • 實作一個 Messenger 契約,可跨鏈傳送和接收訊息
  • 部署至兩個網路並驗證往返訊息

建立專案​

在本節中,您將使用 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/

撰寫智慧型契約​

在本節中,您將使用下面的程式碼來跨鏈傳送和接收訊息。

在專案的 rc 目錄下製作一個新檔案,命名為 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] (via allowlistSender(addr, allowed)):當訊息到達時,限制信任來源鏈上的哪些寄件者位址。
備註

測試前在兩端都設定好。來源必須信任寄件者和連鎖。目的地也必須允許傳送。

2. 傳送訊息

sendMessagePayLINK(selector, receiver, text):傳送訊息並在 LINK 中支付 CCIP 費用。這會建立訊息、報價費用、檢查 LINK 結餘、核准 Router,然後執行 ccipSend。當完成時,它會回傳一個與傳送訊息相關的唯一 ID。

sendMessagePayNative(selector, receiver, text):傳送訊息並以原生代幣支付 CCIP 費用。這會建立訊息、報價費用、檢查本機餘額,然後執行 ccipSend(value:費用)。完成後,它會回傳一個與傳送訊息相關的唯一 ID。

3. 建立訊息

_buildCCIPMessage(receiver, text, feeTokenAddress) -> EVM2AnyMessage

  • 編碼接收器和文字
  • 未傳送任何代幣(tokenAmounts 為空)
  • 使用 GenericExtraArgsV2 與可設定 gasLimit 的 extraArgs 套件
  • 設定 feeToken 為 LINK 或 address(0)。

4. 接收訊息

CCIP 呼叫 _ccipReceive(...) 在目的地鏈上。合約:

  • 根據允許清單驗證來源鏈和寄件者
  • 解碼字串
  • 將其儲存為最後收到的有效負載
  • 輸出 MessageReceived
  • 讀取最後一次入站的有效負載: getLastReceivedMessageDetails() -> (messageId, text)

編譯智慧型契約​

要編譯您的智慧型契約,請執行


forge build

部署智慧型契約​

設定您的錢包為部署者​

在您將智慧型契約部署到網路之前,您需要設定一個錢包作為部署者。為此,您可以使用 cast wallet import 指令,將錢包的私密金鑰匯入 Foundry 安全加密的 keystore:


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 檢視部署狀態和合約。

啟用您的合約發送 CCIP 訊息至 Ethereum Sepolia 上的接收者合約​

首先,我們需要更新目的地鏈的交易允許列表狀態。若要執行,請執行下列指令:


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

上面的程式碼呼叫 allowlistDestinationChain() 來設定 Sender 契約允許的目的地鏈選擇器。各鏈選擇器可在 [CCIP 目錄] (https://docs.chain.link/ccip/directory) 中找到。

將 Receiver 合約部署至 Ethereum Sepolia​

要將 Receiver 智慧型契約部署到 Ethereum 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

出現提示時,輸入之前匯入錢包私人密碼匙時設定的密碼。

執行上述指令後,該契約將部署在 Ethereum 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() 來設定 Receiver 契約上允許的來源鏈選擇器。每個鏈選擇器可在 [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 上有一個接收者合約。作為安全措施,您啟用了傳送者合約,以傳送 CCIP 訊息到 Ethereum Sepolia,並啟用接收者合約,以接收來自傳送者和 Kairos Testnet 的 CCIP 訊息。

為您的智慧型契約提供資金​

為了支付與傳送訊息相關的費用,傳送者合約需要持有 LINK 代幣的餘額,如果傳送資料並使用原生代幣支付,則需要持有 ETH 和 KAIA。

** 連結**

直接從您的錢包中為您的合約注資,或執行以下 cast 指令:


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

上述指令會在 Kairos Testnet 上傳送 5 個 LINK 代幣到寄件者合約。

備註

在執行所提供的 cast 指令之前,請將 SENDER_DEPLOYED_ADDRESS 改為您已部署的 Sender 合約的合約位址。

與智慧型契約互動​

在本節中,您將與已部署的智慧型契約互動,並使用 Foundry cast 指令列工具呼叫其功能。

在這個步驟中,您將使用 CCIP 傳送簡訊,使用 CCIP 的費用將以 LINK 方式支付。

為此,您使用 cast 指令,在部署到 Kairos Testnet 的 Sender 契約上呼叫 sendMessagePayLINK(uint64, address, string) 函式,以便將訊息資料傳送至 Ethereum Sepolia 上的 Receiver 契約。

要呼叫 Sender 智慧合約的 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)、Receiver 合約位址,以及要包含在訊息中的文字資料 (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 契約扮演收件者。

首先,我們需要在 Ethereum Sepolia 上以 ETH 為寄件者合約提供資金。若要執行,請執行下列 cast 指令:


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

這會在 Ethereum Sepolia 上發送 0.3 ETH 到您的寄件者合約。

備註

將 SENDER_DEPLOYED_ADDRESS 改為寄件者合約地址。

接下來是在 Ethereum 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,並呼叫 Receiver 契約上的 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 文件。

讓這個頁面變得更好