indexing/SKILL.md
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.
npx skillsauth add cloudzombie/liquidskills indexingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
┌─────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────┘
All reads go to https://api.hyperliquid.xyz/info (mainnet) or https://api.hyperliquid-testnet.xyz/info (testnet).
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;
}
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')}")
For live data, use wss://api.hyperliquid.xyz/ws.
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);
For data from your own Solidity contracts, use standard EVM event indexing.
// 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
);
}
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);
}
},
});
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);
}
}
Once you're writing events to Supabase, expose them via Realtime:
// 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();
| 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 |
/info API. Don't use EVM RPC for this.eth_getLogs + event indexer.eth_getLogs. Query in blocks of 1000-5000. Larger ranges timeout.development
Why build on Hyperliquid. HyperBFT consensus, native orderbook, speed, AI agent angle, honest tradeoffs. Use when someone asks "should I build on Hyperliquid?", "why not Ethereum?", or when an agent needs to understand what makes Hyperliquid unique.
development
Wallets on Hyperliquid — MetaMask + chain ID 999 setup, HyperCore API wallets, agent wallet patterns, EIP-712 signing for exchange actions. Essential for any agent that needs to interact with Hyperliquid.
tools
Development tools for Hyperliquid — Foundry, Hardhat, viem, wagmi for HyperEVM; Python SDK and TypeScript SDK for HyperCore API. What works, what to use, how to set up.
testing
Smart contract testing for HyperEVM with Foundry/Hardhat — unit tests, fuzz testing, testnet fork testing. What to test, what not to test, and what LLMs get wrong on Hyperliquid.