@nktkas/hyperliquid
Version:
Hyperliquid API SDK for all major JS runtimes, written in TypeScript.
189 lines (188 loc) • 7.92 kB
JavaScript
/**
* WebSocket transport for {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions | subscriptions}
* and {@link https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/post-requests | POST requests}.
*
* Use {@link WebSocketTransport} for real-time subscriptions and for lower-latency API requests.
*
* @example
* ```ts
* import { SubscriptionClient, WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* const client = new SubscriptionClient({ transport });
*
* const subscription = await client.allMids((data) => {
* console.log(data.mids);
* });
*
* await subscription.unsubscribe();
* ```
*
* @module
*/ import { ReconnectingWebSocket } from "@nktkas/rews";
import { WebSocketDispatcher, WebSocketRequestError } from "./_dispatcher.js";
import { HyperliquidEventTarget } from "./_events.js";
import { WebSocketKeepAlive } from "./_keepAlive.js";
import { WebSocketSubscriptionManager } from "./_subscriptionManager.js";
export { WebSocketRequestError };
/** Mainnet API WebSocket URL. */ export const MAINNET_API_WS_URL = "wss://api.hyperliquid.xyz/ws";
/** Testnet API WebSocket URL. */ export const TESTNET_API_WS_URL = "wss://api.hyperliquid-testnet.xyz/ws";
/** Mainnet RPC WebSocket URL. */ export const MAINNET_RPC_WS_URL = "wss://rpc.hyperliquid.xyz/ws";
/** Testnet RPC WebSocket URL. */ export const TESTNET_RPC_WS_URL = "wss://rpc.hyperliquid-testnet.xyz/ws";
/**
* WebSocket transport for the Hyperliquid API.
*
* @example
* ```ts
* import { WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* const mids = await transport.request("info", { type: "allMids" });
* transport.close();
* ```
*
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/post-requests
*/ export class WebSocketTransport {
/** Indicates this transport uses testnet endpoint. */ isTestnet;
/** The WebSocket that is used for communication. */ socket;
/** Enable automatic re-subscription to Hyperliquid subscription after reconnection. */ get resubscribe() {
return this._subscriptionManager.resubscribe;
}
set resubscribe(value) {
this._subscriptionManager.resubscribe = value;
}
/** Timeout for requests in ms. Set to `null` to disable. */ get timeout() {
return this._dispatcher.timeout;
}
set timeout(value) {
this._dispatcher.timeout = value;
}
_hlEvents;
_dispatcher;
_keepAlive;
_subscriptionManager;
/** Creates the transport and immediately starts connecting. */ constructor(options){
this.isTestnet = options?.isTestnet ?? false;
this.socket = new ReconnectingWebSocket(options?.url ?? (this.isTestnet ? TESTNET_API_WS_URL : MAINNET_API_WS_URL), options?.reconnect);
this._hlEvents = new HyperliquidEventTarget(this.socket);
this._dispatcher = new WebSocketDispatcher(this.socket, this._hlEvents, options?.timeout === undefined ? 10_000 : options.timeout);
this._keepAlive = new WebSocketKeepAlive(this.socket, this._hlEvents, options?.keepAlive);
this._subscriptionManager = new WebSocketSubscriptionManager(this.socket, this._dispatcher, this._hlEvents, options?.resubscribe ?? true);
}
/**
* Sends a request to the Hyperliquid API via WebSocket.
*
* The `explorer` endpoint is HTTP-only and not supported by this transport.
*
* @param endpoint The API endpoint to send the request to.
* @param payload The payload to send with the request.
* @param signal {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | AbortSignal} to cancel the request.
* @return A promise that resolves with the parsed response payload.
*
* @throws {WebSocketRequestError} An error that occurs when a WebSocket request fails.
*
* @example
* ```ts
* import { WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* const mids = await transport.request("info", { type: "allMids" });
* ```
*/ async request(endpoint, payload, signal) {
const wrapped = {
type: endpoint === "exchange" ? "action" : endpoint,
payload
};
return await this._dispatcher.request("post", wrapped, signal);
}
/**
* Subscribes to a Hyperliquid event channel.
*
* @param channel The event channel to listen to.
* @param payload The payload to send with the subscription request.
* @param listener The function to call when the event is dispatched.
* @param options Subscription options; see {@linkcode WebSocketSubscriptionManager.subscribe}.
* @return A promise that resolves with a subscription handle once the server confirms the subscription.
*
* @throws {WebSocketRequestError} An error that occurs when the subscription request fails.
*
* @example
* ```ts
* import { WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* const subscription = await transport.subscribe("allMids", { type: "allMids" }, (event) => {
* console.log(event.detail);
* });
*
* await subscription.unsubscribe();
* ```
*/ subscribe(channel, payload, listener, options) {
return this._subscriptionManager.subscribe(channel, payload, listener, options);
}
/**
* Waits until the WebSocket connection is ready.
*
* @param signal Stops waiting for the connection; the connection itself keeps establishing.
* @return A promise that resolves once the connection is open.
*
* @throws {WebSocketRequestError} When the connection is permanently terminated, or when `signal` aborts the waiting.
*
* @example
* ```ts
* import { WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* await transport.ready(AbortSignal.timeout(5_000));
* ```
*/ ready(signal) {
return new Promise((resolve, reject)=>{
const failTerminated = ()=>reject(new WebSocketRequestError("Failed to establish WebSocket connection", {
cause: this.socket.terminationSignal.reason
}));
const failAborted = ()=>reject(new WebSocketRequestError("Waiting for the connection was aborted", {
cause: signal?.reason
}));
if (signal?.aborted) return failAborted();
if (this.socket.terminationSignal.aborted) return failTerminated();
if (this.socket.readyState === ReconnectingWebSocket.OPEN) return resolve();
const done = new AbortController();
const settle = (fn)=>()=>{
done.abort();
fn();
};
this.socket.addEventListener("open", settle(resolve), {
signal: done.signal
});
this.socket.terminationSignal.addEventListener("abort", settle(failTerminated), {
signal: done.signal
});
signal?.addEventListener("abort", settle(failAborted), {
signal: done.signal
});
});
}
/**
* Permanently closes the WebSocket connection.
*
* @example
* ```ts
* import { WebSocketTransport } from "@nktkas/hyperliquid";
*
* const transport = new WebSocketTransport();
* transport.close();
* ```
*/ close() {
// socket.close() terminates synchronously on every path: when it returns,
// the termination signal is already aborted — there is nothing to await.
//
// Subscribing to the final "close" event instead would deadlock in one
// case: when this method runs inside a "close" listener, the socket treats
// the event being dispatched as its final one and never fires another, so
// a listener added here would never be invoked.
this.socket.close();
}
}
//# sourceMappingURL=mod.js.map