diff --git a/src/api/exchange/_methods/userSetAbstraction.ts b/src/api/exchange/_methods/userSetAbstraction.ts index d90de573..c6dba670 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(["disabled", "unifiedAccount", "portfolioMargin"]), + abstraction: v.picklist(["disabled", "unifiedAccount", "portfolioMargin", "dexAbstraction"]), /** Nonce (timestamp in ms) used to prevent replay attacks. */ nonce: UnsignedInteger, }), @@ -111,6 +111,8 @@ function toMultiSigPayloadAction(action: Readonly>): 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; } diff --git a/src/api/info/_methods/userAbstraction.ts b/src/api/info/_methods/userAbstraction.ts index 577e55fb..dbf0004e 100644 --- a/src/api/info/_methods/userAbstraction.ts +++ b/src/api/info/_methods/userAbstraction.ts @@ -24,7 +24,7 @@ export type UserAbstractionRequest = v.InferOutput { } }); + 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 }; + 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); diff --git a/tests/api/exchange/userSetAbstraction.test.ts b/tests/api/exchange/userSetAbstraction.test.ts index 6b649495..2038cc17 100644 --- a/tests/api/exchange/userSetAbstraction.test.ts +++ b/tests/api/exchange/userSetAbstraction.test.ts @@ -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); }, }); diff --git a/tests/api/info/userAbstraction.test.ts b/tests/api/info/userAbstraction.test.ts index 1f2ed792..a78ef74b 100644 --- a/tests/api/info/userAbstraction.test.ts +++ b/tests/api/info/userAbstraction.test.ts @@ -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"]); }, }); diff --git a/tests/api/subscription/client.test.ts b/tests/api/subscription/client.test.ts index cae6db91..f798dcae 100644 --- a/tests/api/subscription/client.test.ts +++ b/tests/api/subscription/client.test.ts @@ -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: [] }], }, ]; diff --git a/tests/api/subscription/webData3.test.ts b/tests/api/subscription/webData3.test.ts index 1cc04793..77e165be 100644 --- a/tests/api/subscription/webData3.test.ts +++ b/tests/api/subscription/webData3.test.ts @@ -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", ]); }, });