#!/usr/bin/env npx tsx // Get market info from Hyperliquid import { getClient } from '../core/client.js'; import { formatUsd, parseArgs } from '../core/utils.js'; interface MarketDisplay { coin: string; assetId: number; markPx: number; oraclePx: number; prevDayPx: number; change24h: number; volume24h: number; openInterest: number; maxLeverage: number; szDecimals: number; } async function main() { const args = parseArgs(process.argv.slice(2)); const filterCoin = args.coin as string | undefined; const topN = parseInt(args.top as string) || 30; const sortBy = (args.sort as string) || 'volume'; // volume, oi, change const jsonOutput = args.json as boolean; const client = getClient(); const includeHip3 = args['include-hip3'] as boolean || args['hip3'] as boolean || (filterCoin?.includes(':') ?? false); try { const meta = await client.getMetaAndAssetCtxs(); const markets: MarketDisplay[] = []; for (let i = 0; i < meta.meta.universe.length; i++) { const asset = meta.meta.universe[i]; const ctx = meta.assetCtxs[i]; if (filterCoin && asset.name !== filterCoin) continue; const markPx = parseFloat(ctx.markPx); const oraclePx = parseFloat(ctx.oraclePx); const prevDayPx = parseFloat(ctx.prevDayPx); const volume24h = parseFloat(ctx.dayNtlVlm); const openInterest = parseFloat(ctx.openInterest); const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0; markets.push({ coin: asset.name, assetId: i, markPx, oraclePx, prevDayPx, change24h, volume24h, openInterest, maxLeverage: asset.maxLeverage, szDecimals: asset.szDecimals, }); } // Include HIP-3 markets if (client.isTestnet && includeHip3 && !(filterCoin?.includes(':'))) { console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use --coin dexName:COIN to view a specific HIP-3 asset.\n'); } if (includeHip3 || (filterCoin && filterCoin.includes(':'))) { // On testnet, load the specific dex on demand if user specified dex:COIN if (client.isTestnet && filterCoin?.includes(':')) { await client.loadSingleHip3Dex(filterCoin.split(':')[0]); } const allPerps = await client.getAllPerpMetas(); for (const dexData of allPerps) { if (!dexData.dexName) continue; for (let i = 0; i < dexData.meta.universe.length; i++) { const asset = dexData.meta.universe[i]; const ctx = dexData.assetCtxs[i]; if (!ctx) continue; // API returns names already prefixed (e.g., "xyz:CL") const coinName = asset.name; if (filterCoin && coinName !== filterCoin) continue; const markPx = parseFloat(ctx.markPx); const oraclePx = parseFloat(ctx.oraclePx); const prevDayPx = parseFloat(ctx.prevDayPx); const volume24h = parseFloat(ctx.dayNtlVlm); const openInterest = parseFloat(ctx.openInterest); const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0; let assetId = -1; try { assetId = client.getAssetIndex(coinName); } catch { /* not registered */ } markets.push({ coin: coinName, assetId, markPx, oraclePx, prevDayPx, change24h, volume24h, openInterest, maxLeverage: asset.maxLeverage, szDecimals: asset.szDecimals, }); } } } // Sort if (sortBy === 'volume') { markets.sort((a, b) => b.volume24h - a.volume24h); } else if (sortBy === 'oi') { markets.sort((a, b) => b.openInterest - a.openInterest); } else if (sortBy === 'change') { markets.sort((a, b) => Math.abs(b.change24h) - Math.abs(a.change24h)); } // Limit const displayData = filterCoin ? markets : markets.slice(0, topN); if (jsonOutput) { console.log(JSON.stringify(displayData, null, 2)); return; } console.log('Open Broker - Markets'); console.log('=====================\n'); if (displayData.length === 0) { console.log(filterCoin ? `No data for ${filterCoin}` : 'No market data available'); return; } if (filterCoin && displayData.length === 1) { // Detailed view for single coin const m = displayData[0]; console.log(`${m.coin}`); console.log('─'.repeat(40)); console.log(`Mark Price: ${formatUsd(m.markPx)}`); console.log(`Oracle Price: ${formatUsd(m.oraclePx)}`); console.log(`24h Change: ${formatChange(m.change24h)}`); console.log(`24h Volume: ${formatVolume(m.volume24h)}`); console.log(`Open Interest: ${formatVolume(m.openInterest * m.markPx)} (${formatOI(m.openInterest)} contracts)`); console.log(`Max Leverage: ${m.maxLeverage}x`); console.log(`Size Decimals: ${m.szDecimals}`); console.log(`Min Size: ${(10 ** -m.szDecimals).toFixed(m.szDecimals)}`); return; } // Table view console.log('Coin | Mark | 24h Chg | 24h Volume | OI | Lev'); console.log('---------|--------------|------------|---------------|---------------|-----'); for (const m of displayData) { console.log( `${m.coin.padEnd(8)} | ${formatUsd(m.markPx).padStart(12)} | ` + `${formatChange(m.change24h).padStart(10)} | ` + `${formatVolume(m.volume24h).padStart(13)} | ` + `${formatVolume(m.openInterest * m.markPx).padStart(13)} | ` + `${m.maxLeverage}x` ); } // Top movers if (!filterCoin) { const gainers = [...markets].sort((a, b) => b.change24h - a.change24h).slice(0, 3); const losers = [...markets].sort((a, b) => a.change24h - b.change24h).slice(0, 3); console.log('\nšŸ“ˆ Top Gainers:'); for (const m of gainers) { if (m.change24h <= 0) break; console.log(` ${m.coin}: ${formatChange(m.change24h)}`); } console.log('\nšŸ“‰ Top Losers:'); for (const m of losers) { if (m.change24h >= 0) break; console.log(` ${m.coin}: ${formatChange(m.change24h)}`); } } } catch (error) { console.error('Error fetching market data:', error); process.exit(1); } } function formatChange(change: number): string { const sign = change >= 0 ? '+' : ''; return `${sign}${(change * 100).toFixed(2)}%`; } function formatVolume(volume: number): string { if (volume >= 1_000_000_000) return `$${(volume / 1_000_000_000).toFixed(2)}B`; if (volume >= 1_000_000) return `$${(volume / 1_000_000).toFixed(2)}M`; if (volume >= 1_000) return `$${(volume / 1_000).toFixed(1)}K`; return `$${volume.toFixed(0)}`; } function formatOI(oi: number): string { if (oi >= 1_000_000) return `${(oi / 1_000_000).toFixed(2)}M`; if (oi >= 1_000) return `${(oi / 1_000).toFixed(1)}K`; return oi.toFixed(0); } main();