UNPKG

@nktkas/hyperliquid

Version:

Unofficial Hyperliquid API SDK for all major JS runtimes, written in TypeScript and provided with tests

151 lines (150 loc) 6.6 kB
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "../base.js"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpTransport = exports.HttpRequestError = void 0; const base_js_1 = require("../base.js"); /** * Error thrown when an HTTP response is deemed invalid: * - Non-200 status code * - Unexpected content type */ class HttpRequestError extends base_js_1.TransportError { response; responseBody; /** * Creates a new HTTP request error. * @param response - The failed HTTP response. * @param responseBody - The raw response body content, if available. */ constructor(response, responseBody) { let message = `HTTP request failed: status ${response.status}`; if (responseBody) message += `, body "${responseBody}"`; super(message); this.response = response; this.responseBody = responseBody; this.name = "HttpRequestError"; } } exports.HttpRequestError = HttpRequestError; /** HTTP implementation of the REST transport interface. */ class HttpTransport { isTestnet; timeout; server; fetchOptions; onRequest; onResponse; /** * Creates a new HTTP transport instance. * @param options - Configuration options for the HTTP transport layer. */ constructor(options) { this.isTestnet = options?.isTestnet ?? false; this.timeout = options?.timeout === undefined ? 10_000 : options.timeout; this.server = { mainnet: { api: options?.server?.mainnet?.api ?? "https://api.hyperliquid.xyz", rpc: options?.server?.mainnet?.rpc ?? "https://rpc.hyperliquid.xyz", }, testnet: { api: options?.server?.testnet?.api ?? "https://api.hyperliquid-testnet.xyz", rpc: options?.server?.testnet?.rpc ?? "https://rpc.hyperliquid-testnet.xyz", }, }; this.fetchOptions = options?.fetchOptions ?? {}; this.onRequest = options?.onRequest; this.onResponse = options?.onResponse; } /** * Sends a request to the Hyperliquid API via fetch. * @param endpoint - The API endpoint to send the request to. * @param payload - The payload to send with the request. * @param signal - An optional abort signal. * @returns A promise that resolves with parsed JSON response body. * @throws {HttpRequestError} - Thrown when an HTTP response is deemed invalid. * @throws May throw {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#exceptions | fetch errors}. */ async request(endpoint, payload, signal) { // Construct a Request const url = new URL(endpoint, this.server[this.isTestnet ? "testnet" : "mainnet"][endpoint === "explorer" ? "rpc" : "api"]); const init = mergeRequestInit({ body: JSON.stringify(payload), headers: { "Accept-Encoding": "gzip, deflate, br, zstd", "Content-Type": "application/json", }, keepalive: true, method: "POST", signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, }, this.fetchOptions, { signal }); let request = new Request(url, init); // Call the onRequest callback, if provided if (this.onRequest) { const customRequest = await this.onRequest(request); if (customRequest instanceof Request) request = customRequest; } // Send the Request and wait for a Response let response = await fetch(request); // Call the onResponse callback, if provided if (this.onResponse) { const customResponse = await this.onResponse(response); if (customResponse instanceof Response) response = customResponse; } // Validate the Response if (!response.ok || !response.headers.get("Content-Type")?.includes("application/json")) { // Unload the response body to prevent memory leaks const body = await response.text().catch(() => undefined); throw new HttpRequestError(response, body); } // Parse the response body const body = await response.json(); // Check if the response is an error if (body?.type === "error") { throw new HttpRequestError(response, body?.message); } // Return the response body return body; } } exports.HttpTransport = HttpTransport; /** Merges multiple {@linkcode HeadersInit} into one {@linkcode Headers}. */ function mergeHeadersInit(...inits) { if (inits.length === 0 || inits.length === 1) { return new Headers(inits[0]); } const merged = new Headers(); for (const headers of inits) { const iterator = Symbol.iterator in headers ? headers : Object.entries(headers); for (const [key, value] of iterator) { merged.set(key, value); } } return merged; } /** Merges multiple {@linkcode RequestInit} into one {@linkcode RequestInit}. */ function mergeRequestInit(...inits) { const merged = inits.reduce((acc, init) => ({ ...acc, ...init }), {}); const headersList = inits.map((init) => init.headers) .filter((headers) => typeof headers === "object"); if (headersList.length > 0) { merged.headers = mergeHeadersInit(...headersList); } const signals = inits.map((init) => init.signal) .filter((signal) => signal instanceof AbortSignal); if (signals.length > 0) { merged.signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0]; } return merged; } });