#!/usr/bin/env npx tsx // Execute a market order on Hyperliquid import { getClient } from '../core/client.js'; import { formatUsd, parseArgs, checkBuilderFeeApproval } from '../core/utils.js'; function printUsage() { console.log(` Open Broker - Market Order ========================== Execute a market order with slippage protection. Usage: npx tsx scripts/operations/market-order.ts --coin --side --size Options: --coin Asset to trade (e.g., ETH, BTC) --side Order side: buy or sell --size Order size in base asset --slippage Slippage tolerance in bps (default: from config, usually 50 = 0.5%) --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 --verbose Show full API request/response for debugging Environment: HYPERLIQUID_PRIVATE_KEY Your wallet private key (0x...) HYPERLIQUID_NETWORK "mainnet" or "testnet" (default: mainnet) VERBOSE=1 Enable verbose logging Examples: npx tsx scripts/operations/market-order.ts --coin ETH --side buy --size 0.1 npx tsx scripts/operations/market-order.ts --coin BTC --side sell --size 0.01 --slippage 100 npx tsx scripts/operations/market-order.ts --coin SOL --side buy --size 10 --dry npx tsx scripts/operations/market-order.ts --coin ETH --side buy --size 0.1 --verbose `); } 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 slippage = args.slippage ? parseInt(args.slippage as string) : undefined; 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)) { 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); } const isBuy = side === 'buy'; const client = getClient(); // Enable verbose mode if requested if (args.verbose) { client.verbose = true; } console.log('Open Broker - Market Order'); console.log('==========================\n'); // Check builder fee approval (warning only, don't block) await checkBuilderFeeApproval(client); try { // Get current price const mids = await client.getAllMids(); const midPrice = parseFloat(mids[coin]); if (!midPrice) { console.error(`Error: No market data for ${coin}`); process.exit(1); } // Calculate order details const slippageBps = slippage ?? 50; const slippageMultiplier = slippageBps / 10000; const limitPrice = isBuy ? midPrice * (1 + slippageMultiplier) : midPrice * (1 - slippageMultiplier); const notional = midPrice * size; console.log('Order Details'); console.log('-------------'); console.log(`Coin: ${coin}`); console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`); console.log(`Size: ${size}`); console.log(`Mid Price: ${formatUsd(midPrice)}`); console.log(`Limit Price: ${formatUsd(limitPrice)} (${slippageBps} bps slippage)`); console.log(`Notional: ~${formatUsd(notional)}`); console.log(`Reduce Only: ${reduceOnly ? 'Yes' : 'No'}`); if (leverage) console.log(`Leverage: ${leverage}x`); console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`); if (dryRun) { console.log('\nšŸ” Dry run - order not submitted'); return; } console.log('\nExecuting...'); const response = await client.marketOrder(coin, isBuy, size, slippage, leverage); console.log('\nResult'); console.log('------'); // Log full response for debugging if (args.verbose || process.env.VERBOSE) { console.log('\nFull Response:'); console.log(JSON.stringify(response, null, 2)); } if (response.status === 'ok' && response.response && typeof response.response === 'object') { 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; const slippageActual = isBuy ? (avgPx - midPrice) / midPrice : (midPrice - avgPx) / midPrice; 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)}`); console.log(` Slippage: ${(slippageActual * 10000).toFixed(1)} bps`); } else if (status.resting) { console.log(`ā³ Resting (partial fill)`); console.log(` Order ID: ${status.resting.oid}`); } else if (status.error) { console.log(`āŒ Error: ${status.error}`); } else { // Unknown status - show the whole thing console.log(`āš ļø Unknown status:`); console.log(JSON.stringify(status, null, 2)); } } } else if (response.status === 'err') { console.log(`āŒ API Error: ${response.response || JSON.stringify(response)}`); } else { console.log(`āŒ Unexpected response:`); console.log(JSON.stringify(response, null, 2)); } } catch (error) { console.error('Error executing order:', error); process.exit(1); } } main();