openbroker 1.0.80 → 1.0.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,20 @@ function loadEnvFile(): string | null {
42
42
  const verbose = process.env.VERBOSE === '1' || process.env.VERBOSE === 'true';
43
43
  process.env.DOTENV_CONFIG_QUIET = 'true';
44
44
 
45
+ // Explicit config path via -c / --config (highest priority — overrides shell env vars)
46
+ const explicitPath = process.env.OPENBROKER_CONFIG;
47
+ if (explicitPath) {
48
+ if (existsSync(explicitPath)) {
49
+ loadDotenv({ path: explicitPath, override: true });
50
+ if (verbose) {
51
+ console.log(`[DEBUG] Loaded config from -c flag: ${explicitPath}`);
52
+ }
53
+ return explicitPath;
54
+ }
55
+ console.error(`Config file not found: ${explicitPath}`);
56
+ process.exit(1);
57
+ }
58
+
45
59
  // Check locations in order of priority
46
60
  const locations = [
47
61
  { path: LOCAL_ENV_PATH, name: 'local (.env)' },
@@ -98,7 +112,7 @@ export function loadConfig(): OpenBrokerConfig {
98
112
  // Show warning once
99
113
  if (!readOnlyWarningShown) {
100
114
  readOnlyWarningShown = true;
101
- console.log('\x1b[33m⚠️ Not configured for trading. Run "openbroker setup" to enable trades.\x1b[0m\n');
115
+ console.error('\x1b[33m⚠️ Not configured for trading. Run "openbroker setup" to enable trades.\x1b[0m\n');
102
116
  }
103
117
  }
104
118
 
@@ -124,6 +138,10 @@ export function loadConfig(): OpenBrokerConfig {
124
138
  // Determine if this is an API wallet setup
125
139
  const isApiWallet = accountAddress !== undefined && accountAddress !== walletAddress;
126
140
 
141
+ // Vault address — only for vault trading (ERC4626 contracts / native vaults via CoreWriter).
142
+ // Standard API wallets (approved via approveAgent) do NOT need this.
143
+ const vaultAddress = process.env.HYPERLIQUID_VAULT_ADDRESS?.toLowerCase();
144
+
127
145
  return {
128
146
  baseUrl,
129
147
  privateKey: privateKey as `0x${string}`,
@@ -134,6 +152,7 @@ export function loadConfig(): OpenBrokerConfig {
134
152
  builderAddress,
135
153
  builderFee,
136
154
  slippageBps,
155
+ vaultAddress,
137
156
  };
138
157
  }
139
158
 
@@ -12,6 +12,7 @@ export interface OpenBrokerConfig {
12
12
  builderAddress: string;
13
13
  builderFee: number; // tenths of bps (10 = 1 bps)
14
14
  slippageBps: number;
15
+ vaultAddress?: string; // Explicit vault address for vault trading (ERC4626 / native vaults via CoreWriter)
15
16
  }
16
17
 
17
18
  // ============ Builder ============
@@ -155,8 +155,11 @@ export function generateCloid(): string {
155
155
  * Returns true if approved, false if not
156
156
  */
157
157
  export async function checkBuilderFeeApproval(
158
- client: { getMaxBuilderFee: () => Promise<string | null>; builderAddress: string }
158
+ client: { getMaxBuilderFee: () => Promise<string | null>; builderAddress: string; isTestnet: boolean }
159
159
  ): Promise<boolean> {
160
+ // Skip builder fee check on testnet — builder may not have balance
161
+ if (client.isTestnet) return true;
162
+
160
163
  const approval = await client.getMaxBuilderFee();
161
164
  if (!approval) {
162
165
  console.log('⚠️ Builder fee not approved!');
@@ -123,15 +123,19 @@ async function main() {
123
123
  console.log(`Account Mode: ${modeLabel[accountMode] ?? accountMode}`);
124
124
 
125
125
  if (!isOtherAccount) {
126
- // Check builder fee approval
127
- const builderApproval = await client.getMaxBuilderFee();
128
- console.log(`Builder Address: ${client.builderAddress}`);
129
- console.log(`Builder Fee: ${client.builderFeeBps} bps`);
130
- if (builderApproval) {
131
- console.log(`Builder Approved: ✅ Yes (max: ${builderApproval})`);
126
+ // Check builder fee approval (skip on testnet)
127
+ if (client.isTestnet) {
128
+ console.log(`Builder Fee: skipped (testnet)`);
132
129
  } else {
133
- console.log(`Builder Approved: No`);
134
- console.log(`\n⚠️ Run: npx tsx scripts/setup/approve-builder.ts`);
130
+ const builderApproval = await client.getMaxBuilderFee();
131
+ console.log(`Builder Address: ${client.builderAddress}`);
132
+ console.log(`Builder Fee: ${client.builderFeeBps} bps`);
133
+ if (builderApproval) {
134
+ console.log(`Builder Approved: ✅ Yes (max: ${builderApproval})`);
135
+ } else {
136
+ console.log(`Builder Approved: ❌ No`);
137
+ console.log(`\n⚠️ Run: npx tsx scripts/setup/approve-builder.ts`);
138
+ }
135
139
  }
136
140
 
137
141
  // Warn if API wallet setup looks misconfigured
@@ -7,6 +7,7 @@ interface Args {
7
7
  type?: 'perp' | 'spot' | 'hip3' | 'all';
8
8
  top?: number;
9
9
  verbose?: boolean;
10
+ json?: boolean;
10
11
  }
11
12
 
12
13
  function parseArgs(): Args {
@@ -23,6 +24,8 @@ function parseArgs(): Args {
23
24
  args.top = parseInt(process.argv[++i], 10);
24
25
  } else if (arg === '--verbose') {
25
26
  args.verbose = true;
27
+ } else if (arg === '--json') {
28
+ args.json = true;
26
29
  } else if (arg === '--help' || arg === '-h') {
27
30
  console.log(`
28
31
  All Markets - View all available markets on Hyperliquid
@@ -32,6 +35,7 @@ Usage: npx tsx scripts/info/all-markets.ts [options]
32
35
  Options:
33
36
  --type <type> Market type: perp, spot, hip3, or all (default: all)
34
37
  --top <n> Show only top N markets by volume
38
+ --json Output as JSON (machine-readable)
35
39
  --verbose Show detailed output
36
40
  --help Show this help
37
41
 
@@ -41,6 +45,7 @@ Examples:
41
45
  npx tsx scripts/info/all-markets.ts --type hip3 # Show only HIP-3 perps
42
46
  npx tsx scripts/info/all-markets.ts --type spot # Show only spot markets
43
47
  npx tsx scripts/info/all-markets.ts --top 20 # Show top 20 by volume
48
+ npx tsx scripts/info/all-markets.ts --json # JSON output
44
49
  `);
45
50
  process.exit(0);
46
51
  }
@@ -71,22 +76,27 @@ function formatFunding(rate: string): string {
71
76
  return `${sign}${annualized.toFixed(2)}%`;
72
77
  }
73
78
 
79
+ interface MarketRow {
80
+ type: 'perp' | 'spot' | 'hip3';
81
+ provider: string;
82
+ coin: string;
83
+ assetId: number;
84
+ price: string;
85
+ volume24h: number;
86
+ funding?: string;
87
+ maxLeverage?: number;
88
+ }
89
+
74
90
  async function main() {
75
91
  const args = parseArgs();
76
92
  const client = getClient();
77
93
  client.verbose = args.verbose ?? false;
78
94
 
79
- console.log('Fetching market data...\n');
95
+ if (!args.json) {
96
+ console.log('Fetching market data...\n');
97
+ }
80
98
 
81
- const allMarkets: Array<{
82
- type: 'perp' | 'spot' | 'hip3';
83
- provider: string;
84
- coin: string;
85
- price: string;
86
- volume24h: number;
87
- funding?: string;
88
- maxLeverage?: number;
89
- }> = [];
99
+ const allMarkets: MarketRow[] = [];
90
100
 
91
101
  // Fetch main perps
92
102
  if (args.type === 'all' || args.type === 'perp') {
@@ -98,6 +108,7 @@ async function main() {
98
108
  type: 'perp',
99
109
  provider: 'Hyperliquid',
100
110
  coin: asset.name,
111
+ assetId: i,
101
112
  price: ctx.markPx,
102
113
  volume24h: parseFloat(ctx.dayNtlVlm),
103
114
  funding: ctx.funding,
@@ -108,6 +119,9 @@ async function main() {
108
119
 
109
120
  // Fetch HIP-3 perps
110
121
  if (args.type === 'all' || args.type === 'hip3') {
122
+ if (client.isTestnet && !args.json) {
123
+ console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use "dexName:COIN" syntax to load a specific dex.\n');
124
+ }
111
125
  try {
112
126
  const allPerpMetas = await client.getAllPerpMetas();
113
127
  // Skip index 0 (main dex), process HIP-3 dexs
@@ -120,10 +134,13 @@ async function main() {
120
134
  const ctx = dexData.assetCtxs[i];
121
135
  if (!asset || !ctx) continue;
122
136
 
137
+ let assetId = -1;
138
+ try { assetId = client.getAssetIndex(asset.name); } catch { /* not registered */ }
123
139
  allMarkets.push({
124
140
  type: 'hip3',
125
141
  provider: dexData.dexName || `HIP-3 DEX ${dexIdx}`,
126
142
  coin: asset.name,
143
+ assetId,
127
144
  price: ctx.markPx,
128
145
  volume24h: parseFloat(ctx.dayNtlVlm || '0'),
129
146
  funding: ctx.funding,
@@ -140,15 +157,23 @@ async function main() {
140
157
  if (args.type === 'all' || args.type === 'spot') {
141
158
  try {
142
159
  const spotData = await client.getSpotMetaAndAssetCtxs();
143
- for (let i = 0; i < spotData.meta.universe.length; i++) {
144
- const pair = spotData.meta.universe[i];
145
- const ctx = spotData.assetCtxs[i];
146
- if (!pair || !ctx) continue;
160
+ // contexts carry coin field that matches pair.name; not necessarily aligned by index
161
+ const ctxMap = new Map<string, (typeof spotData.assetCtxs)[number]>();
162
+ for (const ctx of spotData.assetCtxs) {
163
+ if ((ctx as Record<string, unknown>).coin) {
164
+ ctxMap.set((ctx as Record<string, unknown>).coin as string, ctx);
165
+ }
166
+ }
167
+ for (const pair of spotData.meta.universe) {
168
+ if (!pair) continue;
169
+ const ctx = ctxMap.get(pair.name);
170
+ if (!ctx) continue;
147
171
 
148
172
  allMarkets.push({
149
173
  type: 'spot',
150
174
  provider: 'Spot',
151
175
  coin: pair.name,
176
+ assetId: 10000 + pair.index,
152
177
  price: ctx.markPx,
153
178
  volume24h: parseFloat(ctx.dayNtlVlm || '0'),
154
179
  });
@@ -164,6 +189,11 @@ async function main() {
164
189
  // Apply top filter
165
190
  const markets = args.top ? allMarkets.slice(0, args.top) : allMarkets;
166
191
 
192
+ if (args.json) {
193
+ console.log(JSON.stringify(markets, null, 2));
194
+ return;
195
+ }
196
+
167
197
  // Group by type for display
168
198
  const perps = markets.filter((m) => m.type === 'perp');
169
199
  const hip3 = markets.filter((m) => m.type === 'hip3');
@@ -180,11 +210,11 @@ async function main() {
180
210
  // Print perps
181
211
  if (perps.length > 0) {
182
212
  console.log('=== Main Perpetuals ===\n');
183
- console.log('Coin Price 24h Volume Funding (Ann.) Leverage');
184
- console.log('-'.repeat(75));
213
+ console.log('Coin AssetID Price 24h Volume Funding (Ann.) Leverage');
214
+ console.log('-'.repeat(87));
185
215
  for (const m of perps) {
186
216
  console.log(
187
- `${m.coin.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)} ${(m.maxLeverage ? `${m.maxLeverage}x` : '-').padStart(9)}`
217
+ `${m.coin.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)} ${(m.maxLeverage ? `${m.maxLeverage}x` : '-').padStart(9)}`
188
218
  );
189
219
  }
190
220
  console.log();
@@ -193,11 +223,11 @@ async function main() {
193
223
  // Print HIP-3 markets
194
224
  if (hip3.length > 0) {
195
225
  console.log('=== HIP-3 Perpetuals ===\n');
196
- console.log('Coin Provider Price 24h Volume Funding (Ann.)');
197
- console.log('-'.repeat(80));
226
+ console.log('Coin Provider AssetID Price 24h Volume Funding (Ann.)');
227
+ console.log('-'.repeat(92));
198
228
  for (const m of hip3) {
199
229
  console.log(
200
- `${m.coin.padEnd(14)} ${m.provider.padEnd(16)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)}`
230
+ `${m.coin.padEnd(14)} ${m.provider.padEnd(16)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)}`
201
231
  );
202
232
  }
203
233
  console.log();
@@ -206,11 +236,11 @@ async function main() {
206
236
  // Print spot markets
207
237
  if (spots.length > 0) {
208
238
  console.log('=== Spot Markets ===\n');
209
- console.log('Pair Price 24h Volume');
210
- console.log('-'.repeat(50));
239
+ console.log('Pair AssetID Price 24h Volume');
240
+ console.log('-'.repeat(62));
211
241
  for (const m of spots) {
212
242
  console.log(
213
- `${m.coin.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)}`
243
+ `${m.coin.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)}`
214
244
  );
215
245
  }
216
246
  console.log();
@@ -21,6 +21,7 @@ Options:
21
21
  --interval <interval> Candle interval (default: 1h)
22
22
  Valid: ${VALID_INTERVALS.join(', ')}
23
23
  --bars <n> Number of bars to fetch (default: 24)
24
+ --json Output as JSON (machine-readable)
24
25
  --help, -h Show this help
25
26
 
26
27
  Examples:
@@ -59,19 +60,32 @@ async function main() {
59
60
  }
60
61
 
61
62
  const bars = parseInt(args.bars as string) || 24;
63
+ const jsonOutput = args.json as boolean;
62
64
  const client = getClient();
63
65
 
64
- console.log(`Open Broker - ${normalizeCoin(coin)} Candles (${interval})`);
65
- console.log('='.repeat(40) + '\n');
66
+ if (!jsonOutput) {
67
+ console.log(`Open Broker - ${normalizeCoin(coin)} Candles (${interval})`);
68
+ console.log('='.repeat(40) + '\n');
69
+ }
66
70
 
67
71
  try {
68
72
  // Load metadata (needed for HIP-3 coin resolution)
69
73
  await client.getMetaAndAssetCtxs();
74
+ if (client.isTestnet && coin.includes(':')) {
75
+ await client.loadSingleHip3Dex(coin.split(':')[0]);
76
+ }
70
77
 
71
78
  const now = Date.now();
72
79
  const startTime = now - (bars * (INTERVAL_MS[interval] || 3_600_000));
73
80
  const candles = await client.getCandleSnapshot(normalizeCoin(coin), interval, startTime);
74
81
 
82
+ if (jsonOutput) {
83
+ let assetId = -1;
84
+ try { assetId = client.getAssetIndex(normalizeCoin(coin)); } catch { /* noop */ }
85
+ console.log(JSON.stringify({ coin: normalizeCoin(coin), assetId, interval, candles }, null, 2));
86
+ return;
87
+ }
88
+
75
89
  if (candles.length === 0) {
76
90
  console.log('No candle data found');
77
91
  return;
@@ -9,10 +9,13 @@ function printUsage() {
9
9
  Usage: openbroker fees [options]
10
10
 
11
11
  Options:
12
- --help, -h Show this help
12
+ --address <0x...> Look up another account's fees
13
+ --json Output as JSON (machine-readable)
14
+ --help, -h Show this help
13
15
 
14
16
  Examples:
15
17
  openbroker fees
18
+ openbroker fees --json
16
19
  `);
17
20
  }
18
21
 
@@ -25,19 +28,27 @@ async function main() {
25
28
  }
26
29
 
27
30
  const targetAddress = args.address as string | undefined;
31
+ const jsonOutput = args.json as boolean;
28
32
  const client = getClient();
29
33
  const lookupAddress = targetAddress?.toLowerCase();
30
34
 
31
- console.log('Open Broker - Fee Schedule');
32
- console.log('=========================\n');
35
+ if (!jsonOutput) {
36
+ console.log('Open Broker - Fee Schedule');
37
+ console.log('=========================\n');
33
38
 
34
- if (targetAddress) {
35
- console.log(`Lookup: ${lookupAddress}\n`);
39
+ if (targetAddress) {
40
+ console.log(`Lookup: ${lookupAddress}\n`);
41
+ }
36
42
  }
37
43
 
38
44
  try {
39
45
  const fees = await client.getUserFees(lookupAddress);
40
46
 
47
+ if (jsonOutput) {
48
+ console.log(JSON.stringify(fees, null, 2));
49
+ return;
50
+ }
51
+
41
52
  // Fee rates
42
53
  console.log('Fee Rates');
43
54
  console.log('─'.repeat(40));
@@ -11,6 +11,7 @@ Usage: openbroker funding-history --coin <symbol> [options]
11
11
  Options:
12
12
  --coin <symbol> Asset symbol (required, e.g. ETH, BTC)
13
13
  --hours <n> Hours of history to fetch (default: 24)
14
+ --json Output as JSON (machine-readable)
14
15
  --help, -h Show this help
15
16
 
16
17
  Examples:
@@ -35,19 +36,32 @@ async function main() {
35
36
  }
36
37
 
37
38
  const hours = parseInt(args.hours as string) || 24;
39
+ const jsonOutput = args.json as boolean;
38
40
  const client = getClient();
39
41
 
40
- console.log(`Open Broker - ${normalizeCoin(coin)} Funding History (${hours}h)`);
41
- console.log('='.repeat(40) + '\n');
42
+ if (!jsonOutput) {
43
+ console.log(`Open Broker - ${normalizeCoin(coin)} Funding History (${hours}h)`);
44
+ console.log('='.repeat(40) + '\n');
45
+ }
42
46
 
43
47
  try {
44
48
  // Load metadata (needed for HIP-3 coin resolution)
45
49
  await client.getMetaAndAssetCtxs();
50
+ if (client.isTestnet && coin.includes(':')) {
51
+ await client.loadSingleHip3Dex(coin.split(':')[0]);
52
+ }
46
53
 
47
54
  const now = Date.now();
48
55
  const startTime = now - (hours * 3_600_000);
49
56
  const history = await client.getFundingHistory(normalizeCoin(coin), startTime);
50
57
 
58
+ if (jsonOutput) {
59
+ let assetId = -1;
60
+ try { assetId = client.getAssetIndex(normalizeCoin(coin)); } catch { /* noop */ }
61
+ console.log(JSON.stringify({ coin: normalizeCoin(coin), assetId, history }, null, 2));
62
+ return;
63
+ }
64
+
51
65
  if (history.length === 0) {
52
66
  console.log('No funding history found');
53
67
  return;
@@ -32,6 +32,7 @@ Examples:
32
32
 
33
33
  interface FundingScanResult {
34
34
  coin: string;
35
+ assetId: number;
35
36
  dex: string;
36
37
  hourlyRate: number;
37
38
  annualizedPct: number;
@@ -69,9 +70,14 @@ async function scanFunding(client: ReturnType<typeof getClient>, options: {
69
70
 
70
71
  // API returns HIP-3 names already prefixed (e.g., "xyz:CL")
71
72
  const coin = asset.name;
73
+ let assetId = isMain ? i : -1;
74
+ if (!isMain) {
75
+ try { assetId = client.getAssetIndex(coin); } catch { /* not registered */ }
76
+ }
72
77
 
73
78
  results.push({
74
79
  coin,
80
+ assetId,
75
81
  dex: dexData.dexName ?? 'main',
76
82
  hourlyRate,
77
83
  annualizedPct,
@@ -184,6 +190,10 @@ async function main() {
184
190
  console.log('==================================\n');
185
191
  console.log(`Threshold: ${threshold}% annualized | Scope: ${mainOnly ? 'main only' : hip3Only ? 'HIP-3 only' : 'all dexes'}\n`);
186
192
 
193
+ if (client.isTestnet && !mainOnly) {
194
+ console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Only main perps will be scanned.\n');
195
+ }
196
+
187
197
  const options = { threshold, mainOnly, hip3Only, topN };
188
198
 
189
199
  const runScan = async () => {
@@ -6,6 +6,7 @@ import { formatPercent, annualizeFundingRate, parseArgs } from '../core/utils.js
6
6
 
7
7
  interface FundingDisplay {
8
8
  coin: string;
9
+ assetId: number;
9
10
  hourlyRate: number;
10
11
  annualizedRate: number;
11
12
  premium: number;
@@ -47,6 +48,7 @@ async function main() {
47
48
 
48
49
  fundingData.push({
49
50
  coin: asset.name,
51
+ assetId: i,
50
52
  hourlyRate,
51
53
  annualizedRate,
52
54
  premium,
@@ -56,7 +58,14 @@ async function main() {
56
58
  }
57
59
 
58
60
  // Include HIP-3 dex assets
61
+ if (client.isTestnet && (includeHip3 || showAll) && !(filterCoin?.includes(':'))) {
62
+ console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use --coin dexName:COIN to view a specific HIP-3 asset.\n');
63
+ }
59
64
  if (includeHip3 || showAll || (filterCoin && filterCoin.includes(':'))) {
65
+ // On testnet, load the specific dex on demand if user specified dex:COIN
66
+ if (client.isTestnet && filterCoin?.includes(':')) {
67
+ await client.loadSingleHip3Dex(filterCoin.split(':')[0]);
68
+ }
60
69
  const allPerps = await client.getAllPerpMetas();
61
70
  for (const dexData of allPerps) {
62
71
  if (!dexData.dexName) continue; // Skip main dex (already loaded)
@@ -77,8 +86,12 @@ async function main() {
77
86
 
78
87
  if (!showAll && openInterest < 1000) continue;
79
88
 
89
+ let assetId = -1;
90
+ try { assetId = client.getAssetIndex(coinName); } catch { /* not registered */ }
91
+
80
92
  fundingData.push({
81
93
  coin: coinName,
94
+ assetId,
82
95
  hourlyRate,
83
96
  annualizedRate,
84
97
  premium: 0,
@@ -6,6 +6,7 @@ import { formatUsd, parseArgs } from '../core/utils.js';
6
6
 
7
7
  interface MarketDisplay {
8
8
  coin: string;
9
+ assetId: number;
9
10
  markPx: number;
10
11
  oraclePx: number;
11
12
  prevDayPx: number;
@@ -47,6 +48,7 @@ async function main() {
47
48
 
48
49
  markets.push({
49
50
  coin: asset.name,
51
+ assetId: i,
50
52
  markPx,
51
53
  oraclePx,
52
54
  prevDayPx,
@@ -59,7 +61,14 @@ async function main() {
59
61
  }
60
62
 
61
63
  // Include HIP-3 markets
64
+ if (client.isTestnet && includeHip3 && !(filterCoin?.includes(':'))) {
65
+ console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use --coin dexName:COIN to view a specific HIP-3 asset.\n');
66
+ }
62
67
  if (includeHip3 || (filterCoin && filterCoin.includes(':'))) {
68
+ // On testnet, load the specific dex on demand if user specified dex:COIN
69
+ if (client.isTestnet && filterCoin?.includes(':')) {
70
+ await client.loadSingleHip3Dex(filterCoin.split(':')[0]);
71
+ }
63
72
  const allPerps = await client.getAllPerpMetas();
64
73
  for (const dexData of allPerps) {
65
74
  if (!dexData.dexName) continue;
@@ -79,9 +88,12 @@ async function main() {
79
88
  const volume24h = parseFloat(ctx.dayNtlVlm);
80
89
  const openInterest = parseFloat(ctx.openInterest);
81
90
  const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0;
91
+ let assetId = -1;
92
+ try { assetId = client.getAssetIndex(coinName); } catch { /* not registered */ }
82
93
 
83
94
  markets.push({
84
95
  coin: coinName,
96
+ assetId,
85
97
  markPx,
86
98
  oraclePx,
87
99
  prevDayPx,
@@ -11,6 +11,7 @@ Usage: openbroker order-status --oid <order-id>
11
11
  Options:
12
12
  --oid <id> Order ID (number) or client order ID (hex string) — required
13
13
  --address <0x...> Look up order on another account
14
+ --json Output as JSON (machine-readable)
14
15
  --help, -h Show this help
15
16
 
16
17
  Examples:
@@ -37,15 +38,23 @@ async function main() {
37
38
 
38
39
  const oid = oidArg.startsWith('0x') ? oidArg : parseInt(oidArg);
39
40
  const targetAddress = args.address as string | undefined;
41
+ const jsonOutput = args.json as boolean;
40
42
  const client = getClient();
41
43
  const lookupAddress = targetAddress?.toLowerCase();
42
44
 
43
- console.log('Open Broker - Order Status');
44
- console.log('=========================\n');
45
+ if (!jsonOutput) {
46
+ console.log('Open Broker - Order Status');
47
+ console.log('=========================\n');
48
+ }
45
49
 
46
50
  try {
47
51
  const result = await client.getOrderStatus(oid, lookupAddress);
48
52
 
53
+ if (jsonOutput) {
54
+ console.log(JSON.stringify(result, null, 2));
55
+ return;
56
+ }
57
+
49
58
  if (result.status === 'unknownOid') {
50
59
  console.log(`Order ${oidArg} not found`);
51
60
  return;
@@ -45,6 +45,11 @@ async function main() {
45
45
  const lookupAddress = targetAddress?.toLowerCase();
46
46
 
47
47
  try {
48
+ // On testnet, load specific HIP-3 dex on demand if filtering by dex:COIN
49
+ if (client.isTestnet && filterCoin?.includes(':')) {
50
+ await client.loadSingleHip3Dex(filterCoin.split(':')[0]);
51
+ }
52
+
48
53
  if (openOnly) {
49
54
  // Use the dedicated open orders endpoint
50
55
  let openOrders = await client.getOpenOrders(lookupAddress);
@@ -9,6 +9,7 @@ function printUsage() {
9
9
  Usage: openbroker rate-limit [options]
10
10
 
11
11
  Options:
12
+ --json Output as JSON (machine-readable)
12
13
  --help, -h Show this help
13
14
 
14
15
  Examples:
@@ -24,14 +25,22 @@ async function main() {
24
25
  process.exit(0);
25
26
  }
26
27
 
28
+ const jsonOutput = args.json as boolean;
27
29
  const client = getClient();
28
30
 
29
- console.log('Open Broker - API Rate Limit');
30
- console.log('===========================\n');
31
+ if (!jsonOutput) {
32
+ console.log('Open Broker - API Rate Limit');
33
+ console.log('===========================\n');
34
+ }
31
35
 
32
36
  try {
33
37
  const rl = await client.getUserRateLimit();
34
38
 
39
+ if (jsonOutput) {
40
+ console.log(JSON.stringify(rl, null, 2));
41
+ return;
42
+ }
43
+
35
44
  const used = rl.nRequestsUsed;
36
45
  const cap = rl.nRequestsCap;
37
46
  const surplus = rl.nRequestsSurplus;