UNPKG

@nktkas/hyperliquid

Version:

Unofficial Hyperliquid API SDK for all major JS runtimes, written in TypeScript.

369 lines (368 loc) 13.3 kB
"use strict"; /** * This module contains functions for generating Hyperliquid transaction signatures * and interfaces to various wallet implementations. * * @example Signing an L1 action * ```ts * import { actionSorter, signL1Action } from "@nktkas/hyperliquid/signing"; * * const privateKey = "0x..."; // `viem`, `ethers`, or private key directly` * * const action = actionSorter.cancel({ * type: "cancel", * cancels: [ * { a: 0, o: 12345 }, * ], * }); * const nonce = Date.now(); * * const signature = await signL1Action({ wallet: privateKey, action, nonce }); * * // Send the signed action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action, signature, nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` * * @example Signing a user-signed action * ```ts * import { actionSorter, signUserSignedAction, userSignedActionEip712Types } from "@nktkas/hyperliquid/signing"; * * const privateKey = "0x..."; // `viem`, `ethers`, or private key directly` * * const action = actionSorter.approveAgent({ * type: "approveAgent", * signatureChainId: "0x66eee", * hyperliquidChain: "Mainnet", * agentAddress: "0x...", * agentName: "Agent", * nonce: Date.now(), * }); * * const signature = await signUserSignedAction({ * wallet: privateKey, * action, * types: userSignedActionEip712Types[action.type], * }); * * // Send the signed action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action, signature, nonce: action.nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` * * @example Signing a multi-signature action * ```ts * import { actionSorter, signL1Action, signMultiSigAction } from "@nktkas/hyperliquid/signing"; * import { privateKeyToAccount } from "viem/accounts"; * * const wallet = privateKeyToAccount("0x..."); // or `ethers`, private key directly * const multiSigUser = "0x..."; * * const action = actionSorter.scheduleCancel({ * type: "scheduleCancel", * time: Date.now() + 10000, * }); * const nonce = Date.now(); * * // Create the required number of signatures * const signatures = await Promise.all(["0x...", "0x..."].map(async (signerPrivKey) => { * return await signL1Action({ * wallet: signerPrivKey as `0x${string}`, * action: [multiSigUser.toLowerCase(), wallet.address.toLowerCase(), action], * nonce, * }); * })); * * // or user-signed action * // const signatures = await Promise.all(["0x...", "0x..."].map(async (signerPrivKey) => { * // return await signUserSignedAction({ * // wallet: signerPrivKey as `0x${string}`, * // action: { * // ...action, * // payloadMultiSigUser: multiSigUser, * // outerSigner: wallet.address, * // }, * // types: userSignedActionEip712Types[action.type], * // }); * // })); * * // Then use signatures in the multi-sig action * const multiSigAction = actionSorter.multiSig({ * type: "multiSig", * signatureChainId: "0x66eee", * signatures, * payload: { * multiSigUser, * outerSigner: wallet.address, * action, * }, * }); * const multiSigSignature = await signMultiSigAction({ wallet, action: multiSigAction, nonce }); * * // Send the multi-sig action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action: multiSigAction, signature: multiSigSignature, nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` * * @module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.userSignedActionEip712Types = exports.getWalletChainId = exports.getWalletAddress = exports.actionSorter = void 0; exports.createL1ActionHash = createL1ActionHash; exports.signL1Action = signL1Action; exports.signUserSignedAction = signUserSignedAction; exports.signMultiSigAction = signMultiSigAction; const sha3_1 = require("@noble/hashes/sha3"); const secp256k1_1 = require("@noble/secp256k1"); const msgpack_1 = require("@msgpack/msgpack"); const mod_js_1 = require("./_signTypedData/mod.js"); Object.defineProperty(exports, "getWalletAddress", { enumerable: true, get: function () { return mod_js_1.getWalletAddress; } }); Object.defineProperty(exports, "getWalletChainId", { enumerable: true, get: function () { return mod_js_1.getWalletChainId; } }); const _sorter_js_1 = require("./_sorter.js"); Object.defineProperty(exports, "actionSorter", { enumerable: true, get: function () { return _sorter_js_1.actionSorter; } }); Object.defineProperty(exports, "userSignedActionEip712Types", { enumerable: true, get: function () { return _sorter_js_1.userSignedActionEip712Types; } }); /** * Create a hash of the L1 action. * @example * ```ts * import { actionSorter, createL1ActionHash } from "@nktkas/hyperliquid/signing"; * * const action = actionSorter.cancel({ * type: "cancel", * cancels: [ * { a: 0, o: 12345 }, * ], * }); * const nonce = Date.now(); * * const actionHash = createL1ActionHash({ action, nonce }); * ``` */ function createL1ActionHash(args) { const { action, nonce, vaultAddress, expiresAfter } = args; // 1. Action const actionBytes = (0, msgpack_1.encode)(action); // 2. Nonce const nonceBytes = toUint64Bytes(nonce); // 3. Vault address const vaultMarker = vaultAddress ? new Uint8Array([1]) : new Uint8Array([0]); const vaultBytes = vaultAddress ? secp256k1_1.etc.hexToBytes(vaultAddress.slice(2)) : new Uint8Array(); // 4. Expires after const expiresMarker = expiresAfter !== undefined ? new Uint8Array([0]) : new Uint8Array(); const expiresBytes = expiresAfter !== undefined ? toUint64Bytes(expiresAfter) : new Uint8Array(); // Create a hash const bytes = secp256k1_1.etc.concatBytes(actionBytes, nonceBytes, vaultMarker, vaultBytes, expiresMarker, expiresBytes); const hash = (0, sha3_1.keccak_256)(bytes); return `0x${secp256k1_1.etc.bytesToHex(hash)}`; } function toUint64Bytes(n) { const bytes = new Uint8Array(8); new DataView(bytes.buffer).setBigUint64(0, BigInt(n)); return bytes; } /** * Sign an L1 action. * @example * ```ts * import { actionSorter, signL1Action } from "@nktkas/hyperliquid/signing"; * * const privateKey = "0x..."; // `viem`, `ethers`, or private key directly` * * const action = actionSorter.cancel({ * type: "cancel", * cancels: [ * { a: 0, o: 12345 }, * ], * }); * const nonce = Date.now(); * * const signature = await signL1Action({ wallet: privateKey, action, nonce }); * * // Send the signed action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action, signature, nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` */ async function signL1Action(args) { const { wallet, action, nonce, isTestnet = false, vaultAddress, expiresAfter, } = args; const actionHash = createL1ActionHash({ action, nonce, vaultAddress, expiresAfter }); const message = { source: isTestnet ? "b" : "a", connectionId: actionHash, }; return await (0, mod_js_1.signTypedData)({ wallet, domain: { name: "Exchange", version: "1", chainId: 1337, // hyperliquid requires chainId to be 1337 verifyingContract: "0x0000000000000000000000000000000000000000", }, types: { Agent: [ { name: "source", type: "string" }, { name: "connectionId", type: "bytes32" }, ], }, primaryType: "Agent", message, }); } /** * Sign a user-signed action. * @example * ```ts * import { actionSorter, signUserSignedAction, userSignedActionEip712Types } from "@nktkas/hyperliquid/signing"; * * const privateKey = "0x..."; // `viem`, `ethers`, or private key directly` * * const action = actionSorter.approveAgent({ * type: "approveAgent", * signatureChainId: "0x66eee", * hyperliquidChain: "Mainnet", * agentAddress: "0x...", * agentName: "Agent", * nonce: Date.now(), * }); * * const signature = await signUserSignedAction({ * wallet: privateKey, * action, * types: userSignedActionEip712Types[action.type], * }); * * // Send the signed action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action, signature, nonce: action.nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` */ async function signUserSignedAction(args) { let { wallet, action, types } = args; if (action.type === "approveAgent" && !action.agentName) { // special case for `approveAgent` action = { ...action, agentName: "" }; // set to empty string instead of null } if ("payloadMultiSigUser" in action && "outerSigner" in action) { // special case for multi-sign payload types = structuredClone(types); // for safe mutation Object.values(types)[0].splice(// array mutation 1, // after `hyperliquidChain` 0, // do not remove any elements { name: "payloadMultiSigUser", type: "address" }, { name: "outerSigner", type: "address" }); } return await (0, mod_js_1.signTypedData)({ wallet, domain: { name: "HyperliquidSignTransaction", version: "1", chainId: parseInt(action.signatureChainId), verifyingContract: "0x0000000000000000000000000000000000000000", }, types, primaryType: Object.keys(types)[0], message: action, }); } /** * Sign a multi-signature action. * @example * ```ts * import { actionSorter, signL1Action, signMultiSigAction } from "@nktkas/hyperliquid/signing"; * import { privateKeyToAccount } from "viem/accounts"; * * const wallet = privateKeyToAccount("0x..."); // or ethers, private key directly * const multiSigUser = "0x..."; * * const action = actionSorter.scheduleCancel({ * type: "scheduleCancel", * time: Date.now() + 10000, * }); * const nonce = Date.now(); * * // Create the required number of signatures * const signatures = await Promise.all(["0x...", "0x..."].map(async (signerPrivKey) => { * return await signL1Action({ * wallet: signerPrivKey as `0x${string}`, * action: [multiSigUser.toLowerCase(), wallet.address.toLowerCase(), action], * nonce, * }); * })); * * // or user-signed action * // const signatures = await Promise.all(["0x...", "0x..."].map(async (signerPrivKey) => { * // return await signUserSignedAction({ * // wallet: signerPrivKey as `0x${string}`, * // action: { * // ...action, * // payloadMultiSigUser: multiSigUser, * // outerSigner: wallet.address, * // }, * // types: userSignedActionEip712Types[action.type], * // }); * // })); * * // Then use signatures in the multi-sig action * const multiSigAction = actionSorter.multiSig({ * type: "multiSig", * signatureChainId: "0x66eee", * signatures, * payload: { * multiSigUser, * outerSigner: wallet.address, * action, * }, * }); * const multiSigSignature = await signMultiSigAction({ wallet, action: multiSigAction, nonce }); * * // Send the multi-sig action to the Hyperliquid API * const response = await fetch("https://api.hyperliquid.xyz/exchange", { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ action: multiSigAction, signature: multiSigSignature, nonce }), // recommended to send the same formatted action * }); * const body = await response.json(); * ``` */ async function signMultiSigAction(args) { let { wallet, action, nonce, isTestnet = false, vaultAddress, expiresAfter, } = args; if ("type" in action) { action = structuredClone(action); // for safe mutation delete action.type; } const multiSigActionHash = createL1ActionHash({ action, nonce, vaultAddress, expiresAfter }); const message = { hyperliquidChain: isTestnet ? "Testnet" : "Mainnet", multiSigActionHash, nonce, }; return await (0, mod_js_1.signTypedData)({ wallet, domain: { name: "HyperliquidSignTransaction", version: "1", chainId: parseInt(action.signatureChainId), verifyingContract: "0x0000000000000000000000000000000000000000", }, types: _sorter_js_1.userSignedActionEip712Types.multiSig, primaryType: Object.keys(_sorter_js_1.userSignedActionEip712Types.multiSig)[0], message, }); }