阅读时间 38 分钟
概述
Hyperliquid 将两层架构整合到一个统一的状态下:HyperCore 是一个通过签名操作访问的超低延迟订单簿引擎;HyperEVM 则是一个完全兼容 EVM 的网络,支持标准的 Solidity 开发。
Chainlink CCIP 通过“销毁与铸造”机制,实现了跨链资产的桥接。在 Hyperliquid 中,HyperCore (HIP-1) 资产与其 HyperEVM 表示形式之间的转换由原生协议流程处理。
在本指南中,您将使用 Foundry 对对应于 HIP-1 资产的 HyperEVM 代币进行包装,并利用 CCIP 将该代币在 HyperEVM 和 BNB Smart Chain(BSC)之间进行跨链桥接。
您将负责的工作内容
- 为与 HIP-1 资产关联的 HyperEVM 代币创建一个包装合约
- 在 HyperEVM 和 BSC 上部署和配置 CCIP 代币池
- 在链上验证我们已部署的代币和资金池合约
- 使用 Foundry 脚本在 HyperEVM 和 BSC 之间执行 CCIP 转账
您需要准备的物品
- 已安装Node.js v22 及以上版本以及一个包管理器(npm、yarn 或 pnpm)
- MetaMask已添加 HyperEVM 主网和币安智能链主网
- HyperEVM 和币安智能链的 Quicknode 端点
- 具备Solidity和Foundry的相关经验
在本指南中,我们将使用各条链的主网(MAINNETS)。这是因为在撰写本文时,Chainlink CCIP 尚未在 HyperEVM 测试网(Testnet)上推出。请谨慎操作,仅使用您能够承受损失的资金。
搭建开发环境
我们将使用Foundry来编译、部署智能合约并与之交互。如果您尚未安装 Foundry,可以在终端中运行以下命令进行安装:
curl -L https://foundry.paradigm.xyz | bash
请按照屏幕上的说明操作,之后您就可以使用该 foundryup 用于安装 Foundry 的命令。请确保在新的终端会话中执行此命令,以便反映对您的 PATH 变量。
foundryup
我们需要在本地创建一个新文件夹,用于存放本指南中的项目。我们将该文件夹命名为hyperevm_ccip,但您可以根据自己的喜好随意命名。请在终端中运行以下命令创建该文件夹,然后使用代码编辑器进入该文件夹。在本指南中,我们将使用 VS Code。
forge init hyperevm_ccip
cd hyperevm_ccip
code .
此时,您的设置应该大致如下所示:

太好了!我们的 Foundry 项目结构现已搭建完成。接下来,创建一个 .env 位于项目文件夹根目录下的文件。我们将在此处存储环境变量,例如私钥和 RPC URL。该文件的格式如下: .env 文件应如下所示:
HYPEREVM_RPC="您的_HYPEREVM_RPC_URL"
BSC_RPC="您的 BSC RPC 网址"
PRIVATE_KEY="您的私钥"
ETHERSCAN_API_KEY="您的_etherscan_api_key"
我们稍后将讨论这些变量的具体含义以及如何获取它们。下一步是删除默认的 src/Counter.sol 合同和 script/Counter.s.sol Foundry 为我们生成的脚本。本指南中将不会使用这些文件。此外,您还可以在终端中运行以下命令来删除这些文件:
rm src/Counter.sol
rm script/Counter.s.sol
您还需要配置 foundry.toml 在项目文件夹的根目录下创建一个文件,其中包含 Solidity 编译器的重要设置以及我们将要使用的 import 语句的重新映射。以下是你的 foundry.toml 文件应如下所示:
[个人资料.默认]
src = "src"
out = "out"
库 = ["lib"]
优化器 = true
优化器运行次数 = 200
重映射 = [
'@chainlink/contracts-ccip/=node_modules/@chainlink/contracts-ccip/',
'@chainlink/contracts/=node_modules/@chainlink/contracts/',
]
fs_permissions = [{ 访问 = "读写", 路径 = "./" }]
在此文件中,我们添加了 优化器 用于优化智能合约部署的设置。该 fs_permissions 该条目允许 Foundry 在项目目录中读写文件。该 重新映射 这些条目是本指南中将要使用的 Chainlink 合约包的路径。接下来我们将安装这些包。
安装依赖项
在本指南中,我们将使用两个 Chainlink 合约包: @chainlink/contracts-ccip 以及 @chainlink/contracts. 第一个包包含我们将要交互的核心 CCIP 合约,以及我们将要部署的 CCIP 代币池合约。第二个包包含定制的 ERC20 符合 Chainlink 跨链转账要求的合约。
此外,我们还将安装 @layerzerolabs/hyperliquid-composer 该包将用于提高我们在 HyperEVM 上交易的 gas 限制。HyperEVM 具有一个 多块架构 该网络由两类区块组成:一类是 gas 限制为 200 万 gas 单位的小区块,另一类是 gas 限制为 3000 万 gas 单位的大区块。小区块的区块间隔为 1 秒,而大区块的区块间隔为 1 分钟。默认情况下,HyperEVM 上的账户只能发送符合小区块 gas 限制的交易。通过此套餐,我们将能够使账户发送具有更高 gas 限制的交易,以便纳入大区块中。
要安装这些软件包,请使用您喜欢的软件包管理器输入以下命令:
- npm
- 纱线
npm 安装 @chainlink/contracts-ccip @chainlink/contracts @layerzerolabs/hyperliquid-composer
纱线 添加 @chainlink/contracts-ccip @chainlink/contracts @layerzerolabs/hyperliquid-composer
太好了!您已成功安装了本指南中将要使用的依赖项。接下来,我们将讨论需要在我们的 .env 文件。
环境变量
Quicknode 端点
首先,我们需要获取 HyperEVM 和 BSC 的相应 RPC 端点。您可以从以下位置获取这些信息: Quicknode。只需注册免费试用,创建一个新的多链端点,然后复制每条链的 HTTPS URL。将相应的 URL 粘贴到 HYPEREVM_RPC 以及 BSC_RPC 您中的变量 .env 文件。
Etherscan API
接下来,请前往您的个人资料页面 Etherscan 然后转到“API 密钥”选项卡。如果您还没有账户,请创建一个 这里. 在此处,您将创建一个 API 密钥,该密钥将帮助您在链上验证智能合约。复制该 API 密钥,并将其粘贴到 ETHERSCAN_API_KEY 您中的变量 .env 文件。
私钥
最后,打开您的 MetaMask,复制其中一个账户的私钥。如需了解如何获取私钥,请参阅这篇简短的 指南. 将此私钥粘贴到 PRIVATE_KEY 您中的变量 .env 文件。
至此,您中的所有环境变量 .env 该文件现在应已填充完毕。此设置过程的最后一步是将相应的网络添加到我们的 MetaMask 钱包中。
将网络添加到 MetaMask
我们将把 HyperEVM 主网和币安智能链(Binance Smart Chain)主网添加到我们的 MetaMask 钱包中。添加这些网络的最简单方法是前往 hyperevmscan.io 以及 bscscan.com 然后点击 添加 页面左下角的按钮。这将把这些网络添加到您的 MetaMask 钱包中:

