How to read Hyperliquid data — HyperCore via /info API, HyperEVM events, WebSocket subscriptions. Why you cannot just loop through blocks, and what to use instead.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add dvcrn/openclaw-skills-marketplace --skill indexing --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Indexing?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/dvcrn-indexing)More formats (shields.io, HTML) on the badges page.
---
name: indexing
description: How to read Hyperliquid data — HyperCore via /info API, HyperEVM events, WebSocket subscriptions. Why you cannot just loop through blocks, and what to use instead.
---
# Hyperliquid Data & Indexing
## What You Probably Got Wrong
**You try to loop through HyperEVM blocks for data.** Scanning blocks is slow and rate-limited. For HyperCore data (order fills, positions, funding), use the `/info` REST API — it's instant and comprehensive.
**You don't know the data lives in two places.** HyperCore (perps, spot, order books) has its own API at `api.hyperliquid.xyz`. HyperEVM has standard EVM events. They're different systems. Know which one has your data.
**You try to use `eth_getLogs` for HyperCore activity.** HyperCore trades, fills, and positions don't emit EVM events — they're protocol state. Query them via `POST /info`.
**You treat WebSocket subscriptions as optional.** For live trading data (order book, recent trades, fills), use WebSocket at `wss://api.hyperliquid.xyz/ws`. Polling the REST API for live data is slow and rude.
**You store index results onchain.** Leaderboards, activity feeds, analytics — these belong offchain. Emit events, index offchain. If you need onchain commitment, store a hash.
---
## Two Data Layers
```
┌─────────────────────────────────────────────────────────┐
│ HyperCore Data │
│ ├── Perp positions, open orders, fills │
│ ├── Spot balances and trades │
│ ├── Funding rates, mark prices, oracle prices │
│ ├── Account meta (leverage, margin type) │
│ └── Query via: POST https://api.hyperliquid.xyz/info │
├─────────────────────────────────────────────────────────┤
│ HyperEVM Data │
│ ├── ERC-20 balances and transfers │
│ ├── Custom contract events (your contracts) │
│ ├── Smart contract state │
│ └── Query via: JSON-RPC / eth_getLogs / event indexers │
└─────────────────────────────────────────────────────────┘
```
---
## Reading HyperCore Data
### POST /info — The Primary Read API
All reads go to `https://api.hyperliquid.xyz/info` (mainnet) or `https://api.hyperliquid-testnet.xyz/info` (testnet).
```javascript
import axios from 'axios';
const API = 'https://api.hyperliquid.xyz/info';
// Get all perpetual market data (no auth required)
async function getMeta() {
const { data } = await axios.post(API, { type: 'meta' });
return data;
// Returns: { universe: [{ name, szDecimals, maxLeverage, ... }] }
}
// Get user's perpetual positions and account state
async function getClearinghouseState(address) {
const { data } = await axios.post(API, {
type: 'clearinghouseState',
user: address
});
return data;
// Returns positions, margin summary, withdrawable, etc.
}
// Get user's open orders
async function getOpenOrders(address) {
const { data } = await axios.post(API, {
type: 'openOrders',
user: address
});
return data;
}
// Get user's fill history
async function getUserFills(address) {
const { data } = await axios.post(API, {
type: 'userFills',
user: address
});
return data;
// Returns: array of { coin, px, sz, side, time, fee, ... }
}
// Get L2 order book snapshot for a market
async function getL2Book(coin) {
const { data } = await axios.post(API, {
type: 'l2Book',
coin
});
return data;
// Returns: { coin, levels: [[bids], [asks]] }
}
// Get all mid prices
async function getAllMids() {
const { data } = await axios.post(API, { type: 'allMids' });
return data;
// Returns: { BTC: "95000.0", ETH: "3500.0", ... }
}
// Get spot balances for a user
async function getSpotBalances(address) {
const { data } = await axios.post(API, {
type: 'spotClearinghouseState',
user: address
});
return data;
}
// Get funding rate history
async function getFundingHistory(coin, startTime) {
const { data } = await axios.post(API, {
type: 'fundingHistory',
coin,
startTime
});
return data;
}
```
### Python with the Official SDK
```python
from hyperliquid.info import Info
from hyperliquid.utils import constants
# Mainnet
info = Info(constants.MAINNET_API_URL)
# Get all positions for a user
user_address = "0xYourAddress"
state = info.user_state(user_address)
print(state['assetPositions']) # List of open positions
# Get order book
l2_data = info.l2_snapshot("BTC")
print(l2_data['levels']) # [bids, asks]
# Get recent fills
fills = info.user_fills(user_address)
for fill in fills[:10]:
print(f"{fill['coin']} {fill['side']} {fill['sz']} @ {fill['px']}")
# Get all mid prices
mids = info.all_mids()
print(f"BTC mid: {mids.get('BTC')}")
```
---
## WebSocket Subscriptions
For live data, use `wss://api.hyperliquid.xyz/ws`.
```javascript
import WebSocket from 'ws';
const ws = new WebSocket('wss://api.hyperliquid.xyz/ws');
ws.on('open', () => {
// Subscribe to order book updates for BTC
ws.send(JSON.stringify({
method: 'subscribe',
subscription: { type: 'l2Book', coin: 'BTC' }
}));
// Subscribe to all mid prices (heartbeat every second)
ws.send(JSON.stringify({
method: 'subscribe',
subscription: { type: 'allMids' }
}));
// Subscribe to fills for a specific user
ws.send(JSON.stringify({
method: 'subscribe',
subscription: {
type: 'userFills',
user: '0xYourAddress'
}
}));
// Subscribe to user events (orders, positions)
ws.send(JSON.stringify({
method: 'subscribe',
subscription: {
type: 'userEvents',
user: '0xYourAddress'
}
}));
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.channel === 'l2Book') {
const { coin, levels } = msg.data;
const [bids, asks] = levels;
console.log(`${coin} best bid: ${bids[0]?.px}, best ask: ${asks[0]?.px}`);
}
if (msg.channel === 'userFills') {
for (const fill of msg.data.fills) {
console.log(`Fill: ${fill.coin} ${fill.side} ${fill.sz} @ ${fill.px}`);
}
}
if (msg.channel === 'userEvents') {
// Order updates, position changes
console.log('User event:', msg.data);
}
});
// Heartbeat to keep connection alive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ method: 'ping' }));
}
}, 30000);
```
---
## Reading HyperEVM Events (Your Contracts)
For data from your own Solidity contracts, use standard EVM event indexing.
### Design Events First
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract TokenLaunch {
event TokenLaunched(
address indexed token,
address indexed creator,
string name,
string symbol,
uint256 timestamp
);
event Trade(
address indexed token,
address indexed trader,
bool isBuy,
uint256 hypeAmount, // HYPE in (buy) or out (sell)
uint256 tokenAmount, // tokens out (buy) or in (sell)
uint256 price, // current price after trade
uint256 timestamp
);
event Graduated(
address indexed token,
uint256 totalHypeRaised,
address lpAddress,
uint256 timestamp
);
}
```
### Querying Events with viem
```javascript
import { createPublicClient, http, parseAbiItem } from 'viem';
import { defineChain } from 'viem/chains';
const hyperEVM = defineChain({
id: 999,
name: 'HyperEVM',
nativeCurrency: { name: 'HYPE', symbol: 'HYPE', decimals: 18 },
rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },
});
const client = createPublicClient({
chain: hyperEVM,
transport: http(),
});
const CONTRACT = '0xYourContractAddress';
// Get all Trade events
const trades = await client.getLogs({
address: CONTRACT,
event: parseAbiItem('event Trade(address indexed token, address indexed trader, bool isBuy, uint256 hypeAmount, uint256 tokenAmount, uint256 price, uint256 timestamp)'),
fromBlock: 0n,
toBlock: 'latest',
});
// Get trades for a specific token
const tokenTrades = await client.getLogs({
address: CONTRACT,
event: parseAbiItem('event Trade(address indexed token, address indexed trader, bool isBuy, uint256 hypeAmount, uint256 tokenAmount, uint256 price, uint256 timestamp)'),
args: { token: '0xSpecificToken' },
fromBlock: 0n,
toBlock: 'latest',
});
// Watch for new events in real-time
const unwatch = client.watchEvent({
address: CONTRACT,
event: parseAbiItem('event Trade(address indexed token, address indexed trader, bool isBuy, uint256 hypeAmount, uint256 tokenAmount, uint256 price, uint256 timestamp)'),
onLogs: (logs) => {
for (const log of logs) {
console.log('New trade:', log.args);
}
},
});
```
### Building a Simple Indexer with Supabase
```javascript
import { createPublicClient, http } from 'viem';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY);
const client = createPublicClient({ chain: hyperEVM, transport: http() });
async function indexTrades(fromBlock, toBlock) {
const logs = await client.getLogs({
address: CONTRACT,
events: [TRADE_ABI],
fromBlock,
toBlock,
});
const rows = logs.map(log => ({
tx_hash: log.transactionHash,
block_number: Number(log.blockNumber),
token: log.args.token,
trader: log.args.trader,
is_buy: log.args.isBuy,
hype_amount: log.args.hypeAmount.toString(),
token_amount: log.args.tokenAmount.toString(),
price: log.args.price.toString(),
timestamp: Number(log.args.timestamp),
}));
if (rows.length > 0) {
await supabase.from('trades').upsert(rows, { onConflict: 'tx_hash' });
}
return logs.length;
}
// Run indexer in chunks
async function runIndexer() {
let fromBlock = await getLastIndexedBlock();
const latestBlock = await client.getBlockNumber();
for (let block = fromBlock; block <= latestBlock; block += 1000n) {
const toBlock = block + 999n < latestBlock ? block + 999n : latestBlock;
const count = await indexTrades(block, toBlock);
console.log(`Indexed blocks ${block}-${toBlock}: ${count} events`);
await saveLastIndexedBlock(toBlock);
}
}
```
---
## Supabase Realtime for Live Feeds
Once you're writing events to Supabase, expose them via Realtime:
```javascript
// Subscribe to new trades in real-time
const channel = supabase
.channel('trades')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'trades',
}, (payload) => {
console.log('New trade:', payload.new);
updateUI(payload.new);
})
.subscribe();
```
---
## Key /info Endpoints Reference
| Type | Required Params | Returns |
|------|----------------|---------|
| `meta` | none | All perp markets and their properties |
| `spotMeta` | none | All spot assets |
| `allMids` | none | Current mid prices for all assets |
| `clearinghouseState` | `user` | Positions, margin, withdrawable |
| `spotClearinghouseState` | `user` | Spot token balances |
| `openOrders` | `user` | User's open orders |
| `userFills` | `user` | User's fill history |
| `orderStatus` | `user`, `oid` | Single order status |
| `l2Book` | `coin` | Order book snapshot |
| `candleSnapshot` | `coin`, `interval`, `startTime`, `endTime` | OHLCV candles |
| `fundingHistory` | `coin`, `startTime` | Funding rate history |
| `userFundingHistory` | `user`, `startTime` | User's funding payments |
---
## Rules
1. **HyperCore data → `/info` API.** Don't use EVM RPC for this.
2. **Live data → WebSocket.** Don't poll REST for live feeds.
3. **Your contract data → `eth_getLogs` + event indexer.**
4. **Store index offchain.** Supabase, PostgreSQL, or any database. Never onchain.
5. **Emit events for everything.** If your contract changes state, emit an event. No exceptions.
6. **Handle reconnects.** WebSocket connections drop. Reconnect with exponential backoff and re-subscribe.
7. **Chunk `eth_getLogs`.** Query in blocks of 1000-5000. Larger ranges timeout.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!