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
4 changes: 3 additions & 1 deletion 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(["disabled", "unifiedAccount", "portfolioMargin"]),
abstraction: v.picklist(["disabled", "unifiedAccount", "portfolioMargin", "dexAbstraction"]),
/** Nonce (timestamp in ms) used to prevent replay attacks. */
nonce: UnsignedInteger,
}),
Expand Down Expand Up @@ -111,6 +111,8 @@ function toMultiSigPayloadAction(action: Readonly<Record<string, unknown>>): Rec
if (action.abstraction === "disabled") return { ...action, abstraction: "i" };
if (action.abstraction === "unifiedAccount") return { ...action, abstraction: "u" };
if (action.abstraction === "portfolioMargin") return { ...action, abstraction: "p" };
// "dexAbstraction" has no single-letter wire form; the full name goes through unchanged
// (verified accepted by the exchange's action deserializer).
return action;
}

Expand Down
2 changes: 1 addition & 1 deletion src/api/info/_methods/userAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type UserAbstractionRequest = v.InferOutput<typeof UserAbstractionRequest
* User abstraction state.
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#query-a-users-abstraction-state
*/
export type UserAbstractionResponse = "unifiedAccount" | "portfolioMargin" | "disabled" | "default";
export type UserAbstractionResponse = "unifiedAccount" | "portfolioMargin" | "disabled" | "default" | "dexAbstraction";

// ============================================================
// Execution Logic
Expand Down
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?: "unifiedAccount" | "portfolioMargin" | "disabled";
abstraction?: "unifiedAccount" | "portfolioMargin" | "disabled" | "dexAbstraction";
};
/** Array of perpetual DEX states. */
perpDexStates: {
Expand Down
16 changes: 15 additions & 1 deletion tests/api/exchange/_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,12 +624,26 @@ describe("ExchangeClient multi-sig paths (offline)", () => {
}
});

test("userSetAbstraction: dexAbstraction passes through to the multi-sig payload unmapped", async () => {
const { calls, transport } = recordingTransport();
const client = new ExchangeClient(multiSigConfig(transport));

// dexAbstraction has no single-letter wire form — the full name goes through unchanged.
await client.userSetAbstraction({ user: MULTI_SIG_USER, abstraction: "dexAbstraction" });

assertEquals(calls.length, 1);
const payload = calls[0].payload.action.payload as { action: Record<string, unknown> };
assertEquals(payload.action.abstraction, "dexAbstraction");
assertEquals(payload.action.user, MULTI_SIG_USER);
});

test("userSetAbstraction: an unmapped abstraction passes through on the skipValidation path", async () => {
const { calls, transport } = recordingTransport();
const client = new ExchangeClient(multiSigConfig(transport));

// Only reachable with validation skipped: the schema restricts abstraction to the three
// mapped values, so any other string exercises the payload-mapping passthrough branch.
// letter-mapped values plus dexAbstraction, so any other string exercises the
// payload-mapping passthrough branch.
await client.userSetAbstraction({ user: MULTI_SIG_USER, abstraction: "other" as never }, { skipValidation: true });

assertEquals(calls.length, 1);
Expand Down
4 changes: 3 additions & 1 deletion tests/api/exchange/userSetAbstraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ runTest({
"Portfolio margin requires account value of $10000 or total volume of $5000000.",
);

schemaCoverage(paramsSchema, params);
// "dexAbstraction" is accepted server-side (verified against the exchange deserializer)
// but flipping the shared live test account into that mode would leak into other tests.
schemaCoverage(paramsSchema, params, ["#/properties/abstraction/enum/3"]);
schemaCoverage(responseSchema, data);
},
});
4 changes: 3 additions & 1 deletion tests/api/info/userAbstraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ runTest({
const data = await Promise.all(params.map((p) => client.userAbstraction(p)));

schemaCoverage(paramsSchema, params);
schemaCoverage(responseSchema, data);
// "dexAbstraction" (#/enum/4) is accepted server-side (verified against the exchange
// deserializer) but no testnet account is in that mode, so it can't be produced live.
schemaCoverage(responseSchema, data, ["#/enum/4"]);
},
});

Expand Down
2 changes: 1 addition & 1 deletion tests/api/subscription/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ const CASES: MethodCase[] = [
invalid: [(client) => client.webData3({ user: "0x123" } as never, () => {})],
channel: "webData3",
payload: { type: "webData3", user: USER },
match: { userState: { user: USER }, perpDexStates: [] },
match: { userState: { user: USER, abstraction: "dexAbstraction" }, perpDexStates: [] },
misses: [{ userState: { user: OTHER_USER }, perpDexStates: [] }],
},
];
Expand Down
2 changes: 2 additions & 0 deletions tests/api/subscription/webData3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ runTest({
schemaCoverage(responseSchema, data.flat(), [
"#/properties/userState/properties/agentAddress/defined",
"#/properties/userState/properties/agentValidUntil/defined",
// No testnet account in dexAbstraction mode, so that enum branch can't be produced live.
"#/properties/userState/properties/abstraction/enum/3",
]);
},
});