恭喜!您已成功为本指南搭建了开发环境。下一步,我们将把一个 HIP-1 资产从 HyperCore 桥接到 HyperEVM。
从 HyperCore 过渡到 HyperEVM
接下来,您将把一项 HIP-1 资产从 HyperCore 桥接到 HyperEVM。此过程涉及将 HIP-1 资产转换为其对应的 HyperEVM 代币形式。在本指南中,我们将使用 Hyperliquid 的原生生态系统代币:HYPE。请前往Hyperliquid 交易界面,并使用MetaMask登录以开始操作。
如果您目前还没有 HYPE 代币,可以通过将 USDC 从 Arbitrum 主网存入 Hyperliquid,然后在交易界面的现货市场将其兑换为 HYPE 来获取:

获得一定数量的HYPE后,您可以将其桥接到HyperEVM。操作时,请点击页面底部图表下方的“转入/转出EVM”按钮。只需输入您要桥接的HYPE数量,然后点击“确认”即可。

将 HYPE 与 HyperEVM 连接起来
太好了!您已成功将 HYPE 从 HyperCore 桥接到 HyperEVM。现在,您应该能在 MetaMask 中看到 HyperEVM 网络上的 HYPE 余额。接下来,我们将创建一个符合 Chainlink 跨链转账要求的 HYPE 包装合约。
为 HYPE 创建一个包装合约
为了利用 Chainlink CCIP 在 HyperEVM 和 BSC 之间实现 HYPE 的跨链转移,我们需要创建一个符合 Chainlink 跨链转移要求的包装合约。这包括在 HyperEVM 上创建一个新的 ERC20 代币合约来包装现有的 HYPE 资产,以及在 BSC 上创建一个相应的 ERC20 代币合约。
我们将创建一个 Quicknode 炒作热潮 符号为 的代币 qWHYPE. 输入以下命令,在 src 项目目录下的文件夹中,并将以下代码粘贴到该文件夹内。
- 创建文件
- 代码
touch src/qWHYPE.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
contract qWHYPE is BurnMintERC20 {
event Deposit(address indexed account, uint256 amount);
event Withdraw(address indexed account, uint256 amount);
constructor() BurnMintERC20("Quicknode Wrapped HYPE", "qWHYPE", 18, 0, 0) {}
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) external {
_burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
emit Withdraw(msg.sender, amount);
}
}
理解包装合约
让我们来分析一下 qWHYPE.sol 合同。该合同导入了 BurnMintERC20 来自的合同 @chainlink/contracts 该合约提供了铸造和销毁代币所需的功能。当用户向合约存入 HYPE 时,合约会向其地址铸造等值的 qWHYPE 代币。反之,当用户提取 qWHYPE 代币时,合约会销毁这些代币,并将相应数量的 HYPE 转回用户的地址。
该特定合约将部署在HyperEVM网络上。在BSC上,我们将部署基础版 BurnMintERC20 直接部署该合约,因为它不需要任何额外功能。我们将在下一节中使用脚本部署这两个合约。
《铸造字体的脚本编写》
我们将编写几个 Foundry 脚本,用于部署智能合约并与之交互。本节将分为几个部分,详细介绍每个脚本。我们将创建以下脚本,并按此顺序执行它们:
| 脚本名称 | 描述 |
|---|---|
DeployTokens.s.sol | 在 HyperEVM 上部署 qWHYPE 合约及其基础合约 BurnMintERC20 BSC上的合约 |
DeployPools.s.sol | 在 HyperEVM 和 BSC 上部署 CCIP 代币池合约 |
SetupAdmin.s.sol | 在每条链的 CCIP 合约上注册并配置管理员角色 |
ConfigurePools.s.sol | 配置我们的 CCIP 代币池,以便它们之间进行跨链转账 |
DepositAndTransferTokens.s.sol | 向 qWHYPE 合约存入少量 HYPE 以铸造 qWHYPE 代币,然后执行跨链转账至 BSC |
TransferTokens.s.sol | 从任一方向执行 qWHYPE 代币的跨链转账 |
先决条件
在编写脚本之前,我们需要一种方法来追踪已部署的合约地址,并需要一个文件来存储脚本中将要使用的常用常量。
执行以下命令以创建相应的文件:
touch script/Constants.s.sol
mkdir -p script/output
touch script/output/deployments.json
我们将使用 deployments.json 用于跟踪每条链上已部署合约地址的文件。
以下是 Constants.s.sol 文件应如下所示:
点击展开代码
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script} from "forge-std/Script.sol";
contract Constants is Script {
struct CCIPConstants {
uint64 chainSelector;
address router;
address rmnProxy;
address tokenAdminRegistry;
address registryModuleOwnerCustom;
string nativeCurrencySymbol;
}
function getCCIPConstants(uint256 chainId) public pure returns (CCIPConstants memory) {
if(chainId == 999) {
return CCIPConstants({
chainSelector: 2442541497099098535,
router: 0x13b3332b66389B1467CA6eBd6fa79775CCeF65ec,
rmnProxy: 0x07f15e9813FBd007d38CF534133C0838f449ecFA,
tokenAdminRegistry: 0xcE44363496ABc3a9e53B3F404a740F992D977bDF,
registryModuleOwnerCustom: 0xbAb3aBB5F29275065F2814F1f4B10Ffc1284fFEf,
nativeCurrencySymbol: "HYPE"
});
} else if (chainId == 56) {
return CCIPConstants({
chainSelector: 11344663589394136015,
router: 0x34B03Cb9086d7D758AC55af71584F81A598759FE,
rmnProxy: 0x9e09697842194f77d315E0907F1Bda77922e8f84,
tokenAdminRegistry: 0x736Fd8660c443547a85e4Eaf70A49C1b7Bb008fc,
registryModuleOwnerCustom: 0x47Db76c9c97F4bcFd54D8872FDb848Cab696092d,
nativeCurrencySymbol: "BNB"
});
}
revert("Chain not supported");
}
}
脚本菜单
- DeployTokens.s.sol
- DeployPools.s.sol
- SetupAdmin.s.sol
- ConfigurePools.s.sol
- DepositAndTransferTokens.s.sol
- TransferTokens.s.sol
该脚本将部署 qWHYPE HyperEVM 上的合约及其基础 BurnMintERC20 BSC 上的合约。在 剧本 项目目录下名为 DeployTokens.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/DeployTokens.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {qWHYPE} from "../src/qWHYPE.sol";
import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
contract DeployTokens is Script {
string internal constant OUTPUT_PATH = "script/output/deployments.json";
/// forge script script/DeployTokens.s.sol:DeployTokens
function run() external {
// Load env vars
bytes memory hyperevm = bytes(vm.envString("HYPEREVM_RPC"));
bytes memory bsc = bytes(vm.envString("BSC_RPC"));
uint256 pk = vm.envUint("PRIVATE_KEY");
// Check if env vars are set
require(hyperevm.length != 0, "HYPEREVM_RPC not set");
require(bsc.length != 0, "BSC_RPC not set");
// print deployer address
address deployer = vm.addr(pk);
console.log("Deployer address:", deployer);
// Deploy on both chains
address hyperAddr = deployOn(hyperevm, deployer, pk);
address bscAddr = deployOn(bsc, deployer, pk);
// Write deployed addresses to JSON file
string memory obj = vm.serializeString("deployments", "qWHYPE_hyperevm", vm.toString(hyperAddr));
obj = vm.serializeString("deployments", "qWHYPE_bsc", vm.toString(bscAddr));
vm.writeJson(obj, OUTPUT_PATH);
}
function deployOn(bytes memory rpc, address deployer, uint256 pk) internal returns (address) {
vm.selectFork(vm.createFork(string(rpc)));
vm.startBroadcast(pk);
string memory chainName = getChainName(block.chainid);
address tokenAddr = address(block.chainid == 999 ? new qWHYPE() : new BurnMintERC20("Quicknode Wrapped HYPE", "qWHYPE", 18, 0, 0));
console.log("\nDeployed qWHYPE to:", tokenAddr, "on", chainName);
BurnMintERC20(tokenAddr).grantMintAndBurnRoles(deployer);
console.log("Granted minter and burner roles on", chainName, "qWHYPE to:", deployer);
vm.stopBroadcast();
return tokenAddr;
}
function getChainName(uint256 chainId) internal pure returns (string memory) {
if (chainId == 56) return "\x1b[36mBSC Mainnet\x1b[0m";
else if (chainId == 999) return "\x1b[32mHyperEVM Mainnet\x1b[0m";
else revert("Unsupported chain ID");
}
}
了解 DeployTokens 脚本
在此脚本中,我们首先加载 RPC URL 和私钥的环境变量。然后,我们定义一个 deployOn 该函数接受 RPC URL、部署者地址和私钥作为参数。该函数会创建指定区块链的分叉,使用提供的私钥开始广播交易,并部署 qWHYPE 合约(在 HyperEVM 上)或基础合约 BurnMintERC20 合约(在 BSC 上)。部署完成后,该合约会向部署者地址授予已部署代币合约的铸造和销毁权限。
该脚本将在 HyperEVM 和 BSC 上部署 CCIP 代币池合约。在 剧本 项目目录下名为 DeployPools.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/DeployPools.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script} from "forge-std/Script.sol";
import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
import {IBurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/IBurnMintERC20.sol";
import {BurnMintTokenPool} from "@chainlink/contracts-ccip/contracts/pools/BurnMintTokenPool.sol";
import {Constants} from "./Constants.s.sol";
contract DeployPools is Script {
string internal constant OUTPUT_PATH = "script/output/deployments.json";
/// forge script script/DeployPools.s.sol:DeployPools
function run() external {
// Load env vars
bytes memory hyperevm = bytes(vm.envString("HYPEREVM_RPC"));
bytes memory bsc = bytes(vm.envString("BSC_RPC"));
uint256 pk = vm.envUint("PRIVATE_KEY");
// Check if env vars are set
require(hyperevm.length != 0, "HYPEREVM_RPC not set");
require(bsc.length != 0, "BSC_RPC not set");
// Read deployed token addresses from JSON
string memory json = vm.readFile(OUTPUT_PATH);
address tokenHyp = vm.parseJsonAddress(json, ".qWHYPE_hyperevm");
address tokenBsc = vm.parseJsonAddress(json, ".qWHYPE_bsc");
// Fetch CCIP constants
Constants constants = new Constants();
Constants.CCIPConstants memory cfgHyp = constants.getCCIPConstants(999);
Constants.CCIPConstants memory cfgBsc = constants.getCCIPConstants(56);
// Create forks up front
uint256 hyperFork = vm.createFork(string(hyperevm));
uint256 bscFork = vm.createFork(string(bsc));
// Deploy pool on HyperEVM
vm.selectFork(hyperFork);
vm.startBroadcast(pk);
address poolHyp = address(new BurnMintTokenPool(IBurnMintERC20(tokenHyp), 18, new address[](0), cfgHyp.rmnProxy, cfgHyp.router));
BurnMintERC20(tokenHyp).grantMintAndBurnRoles(poolHyp);
vm.stopBroadcast();
// Deploy pool on BSC
vm.selectFork(bscFork);
vm.startBroadcast(pk);
address poolBsc = address(new BurnMintTokenPool(IBurnMintERC20(tokenBsc), 18, new address[](0), cfgBsc.rmnProxy, cfgBsc.router));
BurnMintERC20(tokenBsc).grantMintAndBurnRoles(poolBsc);
vm.stopBroadcast();
// Write deployed addresses to JSON file
string memory out = vm.serializeString("deployments", "qWHYPE_hyperevm", vm.toString(tokenHyp));
out = vm.serializeString("deployments", "qWHYPE_bsc", vm.toString(tokenBsc));
out = vm.serializeString("deployments", "qWHYPE_pool_hyperevm", vm.toString(poolHyp));
out = vm.serializeString("deployments", "qWHYPE_pool_bsc", vm.toString(poolBsc));
vm.writeJson(out, OUTPUT_PATH);
}
}
了解 DeployPools 脚本
在此脚本中,我们首先加载 RPC URL 和私钥的环境变量。然后,我们从 deployments.json 在上一个脚本中创建的文件。我们使用 常量 合同。
这些常量包括 Chainlink 在每条链上部署的核心 CCIP 合约,包括Router、TokenAdminRegistry、RegistryModuleOwnerCustom,以及池部署所需的RiskManagementNetwork (RMN)代理地址。 Router 负责路由跨链消息,TokenAdminRegistry 管理代币地址与其对应资金池的映射关系,RegistryModuleOwnerCustom 用于不同所有权模式下的代币管理员注册,而 RMN 代理则用于验证跨链消息的 RMN 签名。
利用这些常量,我们部署了 BurnMintTokenPool 两条链上的合约,赋予它们在各自代币合约中铸造和销毁代币的权限。最后,我们更新了 deployments.json 包含已部署代币池地址的文件。
该脚本将注册我们在各条链上部署的代币在 CCIP 注册合约中的管理员。在 剧本 项目目录下名为 SetupAdmin.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/SetupAdmin.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {TokenAdminRegistry} from "@chainlink/contracts-ccip/contracts/tokenAdminRegistry/TokenAdminRegistry.sol";
import {RegistryModuleOwnerCustom} from "@chainlink/contracts-ccip/contracts/tokenAdminRegistry/RegistryModuleOwnerCustom.sol";
import {Constants} from "./Constants.s.sol";
contract SetupAdmin is Script {
string internal constant OUTPUT_PATH = "script/output/deployments.json";
/// forge script script/SetupAdmin.s.sol:SetupAdmin
function run() external {
// Load env vars
bytes memory hyperevm = bytes(vm.envString("HYPEREVM_RPC"));
bytes memory bsc = bytes(vm.envString("BSC_RPC"));
uint256 pk = vm.envUint("PRIVATE_KEY");
// Check if env vars are set
require(hyperevm.length != 0, "HYPEREVM_RPC not set");
require(bsc.length != 0, "BSC_RPC not set");
// Read deployed token addresses
string memory json = vm.readFile(OUTPUT_PATH);
address tokenHyp = vm.parseJsonAddress(json, ".qWHYPE_hyperevm");
address tokenBsc = vm.parseJsonAddress(json, ".qWHYPE_bsc");
// Fork both chains up front
uint256 hyperFork = vm.createFork(string(hyperevm));
uint256 bscFork = vm.createFork(string(bsc));
// Do HyperEVM
vm.selectFork(hyperFork);
_registerAndAccept(tokenHyp, pk);
// Do BSC
vm.selectFork(bscFork);
_registerAndAccept(tokenBsc, pk);
}
function _registerAndAccept(address token, uint256 pk) internal {
Constants constants = new Constants();
Constants.CCIPConstants memory cfg = constants.getCCIPConstants(block.chainid);
address ownerCustomModule = cfg.registryModuleOwnerCustom;
address tokenAdminRegistry = cfg.tokenAdminRegistry;
string memory chainName = _getChainName(block.chainid);
vm.startBroadcast(pk);
// Register admin on RegistryModuleOwnerCustom (msg.sender becomes pending admin)
RegistryModuleOwnerCustom(ownerCustomModule).registerAdminViaGetCCIPAdmin(token);
console.log("Proposed admin via OwnerCustom on", chainName);
// Accept the admin role from the pending admin
TokenAdminRegistry tokenAdminRegistryContract = TokenAdminRegistry(tokenAdminRegistry);
TokenAdminRegistry.TokenConfig memory config = tokenAdminRegistryContract.getTokenConfig(token);
address pendingAdmin = config.pendingAdministrator;
require(pendingAdmin == vm.addr(pk), "Pending admin mismatch");
tokenAdminRegistryContract.acceptAdminRole(token);
console.log("Successfully registered and accepted admin role for token on", chainName, ": ", token);
vm.stopBroadcast();
}
function _getChainName(uint256 chainId) internal pure returns (string memory) {
if (chainId == 56) return "BSC";
if (chainId == 999) return "HyperEVM";
return "Unknown";
}
}
了解 SetupAdmin 脚本
在此脚本中,我们首先加载 RPC URL 和私钥的环境变量。然后,我们从 deployments.json 文件。CCIP 注册表合约需要这些代币地址,以便从各自的区块链上获取并注册每个代币的管理员。
注册 CCIP 代币的管理员需要分两步进行。首先, RegistryModuleOwnerCustom 调用该合约。该合约用于提名该代币的新管理员。当前管理员是通过 getCCIPAdmin 在我们的 qWHYPE 合同,成为待处理的管理员。该 RegistryModuleOwnerCustom 调用 TokenAdminRegistry 合同以提出新的管理员。然后将该状态映射到一个 TokenConfig struct 在 TokenAdminRegistry 合同。然后是最后一步,即 TokenAdminRegistry 调用该方法以接受从 TokenConfig 对于给定的令牌。
您可能在想,为什么注册代币管理员要采用这种复杂的两步流程。为什么不直接在 TokenAdminRegistry 缩写?做出这种设计选择有以下几个原因:
- 增强的安全性:通过两步审核流程,现任管理员可在新管理员正式就职前对其进行审核和批准。
- 多签名兼容性:通过提出并随后接受管理员变更,与多签名钱包(例如 Safe)兼容。
- 所有权模式:通过以下方式与 CCIP 的所有权模式兼容
getCCIPOwner此外,借助 OpenZeppelin 的 Ownable 风格所有权机制等功能,可以接入各种现有的代币。
该脚本将配置我们的 CCIP 代币池,以便它们之间进行跨链转账。在 剧本 项目目录下名为 ConfigurePools.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/ConfigurePools.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {TokenAdminRegistry} from "@chainlink/contracts-ccip/contracts/tokenAdminRegistry/TokenAdminRegistry.sol";
import {TokenPool} from "@chainlink/contracts-ccip/contracts/pools/TokenPool.sol";
import {RateLimiter} from "@chainlink/contracts-ccip/contracts/libraries/RateLimiter.sol";
import {Constants} from "./Constants.s.sol";
contract ConfigurePools is Script {
struct Params {
address localToken;
address localPool;
address tokenAdminRegistry;
uint64 remoteSelector;
address remotePool;
address remoteToken;
uint256 pk;
string chainName;
}
string internal constant OUTPUT_PATH = "script/output/deployments.json";
/// forge script script/ConfigurePools.s.sol:ConfigurePools
function run() external {
// Load env vars
bytes memory hyperevm = bytes(vm.envString("HYPEREVM_RPC"));
bytes memory bsc = bytes(vm.envString("BSC_RPC"));
uint256 pk = vm.envUint("PRIVATE_KEY");
require(hyperevm.length != 0, "HYPEREVM_RPC not set");
require(bsc.length != 0, "BSC_RPC not set");
// Load deployed addresses
string memory json = vm.readFile(OUTPUT_PATH);
address tokenHyp = vm.parseJsonAddress(json, ".qWHYPE_hyperevm");
address tokenBsc = vm.parseJsonAddress(json, ".qWHYPE_bsc");
address poolHyp = vm.parseJsonAddress(json, ".qWHYPE_pool_hyperevm");
address poolBsc = vm.parseJsonAddress(json, ".qWHYPE_pool_bsc");
// Preload constants for both chains
Constants constants = new Constants();
Constants.CCIPConstants memory cfgHyp = constants.getCCIPConstants(999);
Constants.CCIPConstants memory cfgBsc = constants.getCCIPConstants(56);
// Forks
uint256 hyperFork = vm.createFork(string(hyperevm));
uint256 bscFork = vm.createFork(string(bsc));
// Configure HyperEVM pool and registry
vm.selectFork(hyperFork);
Params memory pHyp = Params({
localToken: tokenHyp,
localPool: poolHyp,
tokenAdminRegistry: cfgHyp.tokenAdminRegistry,
remoteSelector: cfgBsc.chainSelector,
remotePool: poolBsc,
remoteToken: tokenBsc,
pk: pk,
chainName: "HyperEVM"
});
_setPoolAndApplyChainUpdates(pHyp);
// Configure BSC pool and registry
vm.selectFork(bscFork);
Params memory pBsc = Params({
localToken: tokenBsc,
localPool: poolBsc,
tokenAdminRegistry: cfgBsc.tokenAdminRegistry,
remoteSelector: cfgHyp.chainSelector,
remotePool: poolHyp,
remoteToken: tokenHyp,
pk: pk,
chainName: "BSC"
});
_setPoolAndApplyChainUpdates(pBsc);
}
function _setPoolAndApplyChainUpdates(Params memory p) internal {
// Set pool in TokenAdminRegistry (maps token -> pool)
vm.startBroadcast(p.pk);
TokenAdminRegistry(p.tokenAdminRegistry).setPool(p.localToken, p.localPool);
console.log("setPool set on ", p.localPool);
// Configure pool with remote chain, pool, rate limits
TokenPool pool = TokenPool(p.localPool);
TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1);
// Encode remote pool addresses (single entry)
bytes[] memory remotePoolAddressesEncoded = new bytes[](1);
remotePoolAddressesEncoded[0] = abi.encode(p.remotePool);
chainUpdates[0] = TokenPool.ChainUpdate({
remoteChainSelector: p.remoteSelector,
remotePoolAddresses: remotePoolAddressesEncoded,
remoteTokenAddress: abi.encode(p.remoteToken),
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}),
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0})
});
// No removals, apply chain updates
uint64[] memory chainSelectorRemovals = new uint64[](0);
pool.applyChainUpdates(chainSelectorRemovals, chainUpdates);
console.log("Chain update applied to pool at address:");
console.log(p.localPool);
vm.stopBroadcast();
}
}
了解 ConfigurePools 脚本
在此脚本中,我们首先加载 RPC URL 和私钥的环境变量。然后,我们从 deployments.json 文件。我们使用以下方法为 HyperEVM 和 BSC 获取必要的 CCIP 常量: 常量 合同。这些常量包括 TokenAdminRegistry 设置每个代币的池所需的地址和链选择器。
我们定义了一个 参数 用于存储配置每个池所需的所有参数的结构体。这不仅更易于阅读,还有助于避免 Solidity 中出现的“栈深度过大”错误。我们将该 setPool 在...上的函数 TokenAdminRegistry 合约用于将每个代币映射到其对应的资金池。接下来,我们使用以下方式为每个资金池配置远程链选择器、远程资金池地址和远程代币地址: applyChainUpdates 在...上的函数 TokenPool 合同。在此示例中,为简化起见,我们禁用了速率限制,但在生产环境中,您可以根据实际需求配置适当的速率限制。
该脚本将向 qWHYPE 合约存入少量 HYPE 以铸造 qWHYPE 代币,然后执行跨链转账至 BSC。在 剧本 项目目录下名为 DepositAndTransferTokens.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/DepositAndTransferTokens.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
import {Constants} from "./Constants.s.sol";
import {qWHYPE} from "../src/qWHYPE.sol";
contract DepositAndTransferTokens is Script {
string internal constant OUTPUT_PATH = "script/output/deployments.json";
/// forge script script/DepositAndTransferTokens.s.sol:DepositAndTransferTokens
function run() external {
// Load env vars
bytes memory hyperevm = bytes(vm.envString("HYPEREVM_RPC"));
bytes memory bsc = bytes(vm.envString("BSC_RPC"));
uint256 pk = vm.envUint("PRIVATE_KEY");
require(hyperevm.length != 0, "HYPEREVM_RPC not set");
require(bsc.length != 0, "BSC_RPC not set");
// Read deployed token addresses
string memory json = vm.readFile(OUTPUT_PATH);
address tokenHyp = vm.parseJsonAddress(json, ".qWHYPE_hyperevm");
// Create forks and select HyperEVM (source)
uint256 hyperFork = vm.createFork(string(hyperevm));
vm.selectFork(hyperFork);
// Resolve CCIP constants for source and destination
Constants constants = new Constants();
Constants.CCIPConstants memory cfgHyp = constants.getCCIPConstants(999);
Constants.CCIPConstants memory cfgBsc = constants.getCCIPConstants(56);
address router = cfgHyp.router;
uint64 destinationChainSelector = cfgBsc.chainSelector;
address sender = vm.addr(pk);
// Amount to wrap and transfer: 0.01 HYPE -> 0.01 qWHYPE (18 decimals)
uint256 amount = 0.01 ether;
vm.startBroadcast(pk);
// Deposit HYPE to mint qWHYPE on HyperEVM
qWHYPE(tokenHyp).deposit{value: amount}();
console.log("Deposited and minted qWHYPE amount");
console.log(amount);
// Approve router to spend qWHYPE
IERC20(tokenHyp).approve(router, amount);
console.log("Approved router to spend qWHYPE");
// Build CCIP EVM2AnyMessage for token transfer
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: tokenHyp, amount: amount});
Client.EVMExtraArgsV1 memory extraArgs = Client.EVMExtraArgsV1({gasLimit: 0});
bytes memory extraArgsBytes = Client._argsToBytes(extraArgs);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(sender),
data: abi.encode(),
tokenAmounts: tokenAmounts,
feeToken: address(0), // pay fees in native
extraArgs: extraArgsBytes
});
// Route via CCIP using native token for gas
IRouterClient routerClient = IRouterClient(router);
require(routerClient.isChainSupported(destinationChainSelector), "Dest chain not supported");
uint256 fee = routerClient.getFee(destinationChainSelector, message);
console.log("Estimated fee (native)", fee);
bytes32 messageId = routerClient.ccipSend{value: fee}(destinationChainSelector, message);
console.log("CCIP messageId");
console.logBytes32(messageId);
vm.stopBroadcast();
}
}
理解 DepositAndTransferTokens 脚本
在此脚本中,我们首先加载 RPC URL 和私钥的环境变量。然后,我们从 deployments.json 文件。我们创建了HyperEVM链的一个分叉,该分叉将作为我们进行跨链转账的源链。
我们使用以下方法为 HyperEVM 和 BSC 获取必要的 CCIP 常量: 常量 合同。这些常量包括 路由器 发送跨链消息所需的地址和链选择器。我们指定要存入和转出的 HYPE 数量,本例中为 0.01 HYPE。
我们称之为 押金 在...上的函数 qWHYPE 通过存入 HYPE 来铸造 qWHYPE 代币。接下来,我们授权 Router 动用我们的 qWHYPE 代币。然后,我们构建一个 Client.EVM2AnyMessage 一个包含跨链转账详细信息的结构体,其中包括收款地址、代币数量以及任何额外参数。
最后,我们调用 ccipSend 在路由器合约上调用该函数以发送跨链消息,并使用源链的原生代币(HYPE)支付所需手续费。该脚本会记录预估手续费以及 CCIP 转账的消息 ID。该消息 ID 可用于在 Chainlink CCIP 浏览器.
该脚本将执行 qWHYPE 代币的跨链转账,支持双向转账。在 剧本 项目目录下名为 TransferTokens.s.sol 并将下面的代码粘贴到其中。
点击展开代码
创建文件:
touch script/TransferTokens.s.sol
将以下代码粘贴到文件中:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
import {Constants} from "./Constants.s.sol";
// Transfer-only script: does NOT deposit. Assumes caller holds qWHYPE on source chain.
contract TransferTokens is Script {
string internal constant OUTPUT_PATH = "script/output/deployments.json";
uint256 internal constant AMOUNT = 0.01 ether;
/// forge script script/TransferTokens.s.sol:TransferTokens --sig 'run(string)' <to-bsc | to-hyperevm>
function run(string memory to) external {
bool toBsc = _eq(to, "to-bsc");
require(toBsc || _eq(to, "to-hyperevm"), "to must be 'to-bsc' or 'to-hyperevm'");
_run(toBsc);
}
function run() external {
string memory to = vm.envOr("TO", string(""));
require(bytes(to).length != 0, "Set TO or use run(string)");
bool toBsc = _eq(to, "to-bsc");
require(toBsc || _eq(to, "to-hyperevm"), "to must be 'to-bsc' or 'to-hyperevm'");
_run(toBsc);
}
function _run(bool toBsc) internal {
// Validate RPCs and select source fork
require(bytes(vm.envString("HYPEREVM_RPC")).length != 0, "HYPEREVM_RPC not set");
require(bytes(vm.envString("BSC_RPC")).length != 0, "BSC_RPC not set");
vm.selectFork(vm.createFork(toBsc ? vm.envString("HYPEREVM_RPC") : vm.envString("BSC_RPC")));
// Resolve router and destination selector
address router = (new Constants()).getCCIPConstants(block.chainid).router;
uint64 destSelector = (new Constants()).getCCIPConstants(toBsc ? 56 : 999).chainSelector;
// Source token address
address srcToken = toBsc
? vm.parseJsonAddress(vm.readFile(OUTPUT_PATH), ".qWHYPE_hyperevm")
: vm.parseJsonAddress(vm.readFile(OUTPUT_PATH), ".qWHYPE_bsc");
// Start broadcasting
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
// Approve router to spend qWHYPE
IERC20(srcToken).approve(router, AMOUNT);
console.log("Approved router to spend qWHYPE");
// Build token amounts
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: srcToken, amount: AMOUNT});
// Send via CCIP using native gas as fee
require(IRouterClient(router).isChainSupported(destSelector), "Dest chain not supported");
uint256 fee = IRouterClient(router).getFee(
destSelector,
Client.EVM2AnyMessage({
receiver: abi.encode(vm.addr(vm.envUint("PRIVATE_KEY"))),
data: abi.encode(),
tokenAmounts: tokenAmounts,
feeToken: address(0),
extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0}))
})
);
console.log("Estimated fee (native)");
console.log(fee);
console.log("CCIP messageId");
console.logBytes32(
IRouterClient(router).ccipSend{value: fee}(
destSelector,
Client.EVM2AnyMessage({
receiver: abi.encode(vm.addr(vm.envUint("PRIVATE_KEY"))),
data: abi.encode(),
tokenAmounts: tokenAmounts,
feeToken: address(0),
extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0}))
})
)
);
vm.stopBroadcast();
}
function _eq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
了解 TransferTokens 脚本
在此脚本中,我们首先检查终端中传入的参数是否为 to-bsc 或 to-hyperevm,以指示转账的方向。基于此,我们创建相应源链(HyperEVM 或 BSC)的分叉。我们使用 常量 合同。这些常量包括 路由器 发送跨链消息所需的地址和链选择器。
我们设定一个恒定的 qWHYPE 转账额度,在本例中为 0.01 qWHYPE。我们授权 Router 消耗我们的 qWHYPE 代币,并构建一个 Client.EVM2AnyMessage 一个包含跨链转账详细信息的结构体,其中包括收款地址、代币数量以及任何额外参数。
最后,我们调用 ccipSend 在路由器合约上调用该函数以发送跨链消息,并使用源链的原生代币(HYPE 或 BNB)支付所需手续费。该脚本会记录预估手续费以及 CCIP 转账的消息 ID。该消息 ID 可用于在 Chainlink CCIP 浏览器.
太好了!现在我们已经编写了所有必要的脚本,用于部署和配置 CCIP 代币池,并在 HyperEVM 和 BSC 之间进行 qWHYPE 代币的跨链转账。接下来,我们将在下一节中执行这些脚本。
运行脚本
既然我们已经编写好了所有必要的脚本,就可以按顺序执行它们,以部署和配置我们的 CCIP 代币池,并执行 qWHYPE 代币的跨链转账。
此前,我们讨论过 Hyperliquid 的多区块架构:较小的区块最多使用 2M gas,而较大的区块最多可使用 30M gas。我们的合约部署交易肯定会超过 2M gas 的限制,因此我们需要先启用账户使用较大区块的功能。要实现这一点,请在终端中执行以下命令:
源文件 .env
npx @layerzerolabs/hyperliquid-composer set-block --size 大 --network 主网 --私钥 $PRIVATE_KEY
我们建议不要在命令行中直接传递私钥,因为它可能会被保存在您的 shell 历史记录中。请务必使用环境变量或安全的方法来处理敏感信息!
如果您的钱包尚未在 HyperCore 上注册,此命令可能会因以下原因失败: 该用户或 API 钱包不存在。 请确保您已在 Hyperliquid 上完成充值或交易,以注册您的钱包。如果您按照 从 HyperCore 过渡到 HyperEVM 没错,您的钱包已经注册成功了!
太好了!我们的账户现在可以使用更大的区块了。接下来,我们将使用 DeployTokens.s.sol 脚本:
forge 脚本 script/DeployTokens.s.sol:DeployTokens --broadcast --验证 --验证器 etherscan
该命令将把我们的代币部署到 HyperEVM 和 BSC 两个网络上。您将在终端中看到部署进度,并在交易确认后看到交易哈希值。该 --广播 该标志表示我们希望将交易发送到网络,而 --验证 以及 --验证器 etherscan 部署完成后,flags 会自动在 hyperevmscan.io 和 bscscan.com 上验证我们的合约。您还会注意到您的 deployments.json 文件中已填入了已部署合约的地址。您的终端输出应类似如下:

接下来,我们将使用 DeployPools.s.sol 脚本:
forge 脚本 script/DeployPools.s.sol:DeployPools --broadcast --验证 --验证器 etherscan
该命令将把我们的代币池部署到 HyperEVM 和 BSC 两个网络上。与上一步类似,您将在终端中看到部署进度,并在交易确认后看到交易哈希。该 deployments.json 该文件将更新为已部署的池地址。您的终端输出应类似如下:

接下来,我们将使用 SetupAdmin.s.sol 使用以下命令编写脚本并配置我们的资源池: ConfigurePools.s.sol 脚本。我们可以使用以下命令依次执行这两个脚本:
forge 脚本 script/SetupAdmin.s.sol:SetupAdmin --broadcast
forge 脚本 script/ConfigurePools.s.sol:ConfigurePools --broadcast
与上一步骤一样,一旦交易得到确认,您将看到类似的终端输出,其中包含交易哈希值。
我们的首次跨链转账
最后,我们将使用……将 qWHYPE 代币从 HyperEVM 跨链转账至 BSC。 DepositAndTransferTokens.s.sol 脚本:
forge 脚本 script/DepositAndTransferTokens.s.sol:DepositAndTransferTokens --broadcast
该命令将向 HyperEVM 存入 HYPE 以铸造 qWHYPE,随后将 qWHYPE 代币转至 BSC。交易确认后,您将在终端中看到转账进度及交易哈希。您还将看到该转账的 CCIP 消息 ID,可用于在Chainlink CCIP 浏览器上追踪转账状态。您的终端将输出如下所示的消息 ID:
- 终端输出
- CCIP 资源库
- HyperEVM 浏览器
- BSC Explorer
显示 CCIP 消息 ID 的终端输出
CCIP Explorer 显示跨链转账的状态
HyperEVM 浏览器显示转账交易的详细信息
BSC Explorer 显示已接收转账交易的详细信息
让我们验证一下是否 qWHYPE 代币已成功存入 BSC 网络。请打开您的 MetaMask 钱包,切换至 BSC 网络,然后导入该 qWHYPE 使用您在 deployments.json 包含密钥的文件 qWHYPE_bsc. 如果您在导入代币时需要帮助,请按照 MetaMask 的指南操作 这里. 您应该会在钱包中看到 0.01 qWHYPE 代币的余额,这表明跨链转账已成功!
向任意方向转移
该 TransferTokens.s.sol 该脚本可用于在 HyperEVM 和 BSC 之间双向转移 qWHYPE 代币。您可以通过传递以下任一参数来指定转移方向: to-bsc 或 to-hyperevm 作为运行脚本时的参数。例如,若要将 qWHYPE 代币从 BSC 转入 HyperEVM,请运行以下命令:
forge 脚本 script/TransferTokens.s.sol:TransferTokens --broadcast --sig 'run(string)' 'to-hyperevm'
输出结果将与我们的首次跨链转账类似,显示交易哈希和 CCIP 消息 ID。将该消息 ID 粘贴到Chainlink CCIP 浏览器中,现在将显示反向的转账记录:

