使用 Chainlink CCIP 在 Hyperliquid 上搭建跨链桥

本文介绍了如何使用 Chainlink CCIP 在 Hyperliquid 上搭建跨链桥,包括创建 HIP-1 资产的包装合约、部署 CCIP 代币池、配置管理角色,并在 HyperEVM 和 BSC 之间执行跨链传输。通过 Foundry 脚本完成合约部署、配置和交互,最终实现 HYPE 代币在 HyperEVM 和 BSC 之间的跨链转移。

概述

Hyperliquid 将两层结构置于一个统一的状态之下:HyperCore,一个通过签名操作访问的超低延迟订单簿引擎;以及 HyperEVM,一个完全兼容 EVM 的网络,用于标准的 Solidity 开发。

Chainlink CCIP 允许使用 销毁并铸造 (burn-and-mint) 机制在链之间桥接资产。在 Hyperliquid 中,HyperCore(HIP-1)资产与其 HyperEVM 表示之间的转换由原生协议流程处理。

在本指南中,你将使用 Foundry 封装一个对应于 HIP-1 资产的 HyperEVM 代币,并使用 CCIP 在 HyperEVM 和 BNB 智能链(BSC)之间桥接该代币。

你将做什么

  • 为链接到 HIP-1 资产的 HyperEVM 代币创建一个封装合约
  • 在 HyperEVM 和 BSC 上部署和配置 CCIP 代币池
  • 验证我们链上部署的代币和池合约
  • 使用 Foundry 脚本执行 HyperEVM 和 BSC 之间的 CCIP 转移

你需要的

免责声明

在本指南中,我们使用的是每个链的 主网 (MAINNET)。这是因为在撰写本文时,Chainlink CCIP 在 HyperEVM 测试网上不可用。请谨慎操作,只使用你能承受损失的资金。

设置开发环境

我们将使用 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="your_hyperevm_rpc_url"
BSC_RPC="your_bsc_rpc_url"
PRIVATE_KEY="your_private_key"
ETHERSCAN_API_KEY="your_etherscan_api_key"

我们稍后将讨论每个变量的含义以及如何获取它们。下一步是删除 Foundry 为我们生成的默认 src/Counter.sol 合约和 script/Counter.s.sol 脚本。我们不会在本指南中使用它们。或者,你可以在终端中运行以下命令来删除这些文件:

rm src/Counter.sol
rm script/Counter.s.sol

你还需要配置项目文件夹根目录中的 foundry.toml 文件,以包含 Solidity 编译器的重要设置以及我们将要使用的导入语句的重映射。你的 foundry.toml 文件应如下所示:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 200

remappings = [\
  '@chainlink/contracts-ccip/=node_modules/@chainlink/contracts-ccip/',\
  '@chainlink/contracts/=node_modules/@chainlink/contracts/',\
]

fs_permissions = [{ access = "read-write", path = "./" }]

在此文件中,我们添加了 optimizer 设置来优化我们的智能合约以进行部署。fs_permissions 条目允许 Foundry 读取和写入项目目录中的文件。remappings 条目是 Chainlink 合约包的路径,我们将在本指南中使用它们。接下来我们将安装这些包。

安装依赖项

在本指南中,我们将使用两个 Chainlink 合约包:@chainlink/contracts-ccip@chainlink/contracts。第一个包包含我们将要交互的核心 CCIP 合约以及我们将要部署的 CCIP 代币池合约。第二个包包含符合 Chainlink 跨链传输要求的定制 ERC20 合约。

我们还将安装 @layerzerolabs/hyperliquid-composer 包,该包将用于增加我们在 HyperEVM 上的交易的 Gas Limit。HyperEVM 具有多区块架构,该架构由 Gas Limit 为 200 万 Gas 单位的较小区块和 Gas Limit 为 3000 万 Gas 单位的较大区块组成。较小区块的区块时间为 1 秒,而较大区块的区块时间为 1 分钟。默认情况下,HyperEVM 上的帐户只能发送符合较小区块 Gas Limit 的交易。此包将允许我们启用我们的帐户以发送 Gas Limit 更高的交易,以适应较大的区块。

