Skip to main content

Read HyperCore Oracle Prices in HyperEVM

Updated on
May 14, 2025

17 min read

Overview

Hyperliquid's dual architecture combines a high-performance trading engine (HyperCore) with EVM compatibility (HyperEVM), creating unique opportunities for DeFi developers. While possibilities are endless, one of the most exciting applications is the ability to access HyperCore's real-time price feeds directly from your HyperEVM dApps, without the need for a separate price oracle.

This unique architecture makes it possible to access real-time price data—like perpetual futures prices—directly within your smart contracts, without relying on external oracles. It’s a game-changer for DeFi builders who want native access to HyperCore’s financial primitives from an EVM context.

In this guide, you’ll build a HyperEVM smart contract that reads live prices from HyperCore. Along the way, you’ll explore how to use Hyperliquid’s L1Read.sol interface and precompile contracts to fetch and convert oracle prices, laying the foundation for more advanced dApps like trading bots, lending protocols, and on-chain analytics.

What You Will Learn


  • How to set up a Foundry project for HyperEVM
  • How to deploy a smart contract on HyperEVM to fetch HyperCore oracle prices by leveraging HyperEVM's precompiles
  • How to query HyperCore price data using direct scripts and smart contracts

What You Will Need


  • Foundry installed on your machine
  • Basic Solidity knowledge
  • HYPE tokens for gas (from testnet faucet or mainnet purchase)
  • A wallet for contract deployment (e.g., MetaMask, Rabby, or brand new wallet created in Foundry)
  • A free QuickNode account (optional, but recommended) to access dedicated Hyperliquid RPC endpoint (for mainnet)

Hyperliquid's Architecture: HyperCore and HyperEVM

Hyperliquid is a Layer 1 blockchain optimized for high-performance DeFi use cases. Its core engine, HyperCore, handles a fully onchain order book with sub-second finality. This performance is made possible by HyperBFT, Hyperliquid’s consensus mechanism, which ensures one-block finality and supports up to 200,000 orders per second. On the other hand, HyperEVM integrates Ethereum-compatible smart contracts into this ecosystem, letting developers build on top of HyperCore's order book data.

Hyperliquid's Architecture: HyperCore and HyperEVM

HyperCore exposes its functionality through precompiles - special contracts at predefined addresses that allow HyperEVM contracts to query data like oracle prices, asset metadata, and user positions. These can be accessed directly in Solidity using static calls, thanks to the L1Read.sol contract provided by Hyperliquid. To ensure you're using the correct precompile addresses, always refer to the latest list in Hyperliquid’s official developer documentation.

In this guide, we'll use following functions that are available in the L1Read.sol contract:

  • oraclePx(uint32 index): Returns the oracle price of a perpetual futures contract at a given index. Utilizes the ORACLE_PX_PRECOMPILE_ADDRESS precompile.
  • perpAssetInfo(uint32 index): Returns the metadata of a perpetual futures contract at a given index. Utilizes the PERP_ASSET_INFO_PRECOMPILE_ADDRESS precompile.
Reading and Writing to the HyperCore

Hyperliquid's architecture allows both reading and writing to the HyperCore within the HyperEVM. However, their write system contracts are not yet available on the HyperEVM mainnet. So, in this guide, we'll focus on reading from the HyperCore.

Prerequisites

Before we start, make sure you do the following:

Install Foundry

To get started, make sure you have Foundry, a toolkit for EVM smart contract development, installed on your machine. If not, install it by running the following command in your terminal:

curl -L https://foundry.paradigm.xyz | bash
foundryup

If you're new to Foundry, check out our Foundry tutorial to get started.

Get Your QuickNode Endpoint

To interact with the HyperEVM, you must connect to the Hyperliquid blockchain using an RPC endpoint. While you can use public RPCs, we recommend using a dedicated endpoint for production use. To get your endpoint, sign up for a free QuickNode account and create an endpoint for Hyperliquid (Mainnet).

Set Up Your Wallet

You can create a new wallet or use an existing one to deploy your smart contract through Foundry.

While importing your wallet, we suggest encrypting your private key for security, instead of using it directly. To learn more about why it's important, check out the guide on How to Secure Your Private Keys in Hardhat and Foundry guide.

Run the following command in your terminal and enter your wallet's private key and a password to encrypt it:

cast wallet import your-wallet-name --interactive

Replace your-wallet-name with the name of your wallet. You'll be prompted to enter your private key and set a password for encryption. The --interactive flag ensures the private key isn't saved in your shell history for security.

For a new wallet, you can use the following command to create a new wallet:

cast wallet new

Get HYPE Tokens

HYPE is the native token on Hyperliquid, and you'll need some HYPE tokens to pay as gas fees for our smart contract deployment.

