const https = require('https'); const { Wallet, keccak256, Signature, verifyTypedData } = require('ethers'); const { encode } = require('@msgpack/msgpack'); const host = "api.hyperliquid-testnet.xyz"; const privKey = "0xcb4e58b6c0921fd4aa8e49cd8a9f06b98b4e6b9cdf7c60c63ba1416856bb0b2a"; const wallet = new Wallet(privKey); console.log("Wallet address:", wallet.address); async function api(method, path, payload) { return new Promise((resolve, reject) => { const data = JSON.stringify(payload); const req = https.request({ hostname: host, path, method, headers: { 'Content-Type': 'application/json' } }, res => { let body = ''; res.on('data', c => body += c); res.on('end', () => { try { resolve(JSON.parse(body)); } catch(e) { resolve(body); } }); }); req.on('error', reject); req.write(data); req.end(); }); } function floatToWire(x) { const rounded = x.toFixed(8); let normalized = rounded.replace(/\.?0+$/, ''); if (normalized === '-0') normalized = '0'; return normalized; } async function main() { // Get meta const meta = await api('POST', '/info', { type: 'meta' }); const universe = meta.universe; const btc = universe.find(u => u.name === 'BTC'); const btcIdx = universe.indexOf(btc); console.log(`BTC index: ${btcIdx}`); const mids = await api('POST', '/info', { type: 'allMids' }); const mid = parseFloat(mids['BTC']); const limitPx = mid * 2; const sz = 0.001; // Build exactly like floatToWire does const pxStr = floatToWire(limitPx); const szStr = floatToWire(sz); console.log(`pxStr: ${pxStr}, szStr: ${szStr}`); // Build order wire EXACTLY like SDK const orderWire = { a: btcIdx, b: true, p: pxStr, s: szStr, r: false, t: { limit: { tif: 'Ioc' } } }; console.log("Order wire:", JSON.stringify(orderWire)); // Sign step by step const nonce = Date.now() * 1000000 + Math.floor(Math.random() * 1000000); // Normalize trailing zeros function normalize(obj) { if (!obj || typeof obj !== 'object') return; if (Array.isArray(obj)) { for (const i of obj) normalize(i); } else { for (const k of Object.keys(obj)) { if ((k === 'p' || k === 's') && typeof obj[k] === 'string') obj[k] = obj[k].replace(/\.?0+$/, '').replace(/^-0$/, '0'); else normalize(obj[k]); }} } const action = { type: 'order', orders: [JSON.parse(JSON.stringify(orderWire))], grouping: 'na' }; normalize(action); // Apply to full action with orders array console.log("Normalized action:", JSON.stringify(action)); const msgBytes = encode(action); console.log(`Msgpack bytes: ${Buffer.from(msgBytes).toString('hex')}`); const buf = new Uint8Array(msgBytes.length + 8 + 1); buf.set(msgBytes, 0); new DataView(buf.buffer).setBigUint64(msgBytes.length, BigInt(nonce), false); buf[msgBytes.length + 8] = 0; const hash = keccak256(buf); console.log(`Hash: ${hash}`); // Sign and verify const sig = Signature.from(await wallet.signTypedData( { name: 'Exchange', version: '1', chainId: 1337, verifyingContract: '0x0000000000000000000000000000000000000000' }, { Agent: [{ name: 'source', type: 'string' }, { name: 'connectionId', type: 'bytes32' }] }, { source: 'b', connectionId: hash } )); // Verify the signature recovers to our wallet const domain = { name: 'Exchange', version: '1', chainId: 1337, verifyingContract: '0x0000000000000000000000000000000000000000' }; const types = { Agent: [{ name: 'source', type: 'string' }, { name: 'connectionId', type: 'bytes32' }] }; const message = { source: 'b', connectionId: hash }; const recoveredAddr = verifyTypedData(domain, types, message, sig); console.log(`Recovered address: ${recoveredAddr}`); console.log(`Wallet address: ${wallet.address.toLowerCase()}`); console.log(`Match: ${recoveredAddr.toLowerCase() === wallet.address.toLowerCase()}`); // Send order const payload = { action, nonce, signature: { r: sig.r, s: sig.s, v: sig.v } }; console.log('\nSending order...'); const result = await api('POST', '/exchange', payload); console.log(JSON.stringify(result, null, 2)); } main().catch(console.error);