要安装这些包,请使用你喜欢的包管理器输入以下命令:

  • npm
  • yarn
npm install @chainlink/contracts-ccip @chainlink/contracts @layerzerolabs/hyperliquid-composer
yarn add @chainlink/contracts-ccip @chainlink/contracts @layerzerolabs/hyperliquid-composer

太棒了!你已成功安装了我们将在本指南中使用的依赖项。接下来,我们将讨论需要在我们的 .env 文件中填充的环境变量。

环境变量

QuickNode 终端节点

首先,我们需要获取 HyperEVM 和 BSC 的相应 RPC 终端节点。你可以从 QuickNode 获取这些端点。只需注册一个免费试用帐户,创建一个新的多链终端节点,然后复制每个链的 HTTPS URL。将相应的 URL 粘贴到你的 .env 文件中的 HYPEREVM_RPCBSC_RPC 变量中。

Etherscan API

接下来,转到你在 Etherscan 上的个人资料,然后导航到 API KEYS 选项卡。如果你没有帐户,请在此处创建一个。在这里,你将创建一个 API 密钥,该密钥将帮助你在链上验证你的智能合约。复制 API 密钥并将其粘贴到你的 .env 文件中的 ETHERSCAN_API_KEY 变量中。

Etherscan API v2

Etherscan 已将其 API 升级到 v2。当你在 Etherscan 上创建 API 密钥时,它也可以用于 Etherscan 支持的多个链,包括 HyperEVM 和 BSC。因此,你不需要为每个链创建单独的 API 密钥。

私钥

最后,进入你的 MetaMask 并复制你的一个帐户的私钥。要了解如何访问你的私钥,请查看这个简短的指南。将此私钥粘贴到你的 .env 文件中的 PRIVATE_KEY 变量中。

这样,你的 .env 文件中的所有环境变量现在都应该已填充。此设置过程的最后一步是将适当的网络添加到我们的 MetaMask 钱包。

将网络添加到 MetaMask

我们将把 HyperEVM Mainnet 和 Binance Smart Chain Mainnet 网络添加到我们的 MetaMask 钱包。添加这些网络的最简单方法是转到 hyperevmscan.iobscscan.com,然后单击页面左下角的 Add 按钮。这会将网络添加到你的 MetaMask 钱包:

恭喜!你已成功设置了本指南的开发环境。我们的下一步是将 HIP-1 资产从 HyperCore 桥接到 HyperEVM。

从 HyperCore 桥接到 HyperEVM

你现在将把 HIP-1 资产从 HyperCore 桥接到 HyperEVM。此过程涉及将 HIP-1 资产转换为其对应的 HyperEVM 代币表示形式。在本指南中,我们将使用 Hyperliquid 的原生生态系统代币:HYPE。转到 Hyperliquid 交易界面 并使用 MetaMask 登录以开始。

如果你还没有 HYPE 代币,你可以通过将 Arbitrum Mainnet 中的 USDC 存入 Hyperliquid,然后在交易界面上的 现货 (Spot) 上将其交易为 HYPE 来获取一些 HYPE 代币:

Hyperliquid 交易界面

提醒

在本指南中,我们使用的是每个链的 主网 (Mainnet)。这是因为在撰写本文时,Chainlink CCIP 在 HyperEVM 测试网上不可用。请谨慎操作,只使用你能承受损失的资金。

一旦你有了 HYPE,你就可以继续将其桥接到 HyperEVM。为此,请单击图表下方页面底部的 转移到/从 EVM (Transfer to/from EVM) 按钮。只需输入你要桥接的 HYPE 数量,然后单击 确认 (Confirm)

将 HYPE 桥接到 HyperEVM

将 HYPE 桥接到 HyperEVM

