# Indexing

> 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.

- Skill: `dvcrn/indexing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dvcrn/indexing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dvcrn/indexing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: dvcrn (https://skillmd.com/u/dvcrn)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/dvcrn/indexing

---


# 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.

