UNPKG

@kinetiq-research/markets-assets

Version:

Asset metadata (descriptions, oracle references) for Kinetiq markets

116 lines (106 loc) • 4.41 kB
import type { AllPerpMetasResponse } from '@nktkas/hyperliquid' import type { PerpsAsset } from './asset' import { HIP3_ASSET_INDEX_BASE, HIP3_DEX_INDEX_MULTIPLIER, } from './asset-indices' import { getDexNameFromMeta } from './get-dex-name-from-meta' import { parsePerpsHyperliquidSymbol } from './parse-perps-hyperliquid-symbol' import { toDisplaySymbol } from './to-display-symbol' import { toTradeUrlSymbol } from './to-trade-url-symbol' // Collateral token index -> quote asset symbol. Indices come from the // Hyperliquid spot meta and are stable, so they are hardcoded here to avoid // threading spot meta through perps normalization. // // This is a DISPLAY LABEL, not a key. It disagrees with the token names // Hyperliquid publishes (index 268 is named `USDT0` in the spot meta), and an // index absent from this map is labelled `USDC` by the fallback below. Anything // that has to identify the settlement token — matching a balance, a fee, or a // collateral figure — keys on `collateralTokenIndex` instead. const COLLATERAL_TOKEN_SYMBOLS: Record<number, PerpsAsset['quoteAssetSymbol']> = { 0: 'USDC', 235: 'USDE', 268: 'USDT', 360: 'USDH', } const getQuoteAssetSymbol = ( collateralToken: number, ): PerpsAsset['quoteAssetSymbol'] => COLLATERAL_TOKEN_SYMBOLS[collateralToken] ?? 'USDC' type NormalizedPerpsMetaUniverseItem = AllPerpMetasResponse[number]['universe'][number] & { asset: PerpsAsset } type NormalizedPerpsMetaDex = Omit<AllPerpMetasResponse[number], 'universe'> & { universe: Array<NormalizedPerpsMetaUniverseItem> } export type NormalizedPerpsMeta = Array<NormalizedPerpsMetaDex> export const normalizePerpsMeta = ({ perpsAnnotations, perpsMeta, }: { perpsAnnotations?: Record<string, string> perpsMeta: AllPerpMetasResponse }): NormalizedPerpsMeta => perpsMeta.map((dexMeta, dexIndex) => { const dex = getDexNameFromMeta({ meta: dexMeta }) const quoteAssetSymbol = getQuoteAssetSymbol(dexMeta.collateralToken) const universe = dexMeta.universe.map((universeItem, positionInDex) => { const { symbol } = parsePerpsHyperliquidSymbol({ hyperliquidSymbol: universeItem.name, }) // Curated override → live annotation → the raw cleaned name. `symbol` // stays the raw cleaned name. The precedence lives in `toDisplaySymbol` // because Pulse's `buildTickerUniverse` resolves the same question from // the same payloads, and when the two disagreed the trade app called one // listing `QQQ` while Pulse briefs called it `USTECH`. const preferredSymbol = toDisplaySymbol({ // `Object.hasOwn`, not a bare index: `perpsAnnotations` is a plain // object literal typed `Record<string, string>`, so a listing named // `constructor` would read a FUNCTION off the prototype chain, and the // typechecker types the read as `string`. Same guard, same reason as // `toOverride` in `to-display-symbol.ts`. annotationDisplayName: perpsAnnotations !== undefined && Object.hasOwn(perpsAnnotations, universeItem.name) ? perpsAnnotations[universeItem.name] : undefined, hyperliquidSymbol: universeItem.name, symbol, }) const display = preferredSymbol // `dex` is passed because a listing whose ticker is live on a second dex // we surface keeps the prefix in its url — see `PREFIXED_URL_SYMBOLS`. const tradeUrlSymbol = toTradeUrlSymbol({ dex, symbol: preferredSymbol }) // Main HL (dex index 0) uses the position directly; HIP-3 dexes use the // computed offset formula. const assetIndex = dexIndex === 0 ? positionInDex : HIP3_ASSET_INDEX_BASE + dexIndex * HIP3_DEX_INDEX_MULTIPLIER + positionInDex return { ...universeItem, asset: { assetIndex, collateralTokenIndex: dexMeta.collateralToken, dex, display, hyperliquidSymbol: universeItem.name, maxLeverage: Number(universeItem.maxLeverage), onlyIsolated: !!universeItem.onlyIsolated, quoteAssetSymbol, symbol, szDecimals: universeItem.szDecimals, tradeUrlSymbol, type: 'perps' as const, }, } }) return { ...dexMeta, universe, } })