SPB Git

Repository Public

helpers.ts 3.1 KB 44 lines typescript

1// Shared test helpers: fixtures, a replaying fetch, instruments and assets taken from the recorded `meta`.2import { readFileSync } from 'node:fs';3import { sign } from 'viem/accounts';4import type { Hex } from 'viem';5import type { Instrument } from '../../src/index.ts';6import { hlInstrument, hlMeta, signatureFromHex, typedDataDigest, type HlAsset, type HlSignature, type HlTypedData } from '../../src/venues/hyperliquid/index.ts';78export interface Fixture { recordedAt: string; network: 'mainnet' | 'testnet'; request: unknown; status: number; body: any }9export const fx = (name: string): Fixture => JSON.parse(readFileSync(new URL(`../fixtures/hyperliquid/${name}.json`, import.meta.url), 'utf8'));10export const raw = (name: string): any => JSON.parse(readFileSync(new URL(`../fixtures/hyperliquid/${name}`, import.meta.url), 'utf8'));1112/** The public test key of the official SDK's tests/signing_test.py — a published test vector, not a wallet. */13export const SDK_TEST_KEY: Hex = '0x0123456789012345678901234567890123456789012345678901234567890123';1415/** Signs typed data the way the isolated signer will: EIP-712 digest, secp256k1, { r, s, v }. Test-only. */16export async function signWith(privateKey: Hex, td: HlTypedData): Promise<HlSignature> {17  return signatureFromHex(await sign({ hash: typedDataDigest(td), privateKey, to: 'hex' }));18}19/** SDK vectors print r and s without leading zeros (`to_hex(int)`): compare as integers */20export const sameSig = (a: { r: string; s: string; v: number }, b: { r: string; s: string; v: number }): boolean => BigInt(a.r) === BigInt(b.r) && BigInt(a.s) === BigInt(b.s) && a.v === b.v;2122export type Route = { status?: number; body: unknown } | ((body: any) => { status?: number; body: unknown } | Promise<{ status?: number; body: unknown }>);23/** fetch that answers POST /info by request `type` (and POST /exchange under the key 'exchange'), recording every call */24export function replay(routes: Record<string, Route>) {25  const calls: { url: string; body: any }[] = [];26  const fetchImpl = (async (url: string, init: RequestInit) => {27    const body = JSON.parse(String(init.body));28    calls.push({ url: String(url), body });29    const key = String(url).endsWith('/exchange') ? 'exchange' : body.type;30    const route = routes[key];31    if (!route) throw new Error(`no fixture for ${key}`);32    const r = typeof route === 'function' ? await route(body) : route;33    return new Response(typeof r.body === 'string' ? r.body : JSON.stringify(r.body), { status: r.status ?? 200, headers: { 'content-type': 'application/json' } });34  }) as unknown as typeof fetch;35  return { fetchImpl, calls };36}3738export function universe(network: 'mainnet' | 'testnet' = 'mainnet'): { assets: HlAsset[]; instruments: Map<string, Instrument> } {39  const meta = hlMeta.parse(fx(`${network}-meta`).body);40  const instruments = new Map(meta.universe.map((u) => [u.name, hlInstrument(u)]));41  const assets = meta.universe.map((u, i) => ({ symbol: u.name, asset: i, szDecimals: u.szDecimals, maxLeverage: u.maxLeverage, isolatedOnly: instruments.get(u.name)!.marginModes.length === 1, delisted: u.isDelisted === true }));42  return { assets, instruments };43}44
Files main
Menu

Theme