For the testnet, you can get some test USDC from Hyperliquid's Testnet Faucet and use it to buy some HYPE. If you want to use the mainnet, you can purchase HYPE tokens on the Hyperliquid Mainnet. Whether you're using testnet or mainnet, you need to transfer HYPE from HyperCore perps to HyperCore spot and then to HyperEVM, all through their UI as shown below:

Transfer HYPE Tokens

Watch this video to learn how to get HYPE tokens on HyperEVM:

In this video, learn everything about Hyperliquid HyperEVM and how to get started with HyperEVM. We cover the differences between HyperCore and HyperEVM, and how to get/transfer HYPE tokens into the HyperEVM wallet.
Subscribe to our YouTube channel for more videos!

How to Interact with the HyperCore from HyperEVM

In this section, we will show you how to interact with the HyperCore Oracle Prices in your HyperEVM dApp by leveraging HyperEVM's precompiles. We will cover the following methods:

  • Using direct queries with Foundry's Cast
  • Building a simple Solidity contract to fetch HyperCore Oracle Prices

First, let's create a Foundry project to work with.

Set Up a Foundry Project

Step 1: Create a Foundry Project

forge init hyperevm-oracle-prices
cd hyperevm-oracle-prices

This command will create a new directory called hyperevm-oracle-prices and initialize a Foundry project inside it, with some initial files (i.e., src/Counter.sol) and configurations (i.e., foundry.toml).

├── README.md
├── foundry.toml // Foundry configuration file
├── lib
├── script
│   └── Counter.s.sol // Foundry deployment script
├── src
│   └── Counter.sol // Main contract file
└── test
└── Counter.t.sol // Test contract file

Step 2: Update the File Structure

Rename the deployment script and main contract files to match the project name, and remove the test file since we won't be using it in this guide.

mv script/Counter.s.sol script/DeployPriceOracleReader.s.sol
mv src/Counter.sol src/PriceOracleReader.sol
rm test/Counter.t.sol

Step 3: Configure Environment Variables

Create a .env file in the root directory to store endpoint URLs. You can use the QuickNode Hyperliquid Mainnet RPC endpoint, or use public endpoints.

TESTNET_RPC_URL=https://rpc.hyperliquid-testnet.xyz/evm
MAINNET_RPC_URL=https://rpc.hyperliquid.xyz/evm # or your own QuickNode Hyperliquid RPC endpoint

Then, load the variables into your terminal environment:

source .env

Step 4: Update Foundry Config File

Update the foundry.toml file to include the Hyperliquid RPC URLs:

foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

[rpc_endpoints]
hyperliquid_testnet = "${TESTNET_RPC_URL}"
hyperliquid_mainnet = "${MAINNET_RPC_URL}"

Get Token Metadata

To get the metadata of a token, we'll make an API call to the Hyperliquid's API endpoint. The endpoint returns the token's name, size decimals (szDecimals), and other metadata along with the token's index.

curl --location 'https://api.hyperliquid.xyz/info' \
--header 'Content-Type: application/json' \
--data '{"type": "meta"}'

Token indexes are different for the mainnet and testnet. So, you need to update the API endpoint to https://api.hyperliquid-testnet.xyz/info if you're using the testnet.

Once you retrieve the token index (e.g., 5), you’ll need to format it correctly for smart contract calls. This involves:

  • Converting the index to hexadecimal
  • Prefixing it with 0x
  • Padding it to 32 bytes (64 hex characters) with leading zeros

For example, if the token index is 5, the conversion would look like this:

Decimal: 5
Hex: 0x0000000000000000000000000000000000000000000000000000000000000005

szDecimals is the number of significant digits in the price. For example, for BTC, the szDecimals is 5.

Now, you are ready to go! Let's understand how to interact with the HyperCore from HyperEVM by directly querying HyperCore's precompiles or building a simple Solidity contract to fetch HyperCore Oracle Prices.

Method 1: Direct Queries with Foundry's Cast

This method allows you to quickly interact with HyperCore's precompiles without the need for a deployed contract. You can use the cast command to perform these queries directly from your terminal.

Step 1: Identify the Precompile Address

As explained in the Hyperliquid's Architecture section, there are different types of precompiles available for interacting with HyperCore. In this example, we'll focus on the ORACLE_PX_PRECOMPILE_ADDRESS, which is the address of the HyperCore Oracle Prices precompile and takes one parameter - the token index.

Always refer to the Hyperliquid’s official developer documentation to ensure you're using the correct precompile addresses.

ORACLE_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000807

Step 2: Query the Price with Cast

The following command template can be used to query the price of any token:

cast call <precompile_address> <input_parameter_token_index> --rpc-url <rpc_url>

For example, to query the price of BTC (token index 3 on the testnet):

cast call 0x0000000000000000000000000000000000000807 0x0000000000000000000000000000000000000000000000000000000000000003 --rpc-url $TESTNET_RPC_URL

Direct queries use raw 32-byte inputs.

