const https = require('https'); // Read args: [action_json, nonce, private_key, address, isMainnet] const [actionJson, nonceStr, privKey, address, isMainnetStr] = process.argv.slice(2); if (!actionJson || !nonceStr || !privKey) { console.log(JSON.stringify({ error: "Usage: node hl_sign.js '' [address] [isMainnet]" })); process.exit(1); } const action = JSON.parse(actionJson); const nonce = parseInt(nonceStr); const isMainnet = isMainnetStr === 'true'; // ---- Build the EIP-712 typed data ---- const { ethers } = require('ethers'); async function main() { const wallet = new ethers.Wallet(privKey); const apiAddr = wallet.address; // 1. Normalize action (remove trailing zeros) let normalizedAction = JSON.parse(JSON.stringify(action)); function normalize(obj) { if (!obj || typeof obj !== 'object') return; if (Array.isArray(obj)) { for (const item of obj) normalize(item); } else { for (const key of Object.keys(obj)) { if ((key === 'p' || key === 's') && typeof obj[key] === 'string') { obj[key] = obj[key].replace(/\.?0+$/, '').replace(/^-0$/, '0'); } else { normalize(obj[key]); } } } } normalize(normalizedAction); // 2. msgpack encode the action const { encode } = require('@msgpack/msgpack'); const msgBytes = encode(normalizedAction); // 3. Build hash: msgpack + nonce(8 bytes big endian) + flag(1 byte) const totalLen = msgBytes.length + 8 + 1; const data = new Uint8Array(totalLen); data.set(msgBytes, 0); const view = new DataView(data.buffer); view.setBigUint64(msgBytes.length, BigInt(nonce), false); view.setUint8(msgBytes.length + 8, 0); // no vault address const hash = ethers.keccak256(data); // 4. EIP-712 signing const source = isMainnet ? 'a' : 'b'; const domain = { name: 'Exchange', version: '1', chainId: 1337, verifyingContract: '0x0000000000000000000000000000000000000000', }; const types = { Agent: [ { name: 'source', type: 'string' }, { name: 'connectionId', type: 'bytes32' }, ], }; const message = { source, connectionId: hash }; const signature = await wallet.signTypedData(domain, types, message); const sig = ethers.Signature.from(signature); // 5. Build result const result = { action: action, nonce: nonce, signature: { r: sig.r, s: sig.s, v: sig.v, }, }; // If address is provided and != wallet.address, it's the main account // The SDK automatically handles this via the accountAddress constructor param // For order placement via exchange API, we just send the signature console.log(JSON.stringify(result)); } main().catch(err => { console.log(JSON.stringify({ error: err.message })); process.exit(1); });