TypeScript toolkit for Hyperliquid HIP-4 prediction markets.
HIP-4 "outcome markets" (live since May 2026) are binary YES/NO markets on Hyperliquid — Fed decisions, the World Cup, BTC price strikes, the SpaceX IPO. They're powerful and almost entirely undocumented at the integration level. This kit is the missing plumbing, extracted from a production trading bot (Cabbge) and tested on mainnet with real money.
Zero dependencies. Works in Node 18+, Bun, Deno, and browsers.
npm install hip4-kitimport { fetchMarkets, fetchTopOfBook, priceWire, encodeAssetId } from 'hip4-kit';
// All live prediction markets with current odds
const markets = await fetchMarkets();
for (const m of markets.slice(0, 5)) {
console.log(`${m.name} — ${m.yes.name} ${(m.yes.price * 100).toFixed(0)}¢`);
}
// Top of book for one side
const { bestBid, bestAsk } = await fetchTopOfBook('#2640');
// The asset id you place orders against
const assetId = encodeAssetId(264, 0); // outcome 264, YES side → 100002640
// The price string you MUST send on the wire (see "the signature bug" below)
const px = priceWire(0.593); // "0.593" — never "0.5930"Hyperliquid re-serializes your action server-side — floats with no trailing zeros — before recovering the signer from your signature. Submit a price string like "0.5930" and the server hashes "0.593", recovers a different address, and rejects with:
User or API Wallet 0xABC... does not exist.
…where the address is garbage that changes on every attempt. Nothing in that error says "price formatting." Buys at 0.646 work by pure luck (no trailing zero); sells at 0.5930 explode. This cost us a day of mainnet debugging.
Fix: always run prices through priceWire() — a Number() round-trip that produces the canonical form.
Order books and open orders call a market #2640. Spot balances call the same instrument +2640. If you only match #, filled positions silently vanish from your portfolio view. parseCoin() accepts both; parseHoldings() normalizes to #.
The $10 minimum order value is enforced against the market mid price. An "$11" buy of 18 contracts with a 0.61 limit still bounces if mid is 0.55 (18 × 0.55 = $9.90). Use meetsMinNotional(contracts, mid, limit).
assetId = 100_000_000 + 10 * outcomeIndex + sideIndex // side 0 = YES book, 1 = NO book
encodeAssetId / decodeAssetId round-trip this for you.
An IOC sell with limit L fills against every bid ≥ L at the bids' prices. On thin books (most HIP-4 books are one market-maker deep), a 35-contract market sell can fill 1 contract and cancel the rest. Check fetchTopOfBook first and handle partial fills.
outcomeMeta includes "Fallback" slots with no real market behind them, all priced at exactly 0.5. Any "most contested" sort picks them first. Filter with isPlaceholder().
| Export | What it does |
|---|---|
fetchMarkets(apiUrl?) |
All live HIP-4 markets with YES/NO names + mid prices |
fetchTopOfBook(symbol, apiUrl?) |
Best bid/ask for one side's book |
parseHoldings(balances) |
HIP-4 positions out of spotClearinghouseState |
priceWire(px) |
Canonical wire price string (the signature-bug fix) |
encodeAssetId(outcome, side) / decodeAssetId(id) |
Order asset-id math |
parseCoin(coin) / isHip4Coin(coin) |
#N / +N symbol parsing |
meetsMinNotional(contracts, mid, limit) |
The $10-at-mid rule |
isPlaceholder(market) |
Filter Fallback/placeholder outcomes |
Order placement (signing, nonces, builder codes) is deliberately out of scope — use the official SDKs or your own signer; this kit makes sure what you send them is correct.
Extracted from Cabbge — a Telegram bot that routes all of Hyperliquid (perps, HIP-3 stocks, HIP-4 predictions) from chat. Every gotcha above was discovered the expensive way, with real positions, in production.
Found a new HIP-4 gotcha? PRs welcome — this should be the place integrators stop tripping on the same wires.
MIT