diff --git a/src/api/exchange/_methods/activateOutcomeDeployer.ts b/src/api/exchange/_methods/activateOutcomeDeployer.ts new file mode 100644 index 00000000..b5a72967 --- /dev/null +++ b/src/api/exchange/_methods/activateOutcomeDeployer.ts @@ -0,0 +1,132 @@ +import * as v from "@valibot/valibot"; + +// ============================================================ +// API Schemas +// ============================================================ + +import { Hex, UnsignedInteger } from "../../_schemas.ts"; + +/** + * Activate or deactivate the signer as an outcome deployer. + * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/hip-4-deployer-actions#activation + */ +export const ActivateOutcomeDeployerRequest = /* @__PURE__ */ (() => { + return v.object({ + /** Action to perform. */ + action: v.object({ + /** Type of action. */ + type: v.literal("activateOutcomeDeployer"), + /** Deactivate instead of activate. */ + isDeactivate: v.boolean(), + }), + /** Nonce (timestamp in ms) used to prevent replay attacks. */ + nonce: UnsignedInteger, + /** ECDSA signature components. */ + signature: v.object({ + /** First 32-byte component. */ + r: v.pipe(Hex, v.length(66)), + /** Second 32-byte component. */ + s: v.pipe(Hex, v.length(66)), + /** Recovery identifier. */ + v: v.picklist([27, 28]), + }), + /** Expiration time of the action. */ + expiresAfter: v.optional(UnsignedInteger), + }); +})(); +export type ActivateOutcomeDeployerRequest = v.InferOutput; + +/** + * Successful response without specific data or error response. + * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/hip-4-deployer-actions#activation + */ +export type ActivateOutcomeDeployerResponse = + | { + /** Successful status. */ + status: "ok"; + /** Response details. */ + response: { + /** Type of response. */ + type: "default"; + }; + } + | { + /** Error status. */ + status: "err"; + /** Error message. */ + response: string; + }; + +// ============================================================ +// Execution Logic +// ============================================================ + +import { parse } from "../../../_base.ts"; +import { canonicalize } from "../../../signing/mod.ts"; +import { + type ExchangeConfig, + type ExcludeErrorResponse, + executeL1Action, + type ExtractRequestOptions, +} from "./_base/mod.ts"; + +/** Schema for action fields (excludes request-level system fields). */ +const ActivateOutcomeDeployerActionSchema = /* @__PURE__ */ (() => { + return v.object(ActivateOutcomeDeployerRequest.entries.action.entries); +})(); + +/** Action parameters for the {@linkcode activateOutcomeDeployer} function. */ +export type ActivateOutcomeDeployerParameters = Omit< + v.InferInput, + "type" +>; + +/** Request options for the {@linkcode activateOutcomeDeployer} function. */ +export type ActivateOutcomeDeployerOptions = ExtractRequestOptions< + v.InferInput +>; + +/** Successful variant of {@linkcode ActivateOutcomeDeployerResponse} without errors. */ +export type ActivateOutcomeDeployerSuccessResponse = ExcludeErrorResponse; + +/** + * Activate or deactivate the signer as an outcome deployer. + * + * Signing: L1 Action. + * + * @param config General configuration for Exchange API requests. + * @param params Parameters specific to the API request. + * @param opts Request execution options. + * @return Successful response without specific data. + * + * @throws {ValidationError} When the request parameters fail validation (before sending). + * @throws {TransportError} When the transport layer throws an error. + * @throws {ApiRequestError} When the API returns an unsuccessful response. + * + * @example + * ```ts + * import { HttpTransport } from "@nktkas/hyperliquid"; + * import { activateOutcomeDeployer } from "@nktkas/hyperliquid/api/exchange"; + * import { privateKeyToAccount } from "npm:viem/accounts"; + * + * const wallet = privateKeyToAccount("0x..."); // viem or ethers + * const transport = new HttpTransport(); // or `WebSocketTransport` + * + * await activateOutcomeDeployer({ transport, wallet }, { + * isDeactivate: false, + * }); + * ``` + * + * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/hip-4-deployer-actions#activation + */ +export function activateOutcomeDeployer( + config: ExchangeConfig, + params: ActivateOutcomeDeployerParameters, + opts?: ActivateOutcomeDeployerOptions, +): Promise { + const action = canonicalize( + ActivateOutcomeDeployerActionSchema, + parse(ActivateOutcomeDeployerActionSchema, { type: "activateOutcomeDeployer", ...params }), + ); + return executeL1Action(config, action, opts); +} diff --git a/src/api/exchange/client.ts b/src/api/exchange/client.ts index 5e7d2815..57f4fc57 100644 --- a/src/api/exchange/client.ts +++ b/src/api/exchange/client.ts @@ -9,6 +9,12 @@ import type { ExchangeConfig, ExchangeSingleWalletConfig } from "./_methods/_bas // Methods Imports // ============================================================ +import { + activateOutcomeDeployer, + type ActivateOutcomeDeployerOptions, + type ActivateOutcomeDeployerParameters, + type ActivateOutcomeDeployerSuccessResponse, +} from "./_methods/activateOutcomeDeployer.ts"; import { agentEnableDexAbstraction, type AgentEnableDexAbstractionOptions, @@ -394,6 +400,40 @@ export class ExchangeClient { + return activateOutcomeDeployer(this.config_, params, opts); + } + /** * Enable HIP-3 DEX abstraction. * @@ -2588,6 +2628,11 @@ export { type ExchangeSingleWalletConfig, } from "./_methods/_base/mod.ts"; +export type { + ActivateOutcomeDeployerOptions, + ActivateOutcomeDeployerParameters, + ActivateOutcomeDeployerSuccessResponse, +} from "./_methods/activateOutcomeDeployer.ts"; export type { AgentEnableDexAbstractionOptions, AgentEnableDexAbstractionSuccessResponse, diff --git a/src/api/exchange/mod.ts b/src/api/exchange/mod.ts index 57fb05a9..06830b6a 100644 --- a/src/api/exchange/mod.ts +++ b/src/api/exchange/mod.ts @@ -44,6 +44,7 @@ export { type ExchangeSingleWalletConfig, } from "./_methods/_base/mod.ts"; +export * from "./_methods/activateOutcomeDeployer.ts"; export * from "./_methods/agentEnableDexAbstraction.ts"; export * from "./_methods/agentSendAsset.ts"; export * from "./_methods/agentSetAbstraction.ts"; diff --git a/tests/api/exchange/activateOutcomeDeployer.test.ts b/tests/api/exchange/activateOutcomeDeployer.test.ts new file mode 100644 index 00000000..3582df5a --- /dev/null +++ b/tests/api/exchange/activateOutcomeDeployer.test.ts @@ -0,0 +1,43 @@ +import { ApiRequestError } from "@nktkas/hyperliquid"; +import { + type ActivateOutcomeDeployerParameters, + ActivateOutcomeDeployerRequest, +} 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 { valibotToJsonSchema } from "../_utils/valibotToJsonSchema.ts"; +import { runTest } from "./_t.ts"; + +const paramsSchema = valibotToJsonSchema( + v.omit(v.object(ActivateOutcomeDeployerRequest.entries.action.entries), ["type"]), +); + +runTest({ + name: "activateOutcomeDeployer", + codeTestFn: async (_t, exchClient) => { + const params: ActivateOutcomeDeployerParameters[] = [ + // activate + { isDeactivate: false }, + // deactivate + { isDeactivate: true }, + ]; + + await assertRejects( + async () => { + await exchClient.activateOutcomeDeployer(params[0]); + }, + ApiRequestError, + "Insufficient stake", + ); + await assertRejects( + async () => { + await exchClient.activateOutcomeDeployer(params[1]); + }, + ApiRequestError, + "Error deploying outcome: not an outcome deployer", + ); + + schemaCoverage(paramsSchema, params); + }, +});