refactor: HL EVM signing via sonirico/go-hyperliquid SDK
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user