本文介绍了如何在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 用例优化的Layer1区块链。它的核心引擎 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(主网)创建一个终端节点。
你可以创建一个新钱包或使用现有钱包通过 Foundry 部署你的智能合约。
导入钱包时,我们建议加密你的私钥以提高安全性,而不是直接使用它。要了解为什么这很重要,请查看有关 如何在 Hardhat 和 Foundry 中保护你的私钥 的指南。
在你的终端中运行以下命令,然后输入你的钱包私钥和密码以对其进行加密:
cast wallet import your-wallet-name --interactive
将 your-wallet-name
替换为你的钱包名称。系统将提示你输入你的私钥并设置加密密码。--interactive
标志可确保私钥不会保存在你的 shell 历史记录中,以确保安全。
对于新钱包,你可以使用以下命令创建一个新钱包:
cast wallet new
HYPE 是 Hyperliquid 上的原生代币,你需要一些 HYPE 代币来支付智能合约部署的 gas 费用。
对于测试网,你可以从 Hyperliquid 的 测试网 Faucet 获取一些测试 USDC,并使用它购买一些 HYPE。如果要使用主网,你可以在 Hyperliquid 主网 上购买 HYPE 代币。无论你使用的是测试网还是主网,你都需要通过它们的 UI 将 HYPE 从 HyperCore 永续合约转移到 HyperCore 现货,然后再转移到 HyperEVM,如下所示:
观看此视频,了解如何在 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 主网 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,则转换将如下所示:
Decimal: 5
Hex: 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 undelegated;
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;
// Import the L1Read contract
import "./L1Read.sol";
contract PriceOracleReader is L1Read {
// Mapping to store the latest price for each perp asset index
mapping(uint32 => uint256) public latestPrices;
// Mapping to store asset names
mapping(uint32 => string) public assetNames;
// Event for price updates
event PriceUpdated(uint32 indexed perpIndex, uint256 price);
/**
* @dev Update the price for a perp asset
* @param perpIndex The index of the perp asset
* @return The converted price with 18 decimals
*/
function updatePrice(uint32 perpIndex) public returns (uint256) {
// Get the raw oracle price using the inherited function
uint64 rawPrice = oraclePx(perpIndex);
// Get the asset info using the inherited function
PerpAssetInfo memory assetInfo = perpAssetInfo(perpIndex);
uint8 szDecimals = assetInfo.szDecimals;
// Store the asset name
assetNames[perpIndex] = assetInfo.coin;
// Convert the price: price / 10^(6 - szDecimals) * 10^18
// This converts from raw price to human-readable price with 18 decimals
uint256 divisor = 10 ** (6 - szDecimals);
uint256 convertedPrice = (uint256(rawPrice) * 1e18) / divisor;
// Store the converted price
latestPrices[perpIndex] = convertedPrice;
// Emit event
emit PriceUpdated(perpIndex, convertedPrice);
return convertedPrice;
}
/**
* @dev Get the latest price for a perp asset
* @param perpIndex The index of the perp asset
* @return The latest converted price with 18 decimals
*/
function getLatestPrice(uint32 perpIndex) public view returns (uint256) {
return latestPrices[perpIndex];
}
}
这个合约:
L1Read
合约updatePrice
函数,该函数获取最新价格并使用继承的 oraclePx
和 perpAssetInfo
函数将其转换为 18 位小数getLatestPrice
函数,该函数返回给定 perp 资产索引的最新价格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 {
// Start broadcast to record and send transactions
vm.startBroadcast();
// Deploy the PriceOracleReader contract
PriceOracleReader reader = new PriceOracleReader();
console.log("PriceOracleReader deployed at:", address(reader));
// End broadcast
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
函数来获取给定 perp 资产索引的最新价格。以下命令可用于查询测试网上索引为 3 的 perp 资产的价格:
cast call <your_contract_address> "updatePrice(uint32)(uint256)" 3 --rpc-url $TESTNET_RPC_URL
注意:
updatePrice
函数采用一个uint32
参数,即 perp 资产的索引。由于索引已编码在oraclePx
函数本身中,因此我们不需要将其作为编码数据传递。
此命令将返回索引为 3 的 perp 资产的最新价格,以 wei 为单位(18 位小数),例如 96036000000000000000000
以表示 $96036。
恭喜!你现在已经学会了如何使用脚本和 Solidity 智能合约通过 HyperEVM 访问来自 HyperCore 的实时预言机价格。这种集成解锁了 DeFi 的可能性,例如借贷平台、合成资产或套利机器人——所有这些都在 Hyperliquid 的高性能 EVM 兼容生态系统中。
你可以使用以太坊库(例如 ethers.js 或 Viem)与你的 HyperEVM 智能合约交互。查看我们的 ethers.js 指南 和 Viem 指南 以了解有关这些库的更多信息。
如果你遇到困难或有疑问,请在我们的 Discord 中提出。通过在 [Twitter](https://
- 原文链接: quicknode.com/guides/oth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!