@deeeed/hyperliquid-node20
Version:
Unofficial Hyperliquid API SDK for all major JS runtimes, written in TypeScript. Fork with Node.js 20.18.0+ compatibility.
151 lines (150 loc) • 6.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiSignClient = void 0;
const sha3_1 = require("@noble/hashes/sha3");
const secp256k1_1 = require("@noble/secp256k1");
const mod_js_1 = require("../signing/mod.js");
const exchange_js_1 = require("./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.
*/
class MultiSignClient extends exchange_js_1.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 = mod_js_1.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(mod_js_1.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 (0, mod_js_1.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 (0, mod_js_1.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 ((0, mod_js_1.isValidPrivateKey)(wallet)) {
return privateKeyToAddress(wallet);
}
else if ((0, mod_js_1.isAbstractViemWalletClient)(wallet)) {
return wallet.address;
}
else if ((0, mod_js_1.isAbstractEthersSigner)(wallet) || (0, mod_js_1.isAbstractEthersV5Signer)(wallet)) {
return await wallet.getAddress();
}
else if ((0, mod_js_1.isAbstractWindowEthereum)(wallet)) {
return await getWindowEthereumAddress(wallet);
}
else {
throw new Error("Unsupported wallet for getting address");
}
}
}
exports.MultiSignClient = MultiSignClient;
function privateKeyToAddress(privateKey) {
const cleanPrivKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
const publicKey = (0, secp256k1_1.getPublicKey)(cleanPrivKey, false);
const publicKeyWithoutPrefix = publicKey.slice(1);
const hash = (0, sha3_1.keccak_256)(publicKeyWithoutPrefix);
const addressBytes = hash.slice(-20);
const address = secp256k1_1.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];
}