太棒了!你已成功将 HYPE 从 HyperCore 桥接到 HyperEVM。你现在应该能够在 HyperEVM 网络上的 MetaMask 中看到你的 HYPE 余额。我们现在将继续为 HYPE 创建一个符合 Chainlink 跨链传输要求的封装合约。

为 HYPE 创建封装合约

要使用 Chainlink CCIP 在 HyperEVM 和 BSC 之间桥接 HYPE,我们需要创建一个符合 Chainlink 跨链传输要求的封装合约。这涉及创建一个新的 ERC20 代币合约,该合约封装 HyperEVM 上现有的 HYPE 资产,以及 BSC 上相应的 ERC20 代币合约。

我们将创建一个符号为 qWHYPEQuickNode 封装 Hype (QuickNode Wrapped Hype) 代币。输入以下命令以在项目目录的 src 文件夹中创建一个新的 Solidity 文件,然后将以下代码粘贴到其中。

  • 创建文件
  • 代码
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 合约中的代码。该合约从 @chainlink/contracts 包中导入 BurnMintERC20 合约,该合约提供了铸造和销毁代币所需的必要功能。当用户将 HYPE 存入合约时,它会向他们的地址铸造等量的 qWHYPE 代币。相反,当用户提取 qWHYPE 代币时,合约会销毁代币并将相应数量的 HYPE 转回用户的地址。

这个特定的合约将部署在 HyperEVM 网络上。在 BSC 上,我们将直接部署基础 BurnMintERC20 合约,因为它不需要任何其他功能。我们将在下一节中使用脚本部署两个合约。

编写 Foundry 脚本

我们将编写几个 Foundry 脚本来部署和与我们的智能合约交互。本节将分为几个部分,以详细介绍每个脚本。我们将创建以下脚本并按此顺序执行它们:

脚本名称 描述
DeployTokens.s.sol 在 HyperEVM 上部署 qWHYPE 合约,并在 BSC 上部署基础 BurnMintERC20 合约
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

此脚本将在 HyperEVM 上部署 qWHYPE 合约,并在 BSC 上部署基础 BurnMintERC20 合约。在项目目录的 script 文件夹中创建一个名为 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、部署者地址和私钥作为参数。此函数创建指定链的一个 fork,开始使用提供的私钥广播交易,并在 HyperEVM 上部署 qWHYPE 合约,或者在 BSC 上部署基础的 BurnMintERC20 合约。部署后,它会授予部署者地址在已部署的 token 合约上的铸币和燃烧角色。

此脚本将在 HyperEVM 和 BSC 上部署 CCIP token 池合约。在你的项目目录的 script 文件夹中创建一个名为 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 文件中读取已部署的 token 地址。我们使用 Constants 合约为 HyperEVM 和 BSC 获取必要的 CCIP 常量。

这些常量包括 Chainlink 在每个链上部署的核心 CCIP 合约,包括 RouterTokenAdminRegistryRegistryModuleOwnerCustomRiskManagementNetwork (RMN) 代理地址,这些地址是池部署所必需的。Router 负责路由跨链消息,TokenAdminRegistry 管理 token 地址到其各自池的映射,RegistryModuleOwnerCustom 用于不同所有权模式的 token 管理注册,而 RMN 代理用于验证跨链消息的 RMN 签名。

使用这些常量,我们在两条链上部署 BurnMintTokenPool 合约,并授予它们各自 token 合约上的铸币和燃烧角色。最后,我们使用已部署的 token 池的地址更新 deployments.json 文件。

此脚本将在每个链的 CCIP 注册合约上注册我们已部署的 token 的管理员。在你的项目目录的 script 文件夹中创建一个名为 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 文件中读取已部署的 token 地址。CCIP 注册合约将需要这些 token 地址,以获取然后在各自链上注册每个 token 的管理员。