The output will be the price of BTC in USD, with up to 5 significant digits and 6 - szDecimals decimals, where szDecimals varies depending on the token.

You should see the value as hexadecimal, for example:

0x00000000000000000000000000000000000000000000000000000000000ea768

Convert the output to a decimal number:

cast --to-dec 0x00000000000000000000000000000000000000000000000000000000000ea768

For example, the output of this command is 960360, which represents the price of BTC in USD (96036.0) at the time of writing this guide.

While direct queries are useful for quick testing, a smart contract provides an onchain solution for dApps. Let's build our PriceOracleReader contract.

Method 2: Query the Price with a Smart Contract

In this section, we'll build a smart contract that queries HyperCore's oracle prices and returns the price of any token.

Step 1: Import the L1Read Contract

First, we need to add the L1Read.sol contract provided by Hyperliquid to our project.

Note: The L1Read.sol contract is not part of the official Foundry package, so we need to add it manually.

Note2: We modified the L1Read.sol contract's all external functions to public, so that we can call them from our smart contract.

Create a new file called L1Read.sol under the src directory and paste the following code:

Click to expand
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));
}
}

This file includes functions that uses Solidity's staticcall to interact with precompiles at their predefined addresses.

Step 2: Create a PriceOracleReader Contract

Open the PriceOracleReader.sol file and paste the following code:

Click to expand
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];
}
}

This contract;

  • inherits from the local L1Read contract we just added
  • includes an updatePrice function that fetches the latest price and converts it to 18 decimals using the inherited oraclePx and perpAssetInfo functions
  • includes a getLatestPrice function that returns the latest price for a given perp asset index
  • includes a PriceUpdated event to notify when the price is updated.
Price Conversion

The updatePrice function converts the raw price to a human-readable price with 18 decimals. The conversion formula is:

uint256 convertedPrice = (uint256(rawPrice) * 1e18) / divisor;

where rawPrice is the raw price returned by the oraclePx function in hexadecimal format, divisor is calculated as 10 ** (6 - szDecimals), and szDecimals is the number of significant digits in the price.

Step 3: Deploy the PriceOracleReader Contract

Now that we have the PriceOracleReader contract, we can deploy it to the testnet or mainnet.

Step 4: Create a Deployment Script

Open the script/DeployPriceOracleReader.s.sol file and paste the following code:

Click to expand
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();
}
}

This script deploys the PriceOracleReader contract and logs the address.

Step 5: Run the Deployment Script

Run the following command to compile and deploy the contract:

forge build
forge script script/DeployPriceOracleReader.s.sol:DeployPriceOracleReader --rpc-url $TESTNET_RPC_URL --account your-wallet-name --broadcast

Note: The --account flag specifies the wallet that will be used to deploy the contract. You can use the same wallet you imported before. Use cast wallet list to list your wallets if you're not sure which one to use.

The deployment script will start the broadcast process and deploy the contract. Once the transaction is confirmed, the contract will be deployed and the address will be logged.

Step 6: Interact with Your Contract on HyperEVM

Now that the contract is deployed, we can call the updatePrice function to fetch the latest price for a given perp asset index. The following command can be used to query the price of the perp asset with index 3 on the testnet:

cast call <your_contract_address> "updatePrice(uint32)(uint256)" 3 --rpc-url $TESTNET_RPC_URL

Note: The updatePrice function takes a uint32 parameter, which is the index of the perp asset. Since the index is encoded within the oraclePx function itself, we don't need to pass it as encoded data.

This command will return the latest price of the perp asset with index 3 in wei (18 decimals), such as 96036000000000000000000 to represent $96036.

Conclusion

Congratulations! You’ve now learned how to access real-time oracle prices from HyperCore using both scripts and Solidity smart contracts via HyperEVM. This integration unlocks DeFi possibilities like lending platforms, synthetic assets, or arbitrage bots—all within a high-performance, EVM-compatible ecosystem of Hyperliquid.

You can interact with your HyperEVM smart contract using Ethereum libraries like ethers.js or Viem. Check out our ethers.js guides and Viem guides to learn more about these libraries.

Next Steps


  • Explore other read precompiles: HyperCore exposes a wide range of precompiles that can be used to query data from the HyperCore order book. Explore the precompiles and their capabilities to see how they can be used in your HyperEVM dApps.
  • Explore Hyperliquid’s write precompiles: Hyperliquid's write precompiles allow you to place trades onchain, enabling advanced trading strategies and advanced order management.
  • Expand your HyperEVM dApp: By combining HyperCore with HyperEVM, you can create a dApp that leverages HyperCore's onchain order book data to provide a seamless user experience, such as trading bots, lending platforms, or synthetic assets.

Further Resources


If you are stuck or have questions, drop them in our Discord. Stay up to date with the latest by following us on Twitter (@QuickNode) or our Telegram announcement channel.

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide