本文介绍了如何在Hyperliquid的HyperEVM上通过智能合约读取HyperCore的实时价格信息,无需依赖外部预言机。文章详细说明了如何设置Foundry项目,部署智能合约,并利用Hyperliquid提供的L1Read.sol接口和预编译合约来获取和转换预言机价格,为开发交易机器人、借贷协议等高级DApp打下基础。
Hyperliquid 的双重架构结合了高性能的交易引擎 ( HyperCore) 和 EVM 兼容性 ( HyperEVM),为 DeFi 开发者创造了独特的机会。虽然可能性是无限的,但最令人兴奋的应用之一是能够直接从你的 HyperEVM dApp 访问 HyperCore 的实时价格馈送,而无需单独的价格预言机。
这种独特的架构使得可以直接在你的智能合约中访问实时价格数据(如永续期货价格),而无需依赖外部预言机。对于希望从 EVM 环境原生访问 HyperCore 金融原语的 DeFi 构建者来说,这是一个改变游戏规则的功能。
在本指南中,你将构建一个 HyperEVM 智能合约,该合约从 HyperCore 读取实时价格。在此过程中,你将利用 Hyperliquid 的 L1Read.sol 接口和预编译合约来获取和转换预言机价格,从而为更高级的 dApp(如交易机器人、借贷协议和链上分析)奠定基础。
Hyperliquid 是一个针对高性能 DeFi 用例优化的 Layer 1 区块链。其核心引擎 HyperCore 处理具有亚秒级最终性的完全链上订单簿。这种性能通过 Hyperliquid 的共识机制 HyperBFT 实现,该机制可确保单区块最终性并支持高达每秒 200,000 个订单。另一方面,HyperEVM 将以太坊兼容的智能合约集成到此生态系统中,使开发者能够基于 HyperCore 的订单簿数据进行构建。
HyperCore 通过预编译公开其功能——预定义地址处的特殊合约,允许 HyperEVM 合约查询数据,如预言机价格、资产元数据和用户头寸。由于 Hyperliquid 提供的 L1Read.sol
合约,可以使用静态调用直接在 Solidity 中访问它们。为确保你使用的是正确的预编译地址,请始终参考 Hyperliquid 官方开发者文档 中的最新列表。
在本指南中,我们将使用 L1Read.sol
合约中提供的以下函数:
oraclePx(uint32 index)
:返回给定索引处的永续期货合约的预言机价格。利用 ORACLE_PX_PRECOMPILE_ADDRESS
预编译。perpAssetInfo(uint32 index)
:返回给定索引处的永续期货合约的元数据。利用 PERP_ASSET_INFO_PRECOMPILE_ADDRESS
预编译。读取和写入 HyperCore
Hyperliquid 的架构允许在 HyperEVM 中读取和写入 HyperCore。但是,它们的写入系统合约尚未在 HyperEVM 主网上提供。因此,在本指南中,我们将专注于从 HyperCore 读取。
在我们开始之前,请确保你执行以下操作:
首先,请确保你的机器上安装了 EVM 智能合约开发工具包 Foundry。如果未安装,请在你的终端中运行以下命令来安装它:
curl -L https://foundry.paradigm.xyz | bash
foundryup
如果你是 Foundry 的新手,请查看我们的 Foundry 教程 以开始使用。
要与 HyperEVM 交互,你必须使用 RPC 端点连接到 Hyperliquid 区块链。虽然你可以使用公共 RPC,但我们建议你使用专用端点进行生产用途。要获取你的端点,请注册一个免费的 QuickNode 帐户 并为 Hyperliquid (Mainnet) 创建一个端点。
你可以创建一个新钱包或使用现有钱包通过 Foundry 部署你的智能合约。
导入你的钱包时,我们建议你加密你的私钥以确保安全,而不是直接使用它。要了解为什么这很重要,请查看关于 如何在 Hardhat 和 Foundry 中保护你的私钥 的指南。
在你的终端中运行以下命令并输入你钱包的私钥和密码进行加密:
cast wallet import your-wallet-name --interactive
将 your-wallet-name
替换为你钱包的名称。系统将提示你输入你的私钥并设置加密密码。--interactive
标志可确保私钥不会保存在你的 shell 历史记录中,以确保安全。
对于新钱包,你可以使用以下命令创建一个新钱包:
cast wallet new
HYPE 是 Hyperliquid 上的原生代币,你需要一些 HYPE 代币来支付我们智能合约部署的 gas 费用。
对于测试网,你可以从 Hyperliquid 的 Testnet 水龙头 获取一些测试 USDC,并用它来购买一些 HYPE。如果你想使用主网,你可以在 Hyperliquid 主网 上购买 HYPE 代币。无论你使用的是测试网还是主网,你都需要将 HYPE 从 HyperCore 永续合约转移到 HyperCore 现货,然后再转移到 HyperEVM,所有这些都通过他们的 UI 完成,如下所示:
观看此视频,了解如何在 HyperEVM 上获取 HYPE 代币:
在本视频中,了解关于 Hyperliquid HyperEVM 的所有信息以及如何开始使用 HyperEVM。我们涵盖了 HyperCore 和 HyperEVM 之间的差异,以及如何获取/转移 HYPE 代币到 HyperEVM 钱包。
QuickNode
什么是 Hyperliquid HyperEVM 以及如何开始使用
QuickNode
在观看
/
订阅我们的 YouTube 频道以获取更多视频!订阅
在本节中,我们将向你展示如何通过利用 HyperEVM 的预编译在你的 HyperEVM dApp 中与 HyperCore 预言机价格交互。我们将介绍以下方法:
Cast
进行直接查询首先,让我们创建一个 Foundry 项目来使用。
forge init hyperevm-oracle-prices
cd hyperevm-oracle-prices
此命令将创建一个名为 hyperevm-oracle-prices
的新目录,并在其中初始化一个 Foundry 项目,其中包含一些初始文件(即 src/Counter.sol
)和配置(即 foundry.toml
)。
├── README.md
├── foundry.toml // Foundry 配置文件
├── lib
├── script
│ └── Counter.s.sol // Foundry 部署脚本
├── src
│ └── Counter.sol // 主合约文件
└── test
└── Counter.t.sol // 测试合约文件
重命名部署脚本和主合约文件以匹配项目名称,并删除测试文件,因为在本指南中我们不会使用它。
mv script/Counter.s.sol script/DeployPriceOracleReader.s.sol
mv src/Counter.sol src/PriceOracleReader.sol
rm test/Counter.t.sol
在根目录中创建一个 .env
文件来存储端点 URL。你可以使用 QuickNode Hyperliquid Mainnet RPC 端点,也可以使用公共端点。
TESTNET_RPC_URL=https://rpc.hyperliquid-testnet.xyz/evm
MAINNET_RPC_URL=https://rpc.hyperliquid.xyz/evm # 或你自己的 QuickNode Hyperliquid RPC 端点
然后,将变量加载到你的终端环境中:
source .env
更新 foundry.toml
文件以包括 Hyperliquid RPC URL:
foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
[rpc_endpoints]
hyperliquid_testnet = "${TESTNET_RPC_URL}"
hyperliquid_mainnet = "${MAINNET_RPC_URL}"
要获取代币的元数据,我们将向 Hyperliquid 的 API 端点发出 API 调用。该端点返回代币的名称、规模小数位(szDecimals)以及其他元数据,以及代币的索引。
curl --location 'https://api.hyperliquid.xyz/info' \
--header 'Content-Type: application/json' \
--data '{"type": "meta"}'
代币索引对于主网和测试网是不同的。因此,如果你使用的是测试网,则需要将 API 端点更新为
https://api.hyperliquid-testnet.xyz/info
。
检索到代币索引(例如,5)后,你需要正确格式化它以进行智能合约调用。这包括:
0x
例如,如果代币索引为 5,则转换将如下所示:
十进制:5
十六进制:0x0000000000000000000000000000000000000000000000000000000000000005
szDecimals
是价格中的有效位数。例如,对于 BTC,szDecimals
为 5。
现在,你已准备就绪!让我们了解如何通过直接查询 HyperCore 的预编译或构建一个简单的 Solidity 合约来获取 HyperCore 预言机价格,从而从 HyperEVM 与 HyperCore 交互。
Cast
进行直接查询此方法允许你快速与 HyperCore 的预编译进行交互,而无需部署合约。你可以使用 cast
命令直接从你的终端执行这些查询。
如 Hyperliquid 的架构 部分中所述,有不同类型的预编译可用于与 HyperCore 交互。在本示例中,我们将重点关注 ORACLE_PX_PRECOMPILE_ADDRESS
,它是 HyperCore 预言机价格预编译的地址,并采用一个参数——代币索引。
始终参考 Hyperliquid 的 官方开发者文档 以确保你使用的是正确的预编译地址。
ORACLE_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000807
以下命令模板可用于查询任何代币的价格:
cast call <precompile_address> <input_parameter_token_index> --rpc-url <rpc_url>
例如,要查询 BTC 的价格(测试网上的代币索引 3):
cast call 0x0000000000000000000000000000000000000807 0x0000000000000000000000000000000000000000000000000000000000000003 --rpc-url $TESTNET_RPC_URL
直接查询使用原始 32 字节输入。
输出将是以美元计价的 BTC 价格,最多 5 个有效数字和 6 - szDecimals
个小数位,其中 szDecimals
因代币而异。
你应该以十六进制形式看到该值,例如:
0x00000000000000000000000000000000000000000000000000000000000ea768
将输出转换为十进制数:
cast --to-dec 0x00000000000000000000000000000000000000000000000000000000000ea768
例如,此命令的输出为 960360,表示撰写本指南时 BTC 以美元计价的价格(96036.0)。
虽然直接查询对于快速测试很有用,但智能合约为 dApp 提供了一种链上解决方案。让我们构建我们的 PriceOracleReader
合约。
在本节中,我们将构建一个智能合约,该合约查询 HyperCore 的预言机价格并返回任何代币的价格。
首先,我们需要将 Hyperliquid 提供的 L1Read.sol
合约添加到我们的项目中。
注意:
L1Read.sol
合约不是官方 Foundry 软件包的一部分,因此我们需要手动添加它。注意 2:我们将
L1Read.sol
合约的所有外部函数修改为 public,以便我们可以从我们的智能合约中调用它们。
在 src
目录下创建一个名为 L1Read.sol
的新文件,并粘贴以下代码:
点击展开
src/L1Read.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract L1Read {
struct Position {
int64 szi;
uint64 entryNtl;
int64 isolatedRawUsd;
uint32 leverage;
bool isIsolated;
}
struct SpotBalance {
uint64 total;
uint64 hold;
uint64 entryNtl;
}
struct UserVaultEquity {
uint64 equity;
uint64 lockedUntilTimestamp;
}
struct Withdrawable {
uint64 withdrawable;
}
struct Delegation {
address validator;
uint64 amount;
uint64 lockedUntilTimestamp;
}
struct DelegatorSummary {
uint64 delegated;
uint64 delegated;
uint64 totalPendingWithdrawal;
uint64 nPendingWithdrawals;
}
struct PerpAssetInfo {
string coin;
uint32 marginTableId;
uint8 szDecimals;
uint8 maxLeverage;
bool onlyIsolated;
}
struct SpotInfo {
string name;
uint64[2] tokens;
}
struct TokenInfo {
string name;
uint64[] spots;
uint64 deployerTradingFeeShare;
address deployer;
address evmContract;
uint8 szDecimals;
uint8 weiDecimals;
int8 evmExtraWeiDecimals;
}
address constant POSITION_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800;
address constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801;
address constant VAULT_EQUITY_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000802;
address constant WITHDRAWABLE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000803;
address constant DELEGATIONS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000804;
address constant DELEGATOR_SUMMARY_PRECOMPILE_ADDRESS =
0x0000000000000000000000000000000000000805;
address constant MARK_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000806;
address constant ORACLE_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000807;
address constant SPOT_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000808;
address constant L1_BLOCK_NUMBER_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000809;
address constant PERP_ASSET_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080a;
address constant SPOT_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080b;
address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C;
function position(address user, uint16 perp) public view returns (Position memory) {
bool success;
bytes memory result;
(success, result) = POSITION_PRECOMPILE_ADDRESS.staticcall(abi.encode(user, perp));
require(success, "Position precompile call failed");
return abi.decode(result, (Position));
}
function spotBalance(address user, uint64 token) public view returns (SpotBalance memory) {
bool success;
bytes memory result;
(success, result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(user, token));
require(success, "SpotBalance precompile call failed");
return abi.decode(result, (SpotBalance));
}
function userVaultEquity(
address user,
address vault
) public view returns (UserVaultEquity memory) {
bool success;
bytes memory result;
(success, result) = VAULT_EQUITY_PRECOMPILE_ADDRESS.staticcall(abi.encode(user, vault));
require(success, "VaultEquity precompile call failed");
return abi.decode(result, (UserVaultEquity));
}
function withdrawable(address user) public view returns (Withdrawable memory) {
bool success;
bytes memory result;
(success, result) = WITHDRAWABLE_PRECOMPILE_ADDRESS.staticcall(abi.encode(user));
require(success, "Withdrawable precompile call failed");
return abi.decode(result, (Withdrawable));
}
function delegations(address user) public view returns (Delegation[] memory) {
bool success;
bytes memory result;
(success, result) = DELEGATIONS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user));
require(success, "Delegations precompile call failed");
return abi.decode(result, (Delegation[]));
}
function delegatorSummary(address user) public view returns (DelegatorSummary memory) {
bool success;
bytes memory result;
(success, result) = DELEGATOR_SUMMARY_PRECOMPILE_ADDRESS.staticcall(abi.encode(user));
require(success, "DelegatorySummary precompile call failed");
return abi.decode(result, (DelegatorSummary));
}
function markPx(uint32 index) public view returns (uint64) {
bool success;
bytes memory result;
(success, result) = MARK_PX_PRECOMPILE_ADDRESS.staticcall(abi.encode(index));
require(success, "MarkPx precompile call failed");
return abi.decode(result, (uint64));
}
function oraclePx(uint32 index) public view returns (uint64) {
bool success;
bytes memory result;
(success, result) = ORACLE_PX_PRECOMPILE_ADDRESS.staticcall(abi.encode(index));
require(success, "OraclePx precompile call failed");
return abi.decode(result, (uint64));
}
function spotPx(uint32 index) public view returns (uint64) {
bool success;
bytes memory result;
(success, result) = SPOT_PX_PRECOMPILE_ADDRESS.staticcall(abi.encode(index));
require(success, "SpotPx precompile call failed");
return abi.decode(result, (uint64));
}
function l1BlockNumber() public view returns (uint64) {
bool success;
bytes memory result;
(success, result) = L1_BLOCK_NUMBER_PRECOMPILE_ADDRESS.staticcall(abi.encode());
require(success, "L1BlockNumber precompile call failed");
return abi.decode(result, (uint64));
}
function perpAssetInfo(uint32 perp) public view returns (PerpAssetInfo memory) {
bool success;
bytes memory result;
(success, result) = PERP_ASSET_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(perp));
require(success, "PerpAssetInfo precompile call failed");
return abi.decode(result, (PerpAssetInfo));
}
function spotInfo(uint32 spot) public view returns (SpotInfo memory) {
bool success;
bytes memory result;
(success, result) = SPOT_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(spot));
require(success, "SpotInfo precompile call failed");
return abi.decode(result, (SpotInfo));
}
function tokenInfo(uint32 token) public view returns (TokenInfo memory) {
bool success;
bytes memory result;
(success, result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(token));
require(success, "TokenInfo precompile call failed");
return abi.decode(result, (TokenInfo));
}
}
该文件包含使用 Solidity 的 staticcall
与预定义地址处的预编译交互的函数。
打开 PriceOracleReader.sol
文件并粘贴以下代码:
点击展开
src/PriceOracleReader.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// 导入 L1Read 合约
import "./L1Read.sol";
contract PriceOracleReader is L1Read {
// 映射以存储每个永续资产索引的最新价格
mapping(uint32 => uint256) public latestPrices;
// 映射以存储资产名称
mapping(uint32 => string) public assetNames;
// 价格更新事件
event PriceUpdated(uint32 indexed perpIndex, uint256 price);
/**
* @dev 更新永续资产的价格
* @param perpIndex 永续资产的索引
* @return 具有 18 个小数位的转换后的价格
*/
function updatePrice(uint32 perpIndex) public returns (uint256) {
// 使用继承的函数获取原始预言机价格
uint64 rawPrice = oraclePx(perpIndex);
// 使用继承的函数获取资产信息
PerpAssetInfo memory assetInfo = perpAssetInfo(perpIndex);
uint8 szDecimals = assetInfo.szDecimals;
// 存储资产名称
assetNames[perpIndex] = assetInfo.coin;
// 转换价格:price / 10^(6 - szDecimals) * 10^18
// 将原始价格转换为具有 18 个小数位的可读价格
uint256 divisor = 10 ** (6 - szDecimals);
uint256 convertedPrice = (uint256(rawPrice) * 1e18) / divisor;
// 存储转换后的价格
latestPrices[perpIndex] = convertedPrice;
// 发出事件
emit PriceUpdated(perpIndex, convertedPrice);
return convertedPrice;
}
/**
* @dev 获取永续资产的最新价格
* @param perpIndex 永续资产的索引
* @return 具有 18 个小数位的最新转换后的价格
*/
function getLatestPrice(uint32 perpIndex) public view returns (uint256) {
return latestPrices[perpIndex];
}
}
该合约:
L1Read
合约updatePrice
函数,该函数获取最新价格并使用继承的 oraclePx
和 perpAssetInfo
函数将其转换为 18 个小数位getLatestPrice
函数,该函数返回给定永续资产索引的最新价格PriceUpdated
事件,以在价格更新时通知。价格转换
updatePrice
函数将原始价格转换为具有 18 个小数位的可读价格。转换公式为:
uint256 convertedPrice = (uint256(rawPrice) * 1e18) / divisor;
其中 rawPrice
是 oraclePx
函数以十六进制格式返回的原始价格,divisor
计算为 10 ** (6 - szDecimals)
,而 szDecimals
是价格中的有效位数。
现在我们有了 PriceOracleReader
合约,我们可以将其部署到测试网或主网。
打开 script/DeployPriceOracleReader.s.sol
文件并粘贴以下代码:
点击展开
script/DeployPriceOracleReader.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Script.sol";
import "../src/PriceOracleReader.sol";
contract DeployPriceOracleReader is Script {
function run() external {
// 开始广播以记录和发送交易
vm.startBroadcast();
// 部署 PriceOracleReader 合约
PriceOracleReader reader = new PriceOracleReader();
console.log("PriceOracleReader deployed at:", address(reader));
// 结束广播
vm.stopBroadcast();
}
}
此脚本部署 PriceOracleReader
合约并记录地址。
运行以下命令来编译和部署合约:
forge build
forge script script/DeployPriceOracleReader.s.sol:DeployPriceOracleReader --rpc-url $TESTNET_RPC_URL --account your-wallet-name --broadcast
注意:
--account
标志指定将用于部署合约的钱包。你可以使用之前导入的同一个钱包。如果不确定使用哪个钱包,请使用cast wallet list
列出你的钱包。
部署脚本将启动广播过程并部署合约。确认交易后,将部署合约并记录地址。
现在已经部署了合约,我们可以调用 updatePrice
函数来获取给定永续资产索引的最新价格。以下命令可用于查询测试网上索引为 3 的永续资产的价格:
cast call <your_contract_address> "updatePrice(uint32)(uint256)" 3 --rpc-url $TESTNET_RPC_URL
注意:
updatePrice
函数采用一个uint32
参数,它是永续资产的索引。由于索引已编码在oraclePx
函数本身中,因此我们无需将其作为编码数据传递。
此命令将以 wei(18 个小数位)返回索引为 3 的永续资产的最新价格,例如 96036000000000000000000
以表示 $96036。
恭喜!你现在已经学会了如何使用脚本和 Solidity 智能合约通过 HyperEVM 访问 HyperCore 的实时预言机价格。这种集成解锁了 DeFi 的可能性,如借贷平台、合成资产或套利机器人——所有这些都在 Hyperliquid 的高性能、EVM 兼容的生态系统中。
你可以使用以太坊库(如 ethers.js 或 Viem)与你的 HyperEVM 智能合约交互。查看我们的 ethers.js 指南 和 Viem 指南 以了解有关这些库的更多信息。
如果你遇到困难或有疑问,请在我们的 Discord 中提出。 通过在 Twitter (@QuickNode) 或我们的 Telegram 公告频道 上关注我们,及时了解最新信息。
如果你对新主题有任何反馈或要求,请 告诉我们。 我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/hyp...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!