#!/usr/bin/env npx tsx // Execute a limit order on Hyperliquid import { getClient } from '../core/client.js'; import { formatUsd, parseArgs } from '../core/utils.js'; function printUsage() { console.log(` Open Broker - Limit Order ========================= Place a limit order with specified price. Usage: npx tsx scripts/operations/limit-order.ts --coin --side --size --price Options: --coin Asset to trade (e.g., ETH, BTC) --side Order side: buy or sell --size Order size in base asset --price Limit price --tif Time in force: GTC, IOC, or ALO (default: GTC) GTC = Good Till Cancel (rests on book) IOC = Immediate Or Cancel (fills or cancels) ALO = Add Liquidity Only (post-only, maker only) --leverage Set leverage (e.g., 10 for 10x). Cross for main perps, isolated for HIP-3 --reduce Reduce-only order (default: false) --dry Dry run - show order details without executing Examples: npx tsx scripts/operations/limit-order.ts --coin ETH --side buy --size 1 --price 3000 npx tsx scripts/operations/limit-order.ts --coin BTC --side sell --size 0.1 --price 100000 --tif ALO npx tsx scripts/operations/limit-order.ts --coin SOL --side buy --size 10 --price 150 --reduce `); } async function main() { const args = parseArgs(process.argv.slice(2)); // Parse and validate arguments const coin = args.coin as string; const side = args.side as string; const size = parseFloat(args.size as string); const price = parseFloat(args.price as string); const tifArg = (args.tif as string)?.toUpperCase() || 'GTC'; const leverage = args.leverage ? parseInt(args.leverage as string) : undefined; const reduceOnly = args.reduce as boolean; const dryRun = args.dry as boolean; if (!coin || !side || isNaN(size) || isNaN(price)) { printUsage(); process.exit(1); } if (side !== 'buy' && side !== 'sell') { console.error('Error: --side must be "buy" or "sell"'); process.exit(1); } if (size <= 0) { console.error('Error: --size must be positive'); process.exit(1); } if (price <= 0) { console.error('Error: --price must be positive'); process.exit(1); } // Map uppercase CLI input to Pascal case for SDK const tifMap: Record = { 'GTC': 'Gtc', 'IOC': 'Ioc', 'ALO': 'Alo' }; const tif = tifMap[tifArg]; if (!tif) { console.error(`Error: --tif must be one of: GTC, IOC, ALO`); process.exit(1); } const isBuy = side === 'buy'; const client = getClient(); console.log('Open Broker - Limit Order'); console.log('=========================\n'); try { // Get current price for reference const mids = await client.getAllMids(); const midPrice = parseFloat(mids[coin]); if (!midPrice) { console.error(`Error: No market data for ${coin}`); process.exit(1); } const notional = price * size; const distanceFromMid = ((price - midPrice) / midPrice) * 100; console.log('Order Details'); console.log('-------------'); console.log(`Coin: ${coin}`); console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`); console.log(`Size: ${size}`); console.log(`Limit Price: ${formatUsd(price)}`); console.log(`Current Mid: ${formatUsd(midPrice)}`); console.log(`Distance: ${distanceFromMid >= 0 ? '+' : ''}${distanceFromMid.toFixed(2)}% from mid`); console.log(`Notional: ${formatUsd(notional)}`); console.log(`Time in Force: ${tif}`); console.log(`Reduce Only: ${reduceOnly ? 'Yes' : 'No'}`); if (leverage) console.log(`Leverage: ${leverage}x`); console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`); // Warning if order would be aggressively priced if ((isBuy && price > midPrice) || (!isBuy && price < midPrice)) { console.log(`\nāš ļø Order is priced aggressively - may fill immediately as taker`); } if (dryRun) { console.log('\nšŸ” Dry run - order not submitted'); return; } console.log('\nSubmitting...'); const response = await client.limitOrder(coin, isBuy, size, price, tif, reduceOnly, leverage); console.log('\nResult'); console.log('------'); if (response.status === 'ok' && response.response && typeof response.response !== 'string') { const statuses = response.response.data.statuses; for (const status of statuses) { if (status.filled) { const fillSz = parseFloat(status.filled.totalSz); const avgPx = parseFloat(status.filled.avgPx); const fillNotional = fillSz * avgPx; console.log(`āœ… Filled`); console.log(` Order ID: ${status.filled.oid}`); console.log(` Size: ${fillSz}`); console.log(` Avg Price: ${formatUsd(avgPx)}`); console.log(` Notional: ${formatUsd(fillNotional)}`); } else if (status.resting) { console.log(`āœ… Order placed`); console.log(` Order ID: ${status.resting.oid}`); console.log(` Status: Resting on book`); } else if (status.error) { console.log(`āŒ Error: ${status.error}`); } } } else { console.log(`āŒ Error: ${response.error || 'Unknown error'}`); } } catch (error) { console.error('Error submitting order:', error); process.exit(1); } } main();