注册 CCIP token 的管理员分两个步骤完成。首先,调用 RegistryModuleOwnerCustom 合约。此合约用于为 token 提议一个新的管理员。当前管理员(通过我们的 qWHYPE 合约上的 getCCIPAdmin 检索)成为待定的管理员。RegistryModuleOwnerCustom 调用 TokenAdminRegistry 合约以提议新的管理员。然后,此状态映射到 TokenAdminRegistry 合约中的 TokenConfig 结构。然后是最后一步,调用 TokenAdminRegistry 合约以从给定 token 的 TokenConfig 中找到的待定管理员那里接受管理员角色。

你可能想知道为什么注册 token 的管理员是通过这种复杂的两步过程完成的。为什么不直接在 TokenAdminRegistry 合约上注册管理员?这种设计选择有几个原因:

  • 增强的安全性:两步流程允许当前管理员在完成之前审查并批准新的管理员。
  • 多重签名兼容性:通过提议和稍后接受管理员更改,与多重签名钱包(例如,Safe)兼容。
  • 所有权模式:通过 getCCIPOwner 与 CCIP 的所有权模型兼容,并且与其他模型中的 OpenZeppelin 的 Ownable 风格的所有权兼容,从而允许加入各种现有 token。

此脚本将配置我们的 CCIP token 池,以在彼此之间进行跨链传输。在你的项目目录的 script 文件夹中创建一个名为 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 文件中读取已部署的 token 和池地址。我们使用 Constants 合约为 HyperEVM 和 BSC 获取必要的 CCIP 常量。这些常量包括 TokenAdminRegistry 地址和链选择器,用于为每个 token 设置池。

我们定义了一个 Params 结构来保存配置每个池所需的所有参数。它不仅更具可读性,而且还有助于避免 Solidity 中的“堆栈太深”错误。我们调用 TokenAdminRegistry 合约上的 setPool 函数,以将每个 token 映射到其各自的池。接下来,我们使用 TokenPool 合约上的 applyChainUpdates 函数,使用远程链选择器、远程池地址和远程 token 地址来配置每个池。在此示例中,为简单起见,我们禁用了速率限制,但在生产环境中,你可以根据需要配置适当的速率限制。

此脚本将在 qWHYPE 合约上存入少量 HYPE,以铸造 qWHYPE token,然后执行到 BSC 的跨链传输。在你的项目目录的 script 文件夹中创建一个名为 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 文件中读取已部署的 token 地址。我们创建一个 HyperEVM 链的 fork,它将成为我们跨链传输的源链。

我们使用 Constants 合约为 HyperEVM 和 BSC 获取必要的 CCIP 常量。这些常量包括发送跨链消息所需的 Router 地址和链选择器。我们定义要存入和转移的 HYPE 数量,在此示例中为 0.01 HYPE。

我们调用 qWHYPE 合约上的 deposit 函数,通过存入 HYPE 来铸造 qWHYPE token。接下来,我们批准 Router 花费我们的 qWHYPE token。然后,我们构建一个 Client.EVM2AnyMessage 结构,其中包含我们跨链传输的详细信息,包括接收者地址、token 数量和任何额外的参数。

最后,我们调用 Router 合约上的 ccipSend 函数以发送跨链消息,并以源链(HYPE)的本地 token 支付所需的费用。该脚本会记录估计的费用和 CCIP 传输的消息 ID。此消息 ID 可用于在 Chainlink CCIP Explorer 上跟踪传输的状态。

此脚本将从任一方向执行 qWHYPE token 的跨链传输。在你的项目目录的 script 文件夹中创建一个名为 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.
// 仅限转移脚本:不存款。 假设调用者在源链上持有 qWHYPE。
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>
    // / 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
        // 验证 RPC 并选择源 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
        // 源 token 地址
        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
        // 批准路由器花费 qWHYPE
        IERC20(srcToken).approve(router, AMOUNT);
        console.log("Approved router to spend qWHYPE");

        // Build token amounts
        // 构建 token 数量
        Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
        tokenAmounts[0] = Client.EVMTokenAmount({token: srcToken, amount: AMOUNT});

        // Send via CCIP using native gas as fee
        // 使用本地 gas 作为费用通过 CCIP 发送
        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)的 fork。我们使用 Constants 合约为两条链获取必要的 CCIP 常量。这些常量包括发送跨链消息所需的 Router 地址和链选择器。

