Repository Public
1// MessagePack encoder for Hyperliquid L1 actions — only what an action can contain: nil, booleans, integers, strings, arrays and2// maps with string keys, written in INSERTION ORDER (the venue hashes the bytes, so key order is part of the signature).3// Written here instead of adding `@msgpack/msgpack`: ~60 lines, no new dependency on the signing path, and the refusal of floats,4// undefined and non-string keys is exactly what is wanted for a signed payload. Byte-for-byte equal to Python `msgpack.packb`5// (the official SDK) for these types — proven by the SDK signing vectors in test/hyperliquid/signing.test.ts.6// Format: https://github.com/msgpack/msgpack/blob/master/spec.md78export type MsgpackValue = null | boolean | number | string | MsgpackValue[] | { [key: string]: MsgpackValue };910const utf8 = new TextEncoder();1112function header(out: number[], n: number, fix: [base: number, max: number] | null, b8: number | null, b16: number, b32: number): void {13 if (fix && n <= fix[1]) out.push(fix[0] | n);14 else if (b8 !== null && n <= 0xff) out.push(b8, n);15 else if (n <= 0xffff) out.push(b16, n >>> 8, n & 0xff);16 else if (n <= 0xffffffff) out.push(b32, (n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff);17 else throw new Error('msgpack: container too large');18}1920function be(out: number[], v: bigint, bytes: number): void {21 for (let i = bytes - 1; i >= 0; i--) out.push(Number((v >> BigInt(8 * i)) & 0xffn));22}2324function int(out: number[], n: number): void {25 if (!Number.isSafeInteger(n)) throw new Error('msgpack: only safe integers are allowed (no floats in a signed action)');26 if (n >= 0) {27 if (n <= 0x7f) out.push(n);28 else if (n <= 0xff) out.push(0xcc, n);29 else if (n <= 0xffff) { out.push(0xcd); be(out, BigInt(n), 2); }30 else if (n <= 0xffffffff) { out.push(0xce); be(out, BigInt(n), 4); }31 else { out.push(0xcf); be(out, BigInt(n), 8); }32 } else if (n >= -32) out.push(0x100 + n);33 else if (n >= -0x80) out.push(0xd0, 0x100 + n);34 else if (n >= -0x8000) { out.push(0xd1); be(out, BigInt.asUintN(16, BigInt(n)), 2); }35 else if (n >= -0x80000000) { out.push(0xd2); be(out, BigInt.asUintN(32, BigInt(n)), 4); }36 else { out.push(0xd3); be(out, BigInt.asUintN(64, BigInt(n)), 8); }37}3839function write(out: number[], v: MsgpackValue, depth: number): void {40 if (depth > 16) throw new Error('msgpack: nesting too deep');41 if (v === null) { out.push(0xc0); return; }42 switch (typeof v) {43 case 'boolean': out.push(v ? 0xc3 : 0xc2); return;44 case 'number': int(out, v); return;45 case 'string': {46 const b = utf8.encode(v);47 header(out, b.length, [0xa0, 31], 0xd9, 0xda, 0xdb);48 for (const x of b) out.push(x);49 return;50 }51 case 'object': {52 if (Array.isArray(v)) {53 header(out, v.length, [0x90, 15], null, 0xdc, 0xdd);54 for (const x of v) write(out, x, depth + 1);55 return;56 }57 if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null) throw new Error('msgpack: plain objects only');58 const keys = Object.keys(v); // insertion order for string keys; integer-like keys would be reordered by JS, refuse them59 for (const k of keys) if (/^(0|[1-9]\d*)$/.test(k)) throw new Error('msgpack: integer-like map keys would lose their order');60 header(out, keys.length, [0x80, 15], null, 0xde, 0xdf);61 for (const k of keys) { write(out, k, depth + 1); write(out, v[k] as MsgpackValue, depth + 1); }62 return;63 }64 default: throw new Error(`msgpack: unsupported value of type ${typeof v}`);65 }66}6768export function msgpackEncode(v: MsgpackValue): Uint8Array {69 const out: number[] = [];70 write(out, v, 0);71 return Uint8Array.from(out);72}73