Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/api/exchange/_methods/userSetAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down Expand Up @@ -106,6 +106,14 @@ export const UserSetAbstractionTypes = {
],
};

/** Produces the action representation serialized into a multi-sig payload. */
function toMultiSigPayloadAction(action: Readonly<Record<string, unknown>>): Record<string, unknown> {
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.
*
Expand Down Expand Up @@ -133,7 +141,7 @@ export const UserSetAbstractionTypes = {
*
* await userSetAbstraction({ transport, wallet }, {
* user: "0x...",
* abstraction: "dexAbstraction",
* abstraction: "unifiedAccount",
* });
* ```
*
Expand All @@ -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 },
);
}
2 changes: 1 addition & 1 deletion src/api/exchange/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2352,7 +2352,7 @@ export class ExchangeClient<C extends ExchangeConfig = ExchangeSingleWalletConfi
* const transport = new hl.HttpTransport(); // or `WebSocketTransport`
* const client = new hl.ExchangeClient({ transport, wallet });
*
* await client.userSetAbstraction({ user: "0x...", abstraction: "dexAbstraction" });
* await client.userSetAbstraction({ user: "0x...", abstraction: "unifiedAccount" });
* ```
*
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#set-user-abstraction
Expand Down
14 changes: 13 additions & 1 deletion src/api/explorer/_methods/userDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ export const UserDetailsRequest = /* @__PURE__ */ (() => {
})();
export type UserDetailsRequest = v.InferOutput<typeof UserDetailsRequest>;

/**
* Transaction returned by {@linkcode userDetails}.
*
* Historical entries may use a positional action tuple.
*/
type UserDetailsTransaction =
& Omit<ExplorerTransaction, "action">
& {
/** Action payload. */
action: ExplorerTransaction["action"] | unknown[];
};

/**
* Response array of user transaction details.
* @see null
Expand All @@ -29,7 +41,7 @@ export type UserDetailsResponse = {
/** Type of response. */
type: "userDetails";
/** Array of user transaction details. */
txs: ExplorerTransaction[];
txs: UserDetailsTransaction[];
};

// ============================================================
Expand Down
3 changes: 1 addition & 2 deletions src/api/info/_methods/userAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export type UserAbstractionResponse =
| "unifiedAccount"
| "portfolioMargin"
| "disabled"
| "default"
| "dexAbstraction";
| "default";

// ============================================================
// Execution Logic
Expand Down
83 changes: 0 additions & 83 deletions src/api/subscription/_methods/webData2.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/api/subscription/_methods/webData3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
40 changes: 0 additions & 40 deletions src/api/subscription/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

// ============================================================
Expand Down Expand Up @@ -1011,41 +1010,6 @@ export class SubscriptionClient<C extends SubscriptionConfig = SubscriptionConfi
return userTwapSliceFills(this.config_, params, listener, options);
}

/**
* Subscribe to comprehensive user and market data updates.
*
* @deprecated use {@linkcode webData3} and other component subscriptions instead.
*
* @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 * as hl from "@nktkas/hyperliquid";
*
* const transport = new hl.WebSocketTransport();
* const client = new hl.SubscriptionClient({ transport });
*
* const sub = await client.webData2({ user: "0x..." }, (data) => {
* 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<ISubscription> {
return webData2(this.config_, params, listener, options);
}

/**
* Subscribe to comprehensive user and market data updates.
*
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion src/api/subscription/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
15 changes: 8 additions & 7 deletions tests/api/exchange/agentEnableDexAbstraction.test.ts
Original file line number Diff line number Diff line change
@@ -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",
);
},
});
16 changes: 13 additions & 3 deletions tests/api/exchange/usdClassTransfer.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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));
},
});
35 changes: 20 additions & 15 deletions tests/api/exchange/userSetAbstraction.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { ApiRequestError } from "@nktkas/hyperliquid";
import { type UserSetAbstractionParameters, UserSetAbstractionRequest } from "@nktkas/hyperliquid/api/exchange";
import { getWalletAddress } from "@nktkas/hyperliquid/signing";
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";
import { runTest } from "./_t.ts";

const sourceFile = new URL("../../../src/api/exchange/_methods/userSetAbstraction.ts", import.meta.url).pathname;
const responseSchema = typeToJsonSchema(sourceFile, "UserSetAbstractionSuccessResponse");
const paramsSchema = valibotToJsonSchema(
v.omit(v.object(UserSetAbstractionRequest.entries.action.entries), [
"type",
Expand All @@ -18,26 +22,27 @@ const paramsSchema = valibotToJsonSchema(
runTest({
name: "userSetAbstraction",
codeTestFn: async (_t, exchClient) => {
// 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);
},
});
Loading