我们定义要传输的 qWHYPE 的常量数量,在此示例中为 0.01 qWHYPE。我们批准 Router 花费我们的 qWHYPE token,并构建一个 Client.EVM2AnyMessage 结构,其中包含我们跨链传输的详细信息,包括接收者地址、token 数量和任何额外的参数。

最后,我们调用 Router 合约上的 ccipSend 函数以发送跨链消息,并以源链(HYPE 或 BNB)的本地 token 支付所需的费用。该脚本会记录估计的费用和 CCIP 传输的消息 ID。此消息 ID 可用于在 Chainlink CCIP Explorer 上跟踪传输的状态。

很好! 我们现在已经编写了所有必要的脚本来部署和配置我们的 CCIP token 池,并执行 HyperEVM 和 BSC 之间 qWHYPE token 的跨链传输。 我们现在将继续在下一节中执行这些脚本。

运行脚本

现在我们已经编写了所有必要的脚本,我们可以按顺序执行它们,以部署和配置我们的 CCIP 代币池,并执行 qWHYPE 代币的跨链转移。

之前,我们讨论了 Hyperliquid 的多区块架构,较小的区块最多使用 2M gas,而较大的区块可以使用高达 30M gas。我们的合约部署交易肯定会超过 2M gas 的限制,因此我们需要首先启用我们的帐户以使用更大的区块。为此,请在你的终端中执行以下命令:

source .env
npx @layerzerolabs/hyperliquid-composer set-block --size big --network mainnet --private-key $PRIVATE_KEY

信息

我们建议不要直接在命令行中传递你的私钥,因为它可能会存储在你的 shell 历史记录中。始终使用环境变量或安全的方法来处理敏感信息!

警告

如果你的钱包未在 HyperCore 上注册,则此命令可能会失败,并显示 User or API Wallet does not exist. 确保你已在 Hyperliquid 上进行存款或交易以注册你的钱包。如果你正确地按照 从 HyperCore 桥接到 HyperEVM 中的步骤操作,你的钱包已经注册!

太棒了!我们的帐户现在可以使用更大的区块。我们现在将使用 DeployTokens.s.sol 脚本部署我们的代币:

forge script script/DeployTokens.s.sol:DeployTokens --broadcast --verify --verifier etherscan

此命令会将我们的代币部署到 HyperEVM 和 BSC 网络。你将在终端中看到部署进度,并在交易确认后看到交易哈希。--broadcast 标志表示我们想要将交易发送到网络,而 --verify--verifier etherscan 标志将在部署后自动在 hyperevmscan.io 和 bscscan.com 上验证我们的合约。你还会注意到你的 deployments.json 文件已填充已部署的合约地址。你的终端输出应如下所示:

Deployment Output

接下来,我们将使用 DeployPools.s.sol 脚本部署我们的 CCIP 代币池:

forge script script/DeployPools.s.sol:DeployPools --broadcast --verify --verifier etherscan

此命令会将我们的代币池部署到 HyperEVM 和 BSC 网络。与上一步类似,你将在终端中看到部署进度,并在交易确认后看到交易哈希。deployments.json 文件将使用已部署的池地址进行更新。你的终端输出应如下所示:

Pool Deployment Output

接下来,我们将使用 SetupAdmin.s.sol 脚本为我们的代币设置管理员,并使用 ConfigurePools.s.sol 脚本配置我们的池。我们可以使用以下命令按顺序执行这两个脚本:

forge script script/SetupAdmin.s.sol:SetupAdmin --broadcast
forge script script/ConfigurePools.s.sol:ConfigurePools --broadcast

与上一步类似,你将在交易确认后看到类似的终端输出和交易哈希。

我们的第一次跨链转移

