@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
JavaScript
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];
}