SPB Git

Repository Public

hl-assets.ts 3.7 KB 52 lines typescript

1// Hyperliquid asset table PINNED for live trading, per network. `describe()` turns the integer of an action (`a`, `asset`) into an2// instrument through this table, so it is a trust input: the isolated signer uses THIS table, shipped in its bundle, and never3// one handed over by the API.4//5// Trade-off. A pinned table cannot be falsified by a compromised API ("index 7 is BTC"), and the signer needs no call to the6// venue. But it is only true while the venue keeps these indices and lot sizes. Hyperliquid appends new assets at the end of7// `meta.universe` and has not renumbered existing ones, but nothing in its documentation promises it. Therefore:8//   - the API compares the venue's live `meta` with this table before every proposal and refuses to trade on any difference9//     (`hlPinnedMismatch`); the table is then updated here, reviewed, and both bundles redeployed;10//   - the signer independently requires each price of an order to be near its own exchange price for the NAMED asset, so an11//     order priced for another asset does not pass as "BTC".12// Only the instruments listed here can be allowed in a venue authorization. Values read from `meta` on 2026-11-11 (fixtures13// packages/derivatives/test/fixtures/hyperliquid/{mainnet,testnet}-meta.json; test/live.test.ts checks them against the fixtures).14import type { HlNetwork } from '../venues/hyperliquid/signing.ts';15import type { HlAsset } from '../venues/hyperliquid/trading.ts';1617export const HL_PINNED_AT = '2026-11-11';18export const HL_PINNED_ASSETS: Record<HlNetwork, readonly HlAsset[]> = {19  mainnet: [20    { symbol: 'BTC', asset: 0, szDecimals: 5, maxLeverage: 40, isolatedOnly: false, delisted: false },21    { symbol: 'ETH', asset: 1, szDecimals: 4, maxLeverage: 25, isolatedOnly: false, delisted: false },22    { symbol: 'SOL', asset: 5, szDecimals: 2, maxLeverage: 20, isolatedOnly: false, delisted: false },23  ],24  testnet: [25    { symbol: 'BTC', asset: 3, szDecimals: 5, maxLeverage: 40, isolatedOnly: false, delisted: false },26    { symbol: 'ETH', asset: 4, szDecimals: 4, maxLeverage: 25, isolatedOnly: false, delisted: false },27    { symbol: 'SOL', asset: 0, szDecimals: 2, maxLeverage: 10, isolatedOnly: false, delisted: false },28  ],29};3031export const hlPinnedAssets = (network: HlNetwork): HlAsset[] => HL_PINNED_ASSETS[network].map((a) => ({ ...a }));32export const hlPinnedAsset = (network: HlNetwork, instrument: string): HlAsset | null =>33  HL_PINNED_ASSETS[network].find((a) => `hyperliquid:${a.symbol}` === instrument) ?? null;3435/**36 * Compares the venue's live asset table with the pinned one. Returns null when every pinned asset still sits at its index with37 * its lot size and is still listed; otherwise the first difference, in words. A LOWER venue maximum leverage is not a38 * difference that can mislead the signer (the venue refuses what exceeds it), so it is not one here.39 */40export function hlPinnedMismatch(network: HlNetwork, live: readonly HlAsset[]): string | null {41  for (const p of HL_PINNED_ASSETS[network]) {42    const at = live.find((a) => a.asset === p.asset), named = live.find((a) => a.symbol === p.symbol);43    if (!named) return `${p.symbol} is no longer in the venue's ${network} asset table`;44    if (named.asset !== p.asset) return `${p.symbol} moved from index ${p.asset} to ${named.asset} on ${network}`;45    if (!at || at.symbol !== p.symbol) return `index ${p.asset} on ${network} is now ${at?.symbol ?? 'empty'}, not ${p.symbol}`;46    if (named.szDecimals !== p.szDecimals) return `${p.symbol}: lot decimals changed from ${p.szDecimals} to ${named.szDecimals} on ${network}`;47    if (named.delisted) return `${p.symbol} is delisted on ${network}`;48    if (named.isolatedOnly !== p.isolatedOnly) return `${p.symbol}: margin mode rules changed on ${network}`;49  }50  return null;51}52
Files main
Menu

Theme