最后,我们将使用 DepositAndTransferTokens.s.sol 脚本执行从 HyperEVM 到 BSC 的 qWHYPE 代币的跨链转移:

forge script script/DepositAndTransferTokens.s.sol:DepositAndTransferTokens --broadcast

此命令会将 HYPE 存入 HyperEVM 以铸造 qWHYPE,然后将 qWHYPE 代币转账到 BSC。你将在终端中看到转移进度,并在交易确认后看到交易哈希。你还将看到转移的 CCIP 消息 ID,你可以使用该 ID 在 Chainlink CCIP Explorer 上跟踪转移的状态。你的终端将输出消息 ID,如下所示:

  • 终端输出
  • CCIP Explorer
  • HyperEVM Explorer
  • BSC Explorer

Transfer Output终端输出显示 CCIP 消息 ID

CCIP ExplorerCCIP Explorer 显示跨链转移的状态

HyperEVM ExplorerHyperEVM Explorer 显示转移交易的详细信息

BSC ExplorerBSC Explorer 显示收到的转移交易的详细信息

让我们验证一下 qWHYPE 代币是否已在 BSC 上收到。打开你的 MetaMask 钱包,切换到 BSC 网络,并使用 deployments.json 文件中找到的地址和 qWHYPE_bsc 键导入 qWHYPE 代币。如果你需要导入代币的帮助,请按照 MetaMask 的指南。你应该在你的钱包中看到 0.01 qWHYPE 代币的余额,确认跨链转移已成功!

MetaMask Balance

任何方向的转移

TransferTokens.s.sol 脚本可用于在 HyperEVM 和 BSC 之间以任何方向转移 qWHYPE 代币。你可以通过在运行脚本时传递 to-bscto-hyperevm 作为参数来指定转移的方向。例如,要将 qWHYPE 代币从 BSC 转移到 HyperEVM,你将运行以下命令:

forge script script/TransferTokens.s.sol:TransferTokens --broadcast --sig 'run(string)' 'to-hyperevm'

输出将与我们的第一次跨链转移类似,显示交易哈希和 CCIP 消息 ID。将消息 ID 粘贴到 Chainlink CCIP Explorer 现在将显示相反方向的转移:

CCIP Explorer Reverse

结论

恭喜!你已成功学习如何使用 Chainlink CCIP 在 Hyperliquid 上桥接代币。你已经部署了自己的 ERC-20 代币,设置了 CCIP 代币池,并执行了 HyperEVM 和 BSC 之间 qWHYPE 代币的跨链转移。

下一步

现在你有了一个有效的跨链桥,你可以进一步改进和自定义你的设置。以下是一些想法:

  • Hyperliquid 桥 UI:使用 React 或 Vue.js 等框架为你的桥构建一个用户友好的界面。这将允许用户轻松地与你的桥交互,而无需使用命令行工具。
  • 速率限制:在你的代币池上实施速率限制,以防止滥用并确保公平使用。你可以在 ConfigurePools.s.sol 脚本中配置速率限制。
  • 实施其他链:扩展你的桥以支持 Chainlink CCIP 支持的其他链。你可以按照本指南中概述的相同步骤在其他链上部署和配置代币池。在 CCIP 目录 上查看通道和支持的链。
  • 包装其他 HIP-1 资产:你可以为 Hyperliquid 上的其他 HIP-1 资产创建和部署其他包装代币,过程与我们为 HYPE 所做的类似。唯一的区别是需要为每个资产创建一个包装 ERC-20 合约。这将允许用户在 HyperEVM 和其他链之间桥接更广泛的资产。

更多资源

如果你遇到困难或有疑问,请将它们放在我们的 Discord 中。通过在 X (@QuickNode) 或我们的 Telegram 公告频道 上关注我们,了解最新信息。

我们 ❤️ 反馈!

如果你对此文档有任何反馈或疑问,请 告诉我们。我们很乐意听取你的意见!

  • 原文链接: quicknode.com/guides/hyp...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。