结论
恭喜!您已成功掌握如何使用 Chainlink CCIP 在 Hyperliquid 上进行代币跨链桥接。您已部署了自己的 ERC-20 代币,设置了 CCIP 代币池,并在 HyperEVM 与 BSC 之间完成了 qWHYPE 代币的跨链转账。
下一步
既然您已经拥有了一个可正常运行的跨链桥,就可以进一步优化和定制您的配置了。以下是一些建议:
- Hyperliquid Bridge UI:使用 React 或 Vue.js 等框架为您的桥接器构建一个用户友好的界面。这样,用户无需使用命令行工具,即可轻松与您的桥接器进行交互。
- 速率限制: 在您的代币池中实施速率限制,以防止滥用并确保公平使用。您可以在
ConfigurePools.s.sol脚本。 - 实现更多链:扩展您的桥接方案,以支持 Chainlink CCIP 支持的其他链。您可以按照本指南中概述的相同步骤,在其他链上部署和配置代币池。请在CCIP 目录中查看通道和支持的链。
- 包装其他 HIP-1 资产:您可以按照与处理 HYPE 时类似的流程,在 Hyperliquid 上为其他 HIP-1 资产创建并部署额外的包装代币。唯一的区别在于,需要为每种资产分别创建一个包装 ERC-20 合约。这将使用户能够在 HyperEVM 与其他链之间桥接更广泛的资产。
更多资源
- Hyperliquid 文档
- Quicknode Hyperliquid 指南
- Quicknode Hyperliquid 文档
- 《铸造书》
- 视频:什么是 Hyperliquid HyperEVM 以及如何入门
如果您遇到困难或有任何疑问,欢迎在我们的Discord 频道中提出。请关注我们的X账号(@Quicknode)或Telegram 公告频道,随时了解最新动态。
我们 ❤️ 您的反馈!
如果您对本文档有任何反馈或疑问,请告诉我们。我们非常期待您的反馈!