Summary
spot_meta_and_asset_ctxs() returns [meta, assetCtxs], and the docstring lays the two
arrays out adjacently. They are not parallel: today meta["universe"] has 320 entries
and assetCtxs has 711. The perp sibling meta_and_asset_ctxs() is parallel (232 vs
232), so carrying the perp intuition across gives you silently wrong prices.
This is a docs request, not a code defect — the SDK returns the raw payload, which is
correct. But the SDK's own __init__ already encodes the difference, so the knowledge
exists in the codebase and just isn't in the docstring:
# perps: the asset id IS the list position
for asset, asset_info in enumerate(meta["universe"]):
self.coin_to_asset[asset_info["name"]] = asset + offset
# spot: the asset id comes from a FIELD, not from the position
for spot_info in spot_meta["universe"]:
asset = spot_info["index"] + 10000
Reproduce (no keys, ~6 lines)
import json, urllib.request
def info(b):
r = urllib.request.Request("https://api.hyperliquid.xyz/info",
data=json.dumps(b).encode(), headers={"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(r).read())
meta, ctxs = info({"type": "spotMetaAndAssetCtxs"})
uni = meta["universe"]
print(len(uni), len(ctxs)) # 320 711
print(sum(1 for i, e in enumerate(uni) if ctxs[i]["coin"] == e["name"])) # 71 <- by position
print(sum(1 for e in uni if ctxs[e["index"]]["coin"] == e["name"])) # 320 <- by index field
Measured on mainnet 2026-08-02.
Why it's worth a docstring line: positions 0-70 line up
That is what makes it a trap rather than an inconvenience. Anything you eyeball while
testing is in the aligned region. The first divergence is at position 71:
universe[71] = @72 i.e. NOCEX/USDC zip -> markPx 1.0 correct -> 0.73944
universe[72] = RANK/USDC (index 73) zip -> markPx 0.73944 correct -> 0.059251
universe[73] = OMNIX/USDC (index 74) zip -> markPx 0.059251 correct -> 0.0000075
Each pair receives its neighbour's price. Note NOCEX getting 1.0 — a number that looks
like a stablecoin and invites exactly the wrong conclusion; and OMNIX wrong by four
orders of magnitude while still returning a valid float. No exception is raised in any
of these cases.
How it cost me: I concluded a stable/stable pair was trading at a mid of 0.1293 and spent
time reasoning about a dislocation that did not exist. The pair was real; the price
belonged to a different token.
Suggested change
Two lines in the spot_meta_and_asset_ctxs docstring, e.g. after the Returns: block:
Note: universe and the asset-contexts list are not parallel arrays and generally
have different lengths (320 vs 711 at time of writing). Join on coin, or index the
contexts list by the universe entry's own index field — assetCtxs[entry["index"]].
This differs from meta_and_asset_ctxs (perps), where list position is the asset id.
Happy to open a PR with exactly that if it's welcome.
Also possible: return a mapping
A spot_ctx_by_coin() helper returning {coin: ctx} would make the correct join the
default one. I'd lean toward the docstring first, since changing the return shape of an
existing method is the more invasive option and the raw passthrough is defensible.
Context, so you can weigh how much to trust the above: I trade a very small book
programmatically and write up venue behaviour that cost me something to learn. Full
write-up with four other findings (Bybit positionIdx, HL's $10 minimum being charged at
the limit price, CCTP bridge costs): https://github.com/massimiliano1991/venue-metrology
If this is already documented somewhere I didn't look, or considered obvious, saying so
is a perfectly useful answer and I'll take it as one.
Edited 2026-08-02: the name field you will actually see for universe[71] is the literal string @72; NOCEX/USDC is derived from meta["tokens"] via the pair's token indices. Clarified so the repro output matches this text exactly.
Summary
spot_meta_and_asset_ctxs()returns[meta, assetCtxs], and the docstring lays the twoarrays out adjacently. They are not parallel: today
meta["universe"]has 320 entriesand
assetCtxshas 711. The perp siblingmeta_and_asset_ctxs()is parallel (232 vs232), so carrying the perp intuition across gives you silently wrong prices.
This is a docs request, not a code defect — the SDK returns the raw payload, which is
correct. But the SDK's own
__init__already encodes the difference, so the knowledgeexists in the codebase and just isn't in the docstring:
Reproduce (no keys, ~6 lines)
Measured on mainnet 2026-08-02.
Why it's worth a docstring line: positions 0-70 line up
That is what makes it a trap rather than an inconvenience. Anything you eyeball while
testing is in the aligned region. The first divergence is at position 71:
Each pair receives its neighbour's price. Note NOCEX getting
1.0— a number that lookslike a stablecoin and invites exactly the wrong conclusion; and OMNIX wrong by four
orders of magnitude while still returning a valid float. No exception is raised in any
of these cases.
How it cost me: I concluded a stable/stable pair was trading at a mid of 0.1293 and spent
time reasoning about a dislocation that did not exist. The pair was real; the price
belonged to a different token.
Suggested change
Two lines in the
spot_meta_and_asset_ctxsdocstring, e.g. after theReturns:block:Happy to open a PR with exactly that if it's welcome.
Also possible: return a mapping
A
spot_ctx_by_coin()helper returning{coin: ctx}would make the correct join thedefault one. I'd lean toward the docstring first, since changing the return shape of an
existing method is the more invasive option and the raw passthrough is defensible.
Context, so you can weigh how much to trust the above: I trade a very small book
programmatically and write up venue behaviour that cost me something to learn. Full
write-up with four other findings (Bybit
positionIdx, HL's $10 minimum being charged atthe limit price, CCTP bridge costs): https://github.com/massimiliano1991/venue-metrology
If this is already documented somewhere I didn't look, or considered obvious, saying so
is a perfectly useful answer and I'll take it as one.
Edited 2026-08-02: the
namefield you will actually see foruniverse[71]is the literal string@72;NOCEX/USDCis derived frommeta["tokens"]via the pair's token indices. Clarified so the repro output matches this text exactly.