Skip to content

HyperliquidApiManager

Stateless HTTP client for the Hyperliquid REST API. Fetches mid prices every second and broadcasts them to subscribers via an event emitter pattern.

High-Level Design

Data Flow

   PriceCollector Alarm (every 1s)


  ┌───────────────────┐
  │ HyperliquidApi    │
  │ Manager           │
  │                   │
  │  POST /info       │──────▶ https://api.hyperliquid.xyz/info
  │  { type:allMids } │◀────── { "BTC":"50123.45", "ETH":"3021.67", ... }
  │                   │
  │  emit("allMids")  │
  └────────┬──────────┘

  listener callback


  PriceStorageManager.handleAllMids()

Core Concepts

1. Event Emitter Decoupling

The manager does not store or transform price data. It fetches raw mid prices and emits them to registered listeners. This decouples data fetching from data processing, allowing PriceStorageManager to subscribe to price updates regardless of the underlying transport (HTTP polling vs WebSocket).

PriceStorageManager.initialize()
  └─ hyperliquidApiManager.on("allMids", this.handleAllMids.bind(this))

PriceCollector.alarm()
  └─ hyperliquidApiManager.getAllMids()
       └─ fetch → parse → emit to listeners → return raw data

Only allMids listeners are actively notified. The clearinghouseState and userFees listener emissions exist in code but are commented out — those endpoints are query-only, not event-driven.

2. Strict 2-Second Timeout

getAllMids() enforces a hard 2-second timeout via AbortController. This is critical because:

  • The alarm loop runs every 1 second — a slow fetch must not block the next cycle
  • Fast failure lets the next alarm retry automatically (no retry logic in the manager itself)
  • The caller (PriceCollector.alarm()) catches errors and continues, so a timeout simply means one missed data point

getClearinghouseState() and getUserFees() have no timeout — they are infrequent on-demand queries where latency is acceptable.

3. Raw Passthrough — No Transformation

The Hyperliquid API returns mid prices as string values ("50123.45"). This manager returns them as-is. All data transformation (string → BigNumber → scaled integer) happens downstream in PriceStorageManager.transformAllMidsToTokenPrices().

4. API Mode vs WebSocket Mode

PriceCollector supports two fetch modes, toggled via a static flag:

AspectAPI Mode (this manager)WebSocket Mode
TransportHTTP POST pollingWebSocket subscription
Timeout2s hard timeout10s connect timeout
RetryNone (alarm retries)5 retries with backoff
StateStatelessConnection management
DO EvictionNo impactLoses connection

API mode is currently active due to its simplicity and resilience to DO eviction.

State & Storage

In-memory only — no SQLite tables.

PropertyTypeDescription
listeners{ allMids?, clearinghouseState?, userFees? }Event subscriber arrays

Interactions

Depends On

  • Hyperliquid REST API (https://api.hyperliquid.xyz/info) — sole data source

Depended By

  • PriceCollector — calls getAllMids() in alarm loop, exposes getClearinghouseState() as RPC passthrough
  • PriceStorageManager — subscribes to allMids events during initialization; also calls getAllMids() directly as on-demand fallback when no latest price data exists

Error Handling

ScenarioBehavior
Network timeout (>2s)AbortController aborts fetch, error thrown
DNS/network failureFetch rejects immediately, error propagates
Missing ticker in responseNot handled here — PriceStorageManager logs and skips
Listener throwsUnhandled — propagates up (all current listeners are safe)

No retry logic. The 1-second alarm loop provides natural retry.

See Also