@nktkas/hyperliquid
Version:
Hyperliquid API SDK for all major JS runtimes, written in TypeScript.
148 lines (147 loc) • 5.36 kB
JavaScript
/**
* Execute helpers for L1 and user-signed Exchange API actions.
* @module
*/ import * as v from "valibot";
import { HyperliquidError, parse } from "../../../../_base.js";
import { getWalletChainId, signL1Action, signMultiSigL1, signMultiSigUserSigned, signUserSignedAction } from "../../../../signing/mod.js";
import { Address, Hex, UnsignedInteger } from "../../../_schemas.js";
import { executeWithShell } from "./_shell.js";
// ============================================================
// Execute L1 Action
// ============================================================
/**
* Execute an L1 action on the Hyperliquid Exchange.
*
* Handles both single-wallet and multi-sig signing.
*
* @param config Exchange API configuration.
* @param action Action payload to execute.
* @param options Additional options for the request.
* @return API response.
*
* @throws {ValidationError} If the request options fail validation.
* @throws {ApiRequestError} If the API returns an error response.
*/ export async function executeL1Action(config, action, options) {
// Validate options before acquiring the lock.
const vaultAddress = parse(v.optional(Address), options?.vaultAddress ?? config.defaultVaultAddress);
const expiresAfter = parse(v.optional(UnsignedInteger), options?.expiresAfter ?? (typeof config.defaultExpiresAfter === "function" ? await config.defaultExpiresAfter() : config.defaultExpiresAfter));
return executeWithShell(config, async (nonce)=>{
if ("wallet" in config) {
const signature = await signL1Action({
wallet: config.wallet,
action,
nonce,
isTestnet: config.transport.isTestnet,
vaultAddress,
expiresAfter
});
return {
action,
signature,
extras: {
vaultAddress,
expiresAfter
}
};
} else {
const { action: wrapper, signature } = await signMultiSigL1({
signers: config.signers,
multiSigUser: config.multiSigUser,
signatureChainId: await resolveSignatureChainId(config),
action,
nonce,
isTestnet: config.transport.isTestnet,
vaultAddress,
expiresAfter
});
return {
action: wrapper,
signature,
extras: {
vaultAddress,
expiresAfter
}
};
}
}, options?.signal);
}
// ============================================================
// Execute User-Signed Action
// ============================================================
/**
* Execute a user-signed action (EIP-712) on the Hyperliquid Exchange.
*
* Handles both single-wallet and multi-sig signing.
*
* @param config Exchange API configuration.
* @param action Action payload to execute.
* @param types EIP-712 type definitions for signing.
* @param options Additional options for the request.
* @return API response.
*
* @throws {ValidationError} If the request options fail validation.
* @throws {ApiRequestError} If the API returns an error response.
*/ export function executeUserSignedAction(config, action, types, options) {
return executeWithShell(config, async (nonce)=>{
// --- Construct full action (type, system fields, user fields, nonce/time)
const { type, ...restAction } = action;
const nonceFieldName = extractNonceFieldName(types);
const baseFields = {
type,
signatureChainId: await resolveSignatureChainId(config),
hyperliquidChain: config.transport.isTestnet ? "Testnet" : "Mainnet"
};
const fullAction = nonceFieldName === "nonce" ? {
...baseFields,
...restAction,
nonce
} : {
...baseFields,
...restAction,
time: nonce
};
// --- Sign (single-wallet or multi-sig) -------------------
if ("wallet" in config) {
const signature = await signUserSignedAction({
wallet: config.wallet,
action: fullAction,
types
});
return {
action: fullAction,
signature
};
} else {
const { action: wrapper, signature } = await signMultiSigUserSigned({
signers: config.signers,
multiSigUser: config.multiSigUser,
action: fullAction,
types
});
return {
action: wrapper,
signature
};
}
}, options?.signal);
}
// ============================================================
// Helpers
// ============================================================
/** Extracts the nonce field name ("nonce" or "time") from EIP-712 type definitions. */ function extractNonceFieldName(types) {
const primaryType = Object.keys(types)[0];
const field = types[primaryType].find((f)=>f.name === "nonce" || f.name === "time");
if (!field) {
throw new HyperliquidError(`EIP-712 types must contain a "nonce" or "time" field in "${primaryType}"`);
}
return field.name;
}
/** Resolves signature chain ID from config, or falls back to the leader wallet's chain ID. */ async function resolveSignatureChainId(config) {
if (config.signatureChainId) {
const id = typeof config.signatureChainId === "function" ? await config.signatureChainId() : config.signatureChainId;
return parse(Hex, id);
}
const leader = "wallet" in config ? config.wallet : config.signers[0];
return await getWalletChainId(leader);
}
//# sourceMappingURL=execute.js.map