From bcec9f468cc1d9baeee5f3597d9c1cfd1dbe1cc8 Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 00:30:47 +0300 Subject: [PATCH 1/6] test(subscription): await asynchronous event collection setup --- tests/api/subscription/_t.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/api/subscription/_t.ts b/tests/api/subscription/_t.ts index 0d17f15b..cf85e936 100644 --- a/tests/api/subscription/_t.ts +++ b/tests/api/subscription/_t.ts @@ -89,13 +89,13 @@ export function runTestWithExchange(options: { export { createTWAP, openOrder } from "../exchange/_t.ts"; -export function collectEventsOverTime( - fn: (cb: (event: T) => void) => unknown, +/** Collects events for a fixed interval after asynchronous setup completes. */ +export async function collectEventsOverTime( + fn: (cb: (event: T) => void) => void | Promise, durationMs: number, ): Promise { - return new Promise((resolve) => { - const data: T[] = []; - fn((event) => data.push(event)); - setTimeout(() => resolve(data), durationMs); - }); + const data: T[] = []; + await fn((event) => data.push(event)); + await new Promise((resolve) => setTimeout(resolve, durationMs)); + return data; } From d27bf5813362a0419df9ee7d7f1fa9fbd514ccef Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 00:57:23 +0300 Subject: [PATCH 2/6] refactor(api/subscription): remove unsupported webData2 --- src/api/subscription/_methods/webData2.ts | 83 ----------------------- src/api/subscription/client.ts | 40 ----------- src/api/subscription/mod.ts | 1 - tests/api/subscription/webData2.test.ts | 55 --------------- 4 files changed, 179 deletions(-) delete mode 100644 src/api/subscription/_methods/webData2.ts delete mode 100644 tests/api/subscription/webData2.test.ts diff --git a/src/api/subscription/_methods/webData2.ts b/src/api/subscription/_methods/webData2.ts deleted file mode 100644 index e71bb567..00000000 --- a/src/api/subscription/_methods/webData2.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as v from "@valibot/valibot"; - -// ============================================================ -// API Schemas -// ============================================================ - -import { Address } from "../../_schemas.ts"; -import type { WebData2Response } from "../../info/_methods/webData2.ts"; - -/** - * Subscription to comprehensive user and market data events. - * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions - */ -export const WebData2Request = /* @__PURE__ */ (() => { - return v.object({ - /** Type of subscription. */ - type: v.literal("webData2"), - /** User address. */ - user: Address, - }); -})(); -export type WebData2Request = v.InferOutput; - -/** - * Event of comprehensive user and market data. - * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions - */ -export type WebData2Event = WebData2Response; - -// ============================================================ -// Execution Logic -// ============================================================ - -import { parse } from "../../../_base.ts"; -import type { ISubscription } from "../../../transport/mod.ts"; -import type { SubscriptionConfig, SubscriptionOptions } from "./_base/mod.ts"; - -/** Request parameters for the {@linkcode webData2} function. */ -export type WebData2Parameters = Omit, "type">; - -/** - * Subscribe to comprehensive user and market data updates. - * - * @deprecated use {@linkcode webData3} and other component subscriptions instead. - * - * @param config General configuration for Subscription API subscriptions. - * @param params Parameters specific to the API subscription. - * @param listener A callback function to be called when the event is received. - * @param options Options to control the subscription lifecycle. - * @return A request-promise that resolves with a {@link ISubscription} object to manage the subscription lifecycle. - * - * @throws {ValidationError} When the request parameters fail validation (before sending). - * @throws {TransportError} When the transport layer throws an error. - * - * @example - * ```ts - * import { WebSocketTransport } from "@nktkas/hyperliquid"; - * import { webData2 } from "@nktkas/hyperliquid/api/subscription"; - * - * const transport = new WebSocketTransport(); - * - * const sub = await webData2( - * { transport }, - * { user: "0x..." }, - * (data) => console.log(data), - * ); - * ``` - * - * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions - */ -export function webData2( - config: SubscriptionConfig, - params: WebData2Parameters, - listener: (data: WebData2Event) => void, - options?: SubscriptionOptions, -): Promise { - const payload = parse(WebData2Request, { type: "webData2", ...params }); - return config.transport.subscribe(payload.type, payload, (e) => { - if (e.detail.user === payload.user) { - listener(e.detail); - } - }, options); -} diff --git a/src/api/subscription/client.ts b/src/api/subscription/client.ts index 74df799e..c01a6d6a 100644 --- a/src/api/subscription/client.ts +++ b/src/api/subscription/client.ts @@ -69,7 +69,6 @@ import { type UserTwapSliceFillsEvent, type UserTwapSliceFillsParameters, } from "./_methods/userTwapSliceFills.ts"; -import { webData2, type WebData2Event, type WebData2Parameters } from "./_methods/webData2.ts"; import { webData3, type WebData3Event, type WebData3Parameters } from "./_methods/webData3.ts"; // ============================================================ @@ -1011,41 +1010,6 @@ export class SubscriptionClient { - * console.log(data); - * }); - * ``` - * - * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions - */ - webData2( - params: WebData2Parameters, - listener: (data: WebData2Event) => void, - options?: SubscriptionOptions, - ): Promise { - return webData2(this.config_, params, listener, options); - } - /** * Subscribe to comprehensive user and market data updates. * @@ -1167,10 +1131,6 @@ export type { UserTwapSliceFillsEvent as UserTwapSliceFillsWsEvent, UserTwapSliceFillsParameters as UserTwapSliceFillsWsParameters, } from "./_methods/userTwapSliceFills.ts"; -export type { - WebData2Event as WebData2WsEvent, - WebData2Parameters as WebData2WsParameters, -} from "./_methods/webData2.ts"; export type { WebData3Event as WebData3WsEvent, WebData3Parameters as WebData3WsParameters, diff --git a/src/api/subscription/mod.ts b/src/api/subscription/mod.ts index 1b7766c4..9f67b378 100644 --- a/src/api/subscription/mod.ts +++ b/src/api/subscription/mod.ts @@ -52,5 +52,4 @@ export * from "./_methods/userHistoricalOrders.ts"; export * from "./_methods/userNonFundingLedgerUpdates.ts"; export * from "./_methods/userTwapHistory.ts"; export * from "./_methods/userTwapSliceFills.ts"; -export * from "./_methods/webData2.ts"; export * from "./_methods/webData3.ts"; diff --git a/tests/api/subscription/webData2.test.ts b/tests/api/subscription/webData2.test.ts deleted file mode 100644 index d3234d98..00000000 --- a/tests/api/subscription/webData2.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { type WebData2Event, type WebData2Parameters, WebData2Request } from "@nktkas/hyperliquid/api/subscription"; -import * as v from "@valibot/valibot"; -import { schemaCoverage } from "../_utils/schemaCoverage.ts"; -import { typeToJsonSchema } from "../_utils/typeToJsonSchema.ts"; -import { valibotToJsonSchema } from "../_utils/valibotToJsonSchema.ts"; -import { collectEventsOverTime, runTest } from "./_t.ts"; - -const sourceFile = new URL("../../../src/api/subscription/_methods/webData2.ts", import.meta.url).pathname; -const responseSchema = typeToJsonSchema(sourceFile, "WebData2Event"); -const paramsSchema = valibotToJsonSchema(v.omit(WebData2Request, ["type"])); - -runTest({ - name: "webData2", - mode: "api", - fn: async (_t, client) => { - const params: WebData2Parameters[] = [ - { user: "0x0000000000000000000000000000000000000000" }, - { user: "0x563C175E6f11582f65D6d9E360A618699DEe14a9" }, - { user: "0xe019d6167E7e324aEd003d94098496b6d986aB05" }, - { user: "0x1defed46db35334232b9f5fd2e5c6180276fb99d" }, - ]; - - const data = await collectEventsOverTime(async (cb) => { - await Promise.all(params.map((p) => client.webData2(p, cb))); - }, 10_000); - - schemaCoverage(paramsSchema, params); - schemaCoverage(responseSchema, data, [ - "#/properties/openOrders/items/properties/orderType/enum/0", - "#/properties/openOrders/items/properties/orderType/enum/4", - "#/properties/openOrders/items/properties/orderType/enum/5", - "#/properties/openOrders/items/properties/tif/enum/1", - "#/properties/openOrders/items/properties/tif/enum/3", - "#/properties/openOrders/items/properties/tif/enum/4", - "#/properties/openOrders/items/properties/tif/enum/5", - "#/properties/openOrders/items/properties/cloid/defined", - "#/properties/meta/properties/universe/items/properties/marginMode/enum/1", - "#/properties/meta/properties/universe/items/properties/growthMode/present", - "#/properties/meta/properties/universe/items/properties/lastGrowthModeChangeTime/present", - "#/properties/twapStates/array", - "#/properties/perpsAtOpenInterestCap/present", - "#/properties/agentAddress/defined", - "#/properties/agentValidUntil/defined", - "#/properties/openOrders/items/properties/children/*", - "#/properties/spotState/properties/portfolioMarginEnabled/present", - "#/properties/spotState/properties/portfolioMarginRatio/present", - "#/properties/spotState/properties/tokenToPortfolioBorrowRatio/present", - "#/properties/spotState/properties/tokenToAvailableAfterMaintenance/present", - "#/properties/spotState/properties/balances/items/anyOf/0/properties/spotHold/present", - "#/properties/spotState/properties/balances/items/anyOf/0/properties/ltv/present", - "#/properties/spotState/properties/balances/items/anyOf/0/properties/borrowed/present", - "#/properties/spotState/properties/balances/items/anyOf/0/properties/supplied/present", - ]); - }, -}); From 720b8f21336d96222a0a44b088e88566cb0011f9 Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 04:18:35 +0300 Subject: [PATCH 3/6] refactor(api): remove discontinued dex abstraction responses --- src/api/info/_methods/userAbstraction.ts | 3 +-- src/api/subscription/_methods/webData3.ts | 2 +- .../exchange/agentEnableDexAbstraction.test.ts | 15 ++++++++------- tests/api/info/userAbstraction.test.ts | 10 ++++------ tests/api/info/userDexAbstraction.test.ts | 2 +- tests/api/subscription/webData3.test.ts | 1 - 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/api/info/_methods/userAbstraction.ts b/src/api/info/_methods/userAbstraction.ts index d6b854ff..20289f7b 100644 --- a/src/api/info/_methods/userAbstraction.ts +++ b/src/api/info/_methods/userAbstraction.ts @@ -28,8 +28,7 @@ export type UserAbstractionResponse = | "unifiedAccount" | "portfolioMargin" | "disabled" - | "default" - | "dexAbstraction"; + | "default"; // ============================================================ // Execution Logic diff --git a/src/api/subscription/_methods/webData3.ts b/src/api/subscription/_methods/webData3.ts index 99012a8d..23d385b7 100644 --- a/src/api/subscription/_methods/webData3.ts +++ b/src/api/subscription/_methods/webData3.ts @@ -55,7 +55,7 @@ export type WebData3Event = { /** Whether DEX abstraction is enabled. */ dexAbstractionEnabled?: boolean; /** Abstraction mode for the user account. */ - abstraction?: "dexAbstraction" | "unifiedAccount" | "portfolioMargin" | "disabled"; + abstraction?: "unifiedAccount" | "portfolioMargin" | "disabled"; }; /** Array of perpetual DEX states. */ perpDexStates: { diff --git a/tests/api/exchange/agentEnableDexAbstraction.test.ts b/tests/api/exchange/agentEnableDexAbstraction.test.ts index 3404ddc5..0c710da0 100644 --- a/tests/api/exchange/agentEnableDexAbstraction.test.ts +++ b/tests/api/exchange/agentEnableDexAbstraction.test.ts @@ -1,15 +1,16 @@ -import { schemaCoverage } from "../_utils/schemaCoverage.ts"; -import { typeToJsonSchema } from "../_utils/typeToJsonSchema.ts"; +import { ApiRequestError } from "@nktkas/hyperliquid"; +import { assertRejects } from "jsr:@std/assert@1"; import { runTest } from "./_t.ts"; -const sourceFile = new URL("../../../src/api/exchange/_methods/agentEnableDexAbstraction.ts", import.meta.url).pathname; -const responseSchema = typeToJsonSchema(sourceFile, "AgentEnableDexAbstractionSuccessResponse"); - runTest({ name: "agentEnableDexAbstraction", codeTestFn: async (_t, exchClient) => { - const data = await Promise.all([exchClient.agentEnableDexAbstraction()]); + await exchClient.agentSetAbstraction({ abstraction: "u" }); - schemaCoverage(responseSchema, data); + await assertRejects( + () => exchClient.agentEnableDexAbstraction(), + ApiRequestError, + "Abstraction transition not allowed", + ); }, }); diff --git a/tests/api/info/userAbstraction.test.ts b/tests/api/info/userAbstraction.test.ts index ab93efdd..ca5deeed 100644 --- a/tests/api/info/userAbstraction.test.ts +++ b/tests/api/info/userAbstraction.test.ts @@ -14,16 +14,14 @@ runTest({ codeTestFn: async (_t, client) => { const params: UserAbstractionParameters[] = [ { user: "0x0000000000000000000000000000000000000001" }, // "default" - { user: "0x563C175E6f11582f65D6d9E360A618699DEe14a9" }, // "dexAbstraction" + { user: "0xe019d6167E7e324aEd003d94098496b6d986aB05" }, // "disabled" + { user: "0x6b043579b088d44400dffa1ef1c5e3e3bfbdf9d2" }, // "portfolioMargin" + { user: "0x8c9c52889ab9d259195a52fd412c250f8183c960" }, // "unifiedAccount" ]; const data = await Promise.all(params.map((p) => client.userAbstraction(p))); schemaCoverage(paramsSchema, params); - schemaCoverage(responseSchema, data, [ - "#/enum/0", // "unifiedAccount" — not tested - "#/enum/1", // "portfolioMargin" — not tested - "#/enum/2", // "disabled" — not tested - ]); + schemaCoverage(responseSchema, data); }, }); diff --git a/tests/api/info/userDexAbstraction.test.ts b/tests/api/info/userDexAbstraction.test.ts index bc718a50..060078a3 100644 --- a/tests/api/info/userDexAbstraction.test.ts +++ b/tests/api/info/userDexAbstraction.test.ts @@ -14,7 +14,7 @@ runTest({ codeTestFn: async (_t, client) => { const params: UserDexAbstractionParameters[] = [ { user: "0x0000000000000000000000000000000000000001" }, // null - { user: "0x187e15e124b8297a01c355b6a87ae74dd4c0069f" }, // boolean + { user: "0xe019d6167E7e324aEd003d94098496b6d986aB05" }, // false ]; const data = await Promise.all(params.map((p) => client.userDexAbstraction(p))); diff --git a/tests/api/subscription/webData3.test.ts b/tests/api/subscription/webData3.test.ts index af5a6333..868f023d 100644 --- a/tests/api/subscription/webData3.test.ts +++ b/tests/api/subscription/webData3.test.ts @@ -15,7 +15,6 @@ runTest({ fn: async (_t, client) => { const params: WebData3Parameters[] = [ { user: "0x0000000000000000000000000000000000000000" }, - { user: "0x563C175E6f11582f65D6d9E360A618699DEe14a9" }, { user: "0xe019d6167E7e324aEd003d94098496b6d986aB05" }, { user: "0x6b043579b088d44400dffa1ef1c5e3e3bfbdf9d2" }, // portfolioMargin { user: "0x8c9c52889ab9d259195a52fd412c250f8183c960" }, // unifiedAccount From 3e0116e40cd29c732b91a8a1a7037681529f7091 Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 04:22:28 +0300 Subject: [PATCH 4/6] test(exchange): cover subaccount usdClassTransfer amount --- tests/api/exchange/usdClassTransfer.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/api/exchange/usdClassTransfer.test.ts b/tests/api/exchange/usdClassTransfer.test.ts index ab1bbc45..561c0db7 100644 --- a/tests/api/exchange/usdClassTransfer.test.ts +++ b/tests/api/exchange/usdClassTransfer.test.ts @@ -1,5 +1,7 @@ +import { ApiRequestError } from "@nktkas/hyperliquid"; import { type UsdClassTransferParameters, UsdClassTransferRequest } from "@nktkas/hyperliquid/api/exchange"; import * as v from "@valibot/valibot"; +import { assertRejects } from "jsr:@std/assert@1"; import { schemaCoverage } from "../_utils/schemaCoverage.ts"; import { typeToJsonSchema } from "../_utils/typeToJsonSchema.ts"; import { valibotToJsonSchema } from "../_utils/valibotToJsonSchema.ts"; @@ -19,21 +21,29 @@ const paramsSchema = valibotToJsonSchema( runTest({ name: "usdClassTransfer", codeTestFn: async (_t, exchClient) => { - // toPerp=false (Perp → Spot) const perpToSpot = await (async () => { const params: UsdClassTransferParameters = { amount: "1", toPerp: false }; return { params, result: await exchClient.usdClassTransfer(params) }; })(); - // toPerp=true (Spot → Perp) const spotToPerp = await (async () => { const params: UsdClassTransferParameters = { amount: "1", toPerp: true }; return { params, result: await exchClient.usdClassTransfer(params) }; })(); + const subAccountParams: UsdClassTransferParameters = { + amount: "1 subaccount:0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1", + toPerp: false, + }; + await assertRejects( + () => exchClient.usdClassTransfer(subAccountParams), + ApiRequestError, + "Sub-account 0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1 is not registered to", + ); + const data = [perpToSpot, spotToPerp]; - schemaCoverage(paramsSchema, data.map((d) => d.params)); + schemaCoverage(paramsSchema, [...data.map((d) => d.params), subAccountParams]); schemaCoverage(responseSchema, data.map((d) => d.result)); }, }); From 2d087bfdc16a745d253e2fe39a25320111954a1e Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 08:44:28 +0300 Subject: [PATCH 5/6] fix(api/exchange): support multisig userSetAbstraction --- .../exchange/_methods/userSetAbstraction.ts | 19 ++++++++-- src/api/exchange/client.ts | 2 +- tests/api/exchange/userSetAbstraction.test.ts | 35 +++++++++++-------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/api/exchange/_methods/userSetAbstraction.ts b/src/api/exchange/_methods/userSetAbstraction.ts index 64de78fd..3b4ea562 100644 --- a/src/api/exchange/_methods/userSetAbstraction.ts +++ b/src/api/exchange/_methods/userSetAbstraction.ts @@ -26,7 +26,7 @@ export const UserSetAbstractionRequest = /* @__PURE__ */ (() => { /** User address. */ user: Address, /** Abstraction mode to set. */ - abstraction: v.picklist(["dexAbstraction", "unifiedAccount", "portfolioMargin", "disabled"]), + abstraction: v.picklist(["disabled", "unifiedAccount", "portfolioMargin"]), /** Nonce (timestamp in ms) used to prevent replay attacks. */ nonce: UnsignedInteger, }), @@ -106,6 +106,14 @@ export const UserSetAbstractionTypes = { ], }; +/** Produces the action representation serialized into a multi-sig payload. */ +function toMultiSigPayloadAction(action: Readonly>): Record { + if (action.abstraction === "disabled") return { ...action, abstraction: "i" }; + if (action.abstraction === "unifiedAccount") return { ...action, abstraction: "u" }; + if (action.abstraction === "portfolioMargin") return { ...action, abstraction: "p" }; + return action; +} + /** * Set user abstraction mode. * @@ -133,7 +141,7 @@ export const UserSetAbstractionTypes = { * * await userSetAbstraction({ transport, wallet }, { * user: "0x...", - * abstraction: "dexAbstraction", + * abstraction: "unifiedAccount", * }); * ``` * @@ -148,5 +156,10 @@ export function userSetAbstraction( UserSetAbstractionActionSchema, parse(UserSetAbstractionActionSchema, { type: "userSetAbstraction", ...params }), ); - return executeUserSignedAction(config, action, UserSetAbstractionTypes, opts); + return executeUserSignedAction( + config, + action, + UserSetAbstractionTypes, + { ...opts, toMultiSigPayloadAction }, + ); } diff --git a/src/api/exchange/client.ts b/src/api/exchange/client.ts index 5a276630..5e7d2815 100644 --- a/src/api/exchange/client.ts +++ b/src/api/exchange/client.ts @@ -2352,7 +2352,7 @@ export class ExchangeClient { - // FIXME: Multisig does not support this method. - if ("signers" in exchClient.config_) return; + const user = "multiSigUser" in exchClient.config_ + ? exchClient.config_.multiSigUser + : await getWalletAddress(exchClient.config_.wallet); const params: UserSetAbstractionParameters[] = [ - { user: "0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1", abstraction: "dexAbstraction" }, - { user: "0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1", abstraction: "unifiedAccount" }, - { user: "0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1", abstraction: "portfolioMargin" }, - { user: "0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1", abstraction: "disabled" }, + { user, abstraction: "disabled" }, + { user, abstraction: "unifiedAccount" }, + { user, abstraction: "portfolioMargin" }, ]; - await Promise.all(params.map((p) => - assertRejects( - async () => { - await exchClient.userSetAbstraction(p); - }, - ApiRequestError, - "Sub-account 0xcb3f0bd249a89e45e86a44bcfc7113e4ffe84cd1 is not registered to", - ) - )); + const data = [ + await exchClient.userSetAbstraction(params[0]), + await exchClient.userSetAbstraction(params[1]), + ]; + await assertRejects( + () => exchClient.userSetAbstraction(params[2]), + ApiRequestError, + "Portfolio margin requires account value of $10000 or total volume of $5000000.", + ); schemaCoverage(paramsSchema, params); + schemaCoverage(responseSchema, data); }, }); From 342dbf447c8af39f6b72551a557a496b17ae3907 Mon Sep 17 00:00:00 2001 From: nktkas Date: Wed, 15 Jul 2026 20:33:13 +0300 Subject: [PATCH 6/6] refactor(api/explorer): support historical action tuples --- src/api/explorer/_methods/userDetails.ts | 14 ++++++- tests/api/explorer/_t.ts | 49 +++++++++++------------- tests/api/explorer/txDetails.test.ts | 4 +- tests/api/explorer/userDetails.test.ts | 1 + 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/api/explorer/_methods/userDetails.ts b/src/api/explorer/_methods/userDetails.ts index 2088e2b6..ec21cbd7 100644 --- a/src/api/explorer/_methods/userDetails.ts +++ b/src/api/explorer/_methods/userDetails.ts @@ -21,6 +21,18 @@ export const UserDetailsRequest = /* @__PURE__ */ (() => { })(); export type UserDetailsRequest = v.InferOutput; +/** + * Transaction returned by {@linkcode userDetails}. + * + * Historical entries may use a positional action tuple. + */ +type UserDetailsTransaction = + & Omit + & { + /** Action payload. */ + action: ExplorerTransaction["action"] | unknown[]; + }; + /** * Response array of user transaction details. * @see null @@ -29,7 +41,7 @@ export type UserDetailsResponse = { /** Type of response. */ type: "userDetails"; /** Array of user transaction details. */ - txs: ExplorerTransaction[]; + txs: UserDetailsTransaction[]; }; // ============================================================ diff --git a/tests/api/explorer/_t.ts b/tests/api/explorer/_t.ts index 7ae3f0c7..28694b91 100644 --- a/tests/api/explorer/_t.ts +++ b/tests/api/explorer/_t.ts @@ -1,35 +1,38 @@ +/** + * Shared helpers for live Explorer API tests. + * @module + */ + import { ExplorerClient, HttpTransport, WebSocketTransport } from "@nktkas/hyperliquid"; -// ============================================================= +// ============================================================================= // Arguments -// ============================================================= +// ============================================================================= const WAIT = 5000; const OFFLINE = Deno.args.includes("--offline"); -// ============================================================= +// ============================================================================= // Test -// ============================================================= +// ============================================================================= /** - * Runs an explorer HTTP-request test on an `HttpTransport` (testnet RPC URL). + * Runs an explorer HTTP-request test on an `HttpTransport` (testnet RPC URL by default). */ export function runRequestTest(options: { name: string; + /** Uses the testnet RPC when true; defaults to `true`. */ + isTestnet?: boolean; fn: (t: Deno.TestContext, client: ExplorerClient) => Promise; }): void { - const { name, fn } = options; + const { name, isTestnet = true, fn } = options; Deno.test(name, { ignore: OFFLINE }, async (t) => { await new Promise((r) => setTimeout(r, WAIT)); // delay to avoid rate limits - // --- Preparation ------------------------------------------------ - - const transport = new HttpTransport({ isTestnet: true }); + const transport = new HttpTransport({ isTestnet }); const client = new ExplorerClient({ transport }); - // --- Test ------------------------------------------------ - await fn(t, client); }); } @@ -46,34 +49,28 @@ export function runSubscriptionTest(options: { Deno.test(name, { ignore: OFFLINE }, async (t) => { await new Promise((r) => setTimeout(r, WAIT)); // delay to avoid rate limits - // --- Preparation ------------------------------------------------ - const transport = new WebSocketTransport({ url: "wss://rpc.hyperliquid-testnet.xyz/ws", isTestnet: true }); await transport.ready(); const client = new ExplorerClient({ transport }); - // --- Test ------------------------------------------------ - await fn(t, client) .finally(() => { - // --- Cleanup ------------------------------------------------ - transport.close(); }); }); } -// ============================================================= +// ============================================================================= // Helpers -// ============================================================= +// ============================================================================= -export function collectEventsOverTime( - fn: (cb: (event: T) => void) => unknown, +/** Collects events for a fixed interval after asynchronous setup completes. */ +export async function collectEventsOverTime( + fn: (cb: (event: T) => void) => void | Promise, durationMs: number, ): Promise { - return new Promise((resolve) => { - const data: T[] = []; - fn((event) => data.push(event)); - setTimeout(() => resolve(data), durationMs); - }); + const data: T[] = []; + await fn((event) => data.push(event)); + await new Promise((resolve) => setTimeout(resolve, durationMs)); + return data; } diff --git a/tests/api/explorer/txDetails.test.ts b/tests/api/explorer/txDetails.test.ts index eafd7b8f..17c6ea5f 100644 --- a/tests/api/explorer/txDetails.test.ts +++ b/tests/api/explorer/txDetails.test.ts @@ -13,8 +13,8 @@ runRequestTest({ name: "txDetails", fn: async (_t, client) => { const params: TxDetailsParameters[] = [ - { hash: "0x8f1b2b67eda04ecbc7b00411ee669b010c0041e8f52c9ff5c3609d9ef7e66c71" }, // error = null - { hash: "0x4de9f1f5d912c23d8fbb0411f01bfe0000eb9f3ccb3fec747cb96e75e8944b06" }, // error = string + { hash: "0x4de9f1f5d912c23d8fbb0411f01bfe0000eb9f3ccb3fec747cb96e75e8944b06" }, // error = null + { hash: "0x8f1b2b67eda04ecbc7b00411ee669b010c0041e8f52c9ff5c3609d9ef7e66c71" }, // error = string ]; const data = await Promise.all(params.map((p) => client.txDetails(p))); diff --git a/tests/api/explorer/userDetails.test.ts b/tests/api/explorer/userDetails.test.ts index 488a3dc4..0b48090e 100644 --- a/tests/api/explorer/userDetails.test.ts +++ b/tests/api/explorer/userDetails.test.ts @@ -11,6 +11,7 @@ const paramsSchema = valibotToJsonSchema(v.omit(UserDetailsRequest, ["type"])); runRequestTest({ name: "userDetails", + isTestnet: false, fn: async (_t, client) => { const params: UserDetailsParameters[] = [ { user: "0x9150749C4cec13Dc7c1555D0d664F08d4d81Be83" },