UNPKG

@deeeed/hyperliquid-node20

Version:

Unofficial Hyperliquid API SDK for all major JS runtimes, written in TypeScript. Fork with Node.js 20.18.0+ compatibility.

147 lines (146 loc) 5.92 kB
import { keccak_256 } from "@noble/hashes/sha3"; import { etc, getPublicKey } from "@noble/secp256k1"; import { actionSorter, isAbstractEthersSigner, isAbstractEthersV5Signer, isAbstractViemWalletClient, isAbstractWindowEthereum, isValidPrivateKey, signL1Action, signUserSignedAction, userSignedActionEip712Types, } from "../signing/mod.js"; import { ExchangeClient, } from "./exchange.js"; /** * Multi-signature exchange client for interacting with the Hyperliquid API. * @typeParam T The transport used to connect to the Hyperliquid API. * @typeParam S Array of wallets where the first wallet acts as the leader. */ export class MultiSignClient extends ExchangeClient { multiSignAddress; signers; /** * Initialises a new multi-signature client instance. * @param args - The parameters for the multi-signature client. * * @example * ```ts * import * as hl from "@nktkas/hyperliquid"; * * const multiSignAddress = "0x..."; * const signers = [ * "0x...", // Private key; or any other wallet libraries * ] as const; * * const transport = new hl.HttpTransport(); * const multiSignClient = new hl.MultiSignClient({ transport, multiSignAddress, signers }); * ``` */ constructor(args) { super({ ...args, wallet: args.signers[0] }); this.multiSignAddress = args.multiSignAddress; this.signers = args.signers; Object.defineProperty(this, "wallet", { get() { return this.signers[0]; }, set(value) { this.signers[0] = value; }, enumerable: true, configurable: true, }); } async _executeAction(args, signal) { const { action, vaultAddress, expiresAfter, multiSigNonce } = args; if (action.type === "multiSig") { // Multi-signature action return await super._executeAction({ action, vaultAddress, expiresAfter, multiSigNonce, }, signal); } // Sign an action // deno-lint-ignore no-explicit-any const sortedAction = actionSorter[action.type](action); // TypeScript cannot infer a type from a dynamic function call let nonce; if ("signatureChainId" in sortedAction) { // User-signed action nonce = "nonce" in sortedAction ? sortedAction.nonce : sortedAction.time; } else { // L1 action nonce = await this.nonceManager(); } const outerSigner = await this._getWalletAddress(this.signers[0]); let signatures; if ("signatureChainId" in sortedAction) { // User-signed action signatures = await Promise.all(this.signers.map(async (signer) => { const types = structuredClone(userSignedActionEip712Types[sortedAction.type]); // 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 signUserSignedAction({ wallet: signer, action: { payloadMultiSigUser: this.multiSignAddress, outerSigner, ...sortedAction, }, types, }); })); if ("agentName" in sortedAction && sortedAction.agentName === "") sortedAction.agentName = null; } else { // L1 action signatures = await Promise.all(this.signers.map(async (signer) => { return await signL1Action({ wallet: signer, action: [this.multiSignAddress.toLowerCase(), outerSigner.toLowerCase(), sortedAction], nonce, isTestnet: this.isTestnet, vaultAddress, expiresAfter, }); })); } // Send a multi-signature action return await super.multiSig({ signatures, payload: { multiSigUser: this.multiSignAddress, outerSigner, action: sortedAction, }, nonce, vaultAddress, expiresAfter, }, signal); } /** Extracts the wallet address from different wallet types. */ async _getWalletAddress(wallet) { if (isValidPrivateKey(wallet)) { return privateKeyToAddress(wallet); } else if (isAbstractViemWalletClient(wallet)) { return wallet.address; } else if (isAbstractEthersSigner(wallet) || isAbstractEthersV5Signer(wallet)) { return await wallet.getAddress(); } else if (isAbstractWindowEthereum(wallet)) { return await getWindowEthereumAddress(wallet); } else { throw new Error("Unsupported wallet for getting address"); } } } function privateKeyToAddress(privateKey) { const cleanPrivKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey; const publicKey = getPublicKey(cleanPrivKey, false); const publicKeyWithoutPrefix = publicKey.slice(1); const hash = keccak_256(publicKeyWithoutPrefix); const addressBytes = hash.slice(-20); const address = etc.bytesToHex(addressBytes); return `0x${address}`; } async function getWindowEthereumAddress(ethereum) { const accounts = await ethereum.request({ method: "eth_requestAccounts", params: [] }); if (!Array.isArray(accounts) || accounts.length === 0) { throw new Error("No Ethereum accounts available"); } return accounts[0]; }