/
ikratkiy
/
Hyperliquid
Обзор
Документация
Войти
/
ikratkiy
/
Hyperliquid
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/shared/hyperliquid/api.ts
249 строк
8 KB
ikratkiy
feat(spot): add fail-closed reconciliation contracts
15 июл 2026, 02:22
15 июл 2026, 02:22
eb972fb
Код
Авторство
О чём код?
import { canSubmitBtcToUsdcExchangeAction, canSubmitExchangeAction, getEnvironmentConfig } from "./config"; import type { AllMids, EnvironmentMode, ExchangeEnvelope, InfoRequest, L2Book, HyperliquidCloid, OrderStatusResponse, PerpMeta, SpotMeta, SpotClearinghouseState, SpotMetaAndAssetCtxs, Trade, UserAbstraction, UserFees, UserFill, UserOpenOrder, } from "./types"; export type EndpointAttempt = { endpoint: string; ok: boolean; message: string; }; export type FallbackResponse<T> = { data: T; endpoint: string; attempts: EndpointAttempt[]; }; function joinEndpoint(baseUrl: string, path: string) { return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`; } export async function postJson<T>(url: string, body: unknown): Promise<T> { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await response.text(); const data = text ? JSON.parse(text) : null; if (!response.ok) { throw new Error(`HTTP ${response.status}: ${text || response.statusText}`); } return data as T; } export async function postJsonWithFallback<T>(baseUrls: string[], path: string, body: unknown): Promise<FallbackResponse<T>> { const attempts: EndpointAttempt[] = []; for (const endpoint of baseUrls) { try { const response = await fetch(joinEndpoint(endpoint, path), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await response.text(); const data = text ? JSON.parse(text) : null; if (!response.ok) { attempts.push({ endpoint, ok: false, message: `HTTP ${response.status}: ${text || response.statusText}` }); continue; } attempts.push({ endpoint, ok: true, message: "ok" }); return { data: data as T, endpoint, attempts }; } catch (error) { attempts.push({ endpoint, ok: false, message: error instanceof Error ? error.message : "Unknown endpoint failure" }); } } const last = attempts.at(-1); throw new Error(last ? last.message : "No Hyperliquid endpoint candidates configured."); } export async function postJsonWithoutFallback<T>(baseUrl: string, path: string, body: unknown): Promise<FallbackResponse<T>> { try { const data = await postJson<T>(joinEndpoint(baseUrl, path), body); return { data, endpoint: baseUrl, attempts: [{ endpoint: baseUrl, ok: true, message: "ok" }], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown endpoint failure"; throw new Error(`${message}. Write request was not retried because its acceptance status is unknown.`); } } export function postInfoWithMeta<T>(mode: EnvironmentMode, body: InfoRequest) { return postJsonWithFallback<T>(getEnvironmentConfig(mode).apiUrls, "/info", body); } export async function postInfo<T>(mode: EnvironmentMode, body: InfoRequest) { return (await postInfoWithMeta<T>(mode, body)).data; } export function postExchangeWithMeta(mode: EnvironmentMode, envelope: ExchangeEnvelope) { if (!canSubmitExchangeAction(mode)) { throw new Error("Signed /exchange actions are disabled for this environment and legacy flow."); } const [endpoint] = getEnvironmentConfig(mode).apiUrls; if (!endpoint) { throw new Error("No Hyperliquid exchange endpoint configured."); } return postJsonWithoutFallback<unknown>(endpoint, "/exchange", envelope); } export async function postExchange(mode: EnvironmentMode, envelope: ExchangeEnvelope) { return (await postExchangeWithMeta(mode, envelope)).data; } export function postBtcToUsdcExchangeWithMeta(mode: EnvironmentMode, envelope: ExchangeEnvelope) { if (!canSubmitBtcToUsdcExchangeAction(mode)) { throw new Error("The BTC to USDC exchange client is restricted to the documented Mainnet scenario."); } const [endpoint] = getEnvironmentConfig(mode).apiUrls; if (!endpoint) { throw new Error("No Hyperliquid exchange endpoint configured."); } return postJsonWithoutFallback<unknown>(endpoint, "/exchange", envelope); } export async function postBtcToUsdcExchange(mode: EnvironmentMode, envelope: ExchangeEnvelope) { return (await postBtcToUsdcExchangeWithMeta(mode, envelope)).data; } export function getAllMids(mode: EnvironmentMode) { return postInfo<AllMids>(mode, { type: "allMids" }); } export function getAllMidsWithMeta(mode: EnvironmentMode) { return postInfoWithMeta<AllMids>(mode, { type: "allMids" }); } export function getPerpMeta(mode: EnvironmentMode) { return postInfo<PerpMeta>(mode, { type: "meta" }); } export function getSpotMeta(mode: EnvironmentMode) { return postInfo<SpotMeta>(mode, { type: "spotMeta" }); } export function getSpotMetaAndAssetCtxs(mode: EnvironmentMode) { return postInfo<SpotMetaAndAssetCtxs>(mode, { type: "spotMetaAndAssetCtxs" }); } export function getSpotMetaAndAssetCtxsWithMeta(mode: EnvironmentMode) { return postInfoWithMeta<SpotMetaAndAssetCtxs>(mode, { type: "spotMetaAndAssetCtxs" }); } export function getL2Book(mode: EnvironmentMode, coin: string) { return postInfo<L2Book>(mode, { type: "l2Book", coin }); } export function getL2BookWithMeta(mode: EnvironmentMode, coin: string) { return postInfoWithMeta<L2Book>(mode, { type: "l2Book", coin }); } export function getRecentTrades(mode: EnvironmentMode, coin: string) { return postInfo<Trade[]>(mode, { type: "recentTrades", coin }); } export function getRecentTradesWithMeta(mode: EnvironmentMode, coin: string) { return postInfoWithMeta<Trade[]>(mode, { type: "recentTrades", coin }); } export function getOpenOrders(mode: EnvironmentMode, user: string) { return postInfo<UserOpenOrder[]>(mode, { type: "openOrders", user: user.toLowerCase() }); } export function getFrontendOpenOrders(mode: EnvironmentMode, user: string) { return postInfo<UserOpenOrder[]>(mode, { type: "frontendOpenOrders", user: user.toLowerCase() }); } export function getUserFills(mode: EnvironmentMode, user: string) { return postInfo<UserFill[]>(mode, { type: "userFills", user: user.toLowerCase(), aggregateByTime: true }); } export function getUserFillsByTime(mode: EnvironmentMode, user: string, startTime: number, endTime?: number) { if (!Number.isSafeInteger(startTime) || startTime < 0) { throw new Error("userFillsByTime startTime must be a non-negative safe integer."); } if (endTime !== undefined && (!Number.isSafeInteger(endTime) || endTime < startTime)) { throw new Error("userFillsByTime endTime must be a safe integer greater than or equal to startTime."); } return postInfo<UserFill[]>(mode, { type: "userFillsByTime", user: user.toLowerCase(), startTime, ...(endTime === undefined ? {} : { endTime }), aggregateByTime: false, }); } export function getOrderStatus(mode: EnvironmentMode, user: string, oid: number | HyperliquidCloid) { if (typeof oid === "number" && (!Number.isSafeInteger(oid) || oid < 0)) { throw new Error("orderStatus numeric oid must be a non-negative safe integer."); } if (typeof oid === "string" && !/^0x[0-9a-fA-F]{32}$/.test(oid)) { throw new Error("orderStatus string oid must be a 16-byte cloid."); } return postInfo<OrderStatusResponse>(mode, { type: "orderStatus", user: user.toLowerCase(), oid }); } export function getUserFees(mode: EnvironmentMode, user: string) { return postInfo<UserFees>(mode, { type: "userFees", user: user.toLowerCase() }); } export function getSpotClearinghouseState(mode: EnvironmentMode, user: string) { return postInfo<SpotClearinghouseState>(mode, { type: "spotClearinghouseState", user: user.toLowerCase() }); } export function getSpotClearinghouseStateWithMeta(mode: EnvironmentMode, user: string) { return postInfoWithMeta<SpotClearinghouseState>(mode, { type: "spotClearinghouseState", user: user.toLowerCase() }); } export function getUserAbstraction(mode: EnvironmentMode, user: string) { return postInfo<UserAbstraction>(mode, { type: "userAbstraction", user: user.toLowerCase() }); } export function getUserAbstractionWithMeta(mode: EnvironmentMode, user: string) { return postInfoWithMeta<UserAbstraction>(mode, { type: "userAbstraction", user: user.toLowerCase() }); } export function getUserRateLimit(mode: EnvironmentMode, user: string) { return postInfo<unknown>(mode, { type: "userRateLimit", user: user.toLowerCase() }); } export function getUserRateLimitWithMeta(mode: EnvironmentMode, user: string) { return postInfoWithMeta<unknown>(mode, { type: "userRateLimit", user: user.toLowerCase() }); }