Appearance
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 dataOnly 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:
| Aspect | API Mode (this manager) | WebSocket Mode |
|---|---|---|
| Transport | HTTP POST polling | WebSocket subscription |
| Timeout | 2s hard timeout | 10s connect timeout |
| Retry | None (alarm retries) | 5 retries with backoff |
| State | Stateless | Connection management |
| DO Eviction | No impact | Loses connection |
API mode is currently active due to its simplicity and resilience to DO eviction.
State & Storage
In-memory only — no SQLite tables.
| Property | Type | Description |
|---|---|---|
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, exposesgetClearinghouseState()as RPC passthrough - PriceStorageManager — subscribes to
allMidsevents during initialization; also callsgetAllMids()directly as on-demand fallback when no latest price data exists
Error Handling
| Scenario | Behavior |
|---|---|
| Network timeout (>2s) | AbortController aborts fetch, error thrown |
| DNS/network failure | Fetch rejects immediately, error propagates |
| Missing ticker in response | Not handled here — PriceStorageManager logs and skips |
| Listener throws | Unhandled — propagates up (all current listeners are safe) |
No retry logic. The 1-second alarm loop provides natural retry.
See Also
- PriceStorageManager — consumes and transforms price events