openbroker 1.0.82 → 1.0.87

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.
@@ -9,6 +9,8 @@ import type {
9
9
  OrderResponse,
10
10
  CancelResponse,
11
11
  MetaAndAssetCtxs,
12
+ AssetMeta,
13
+ AssetCtx,
12
14
  ClearinghouseState,
13
15
  OpenOrder,
14
16
  } from './types.js';
@@ -256,13 +258,16 @@ export class HyperliquidClient {
256
258
  }
257
259
  this.log('metaAndAssetCtxs response:', JSON.stringify(response, null, 2).slice(0, 500) + '...');
258
260
 
259
- this.meta = {
260
- meta: { universe: response[0].universe },
261
- assetCtxs: response[1],
261
+ // Build the narrowed value locally so neither narrowing nor the `| null`
262
+ // field type can be lost across `await` / method-call boundaries below.
263
+ const meta: MetaAndAssetCtxs = {
264
+ meta: { universe: response[0].universe as AssetMeta[] },
265
+ assetCtxs: response[1] as AssetCtx[],
262
266
  };
267
+ this.meta = meta;
263
268
 
264
269
  // Build lookup maps for main dex
265
- this.meta.meta.universe.forEach((asset, index) => {
270
+ meta.meta.universe.forEach((asset, index) => {
266
271
  this.assetMap.set(asset.name, index);
267
272
  this.szDecimalsMap.set(asset.name, asset.szDecimals);
268
273
  this.coinDexMap.set(asset.name, { dexName: null, dexIdx: 0, localName: asset.name });
@@ -274,7 +279,7 @@ export class HyperliquidClient {
274
279
  this.hip3Loaded = true;
275
280
  }
276
281
 
277
- return this.meta;
282
+ return meta;
278
283
  }
279
284
 
280
285
  /**
@@ -782,7 +787,19 @@ export class HyperliquidClient {
782
787
  tokenId,
783
788
  }),
784
789
  });
785
- const data = await response.json();
790
+ const data = await response.json() as {
791
+ name: string;
792
+ maxSupply: string;
793
+ totalSupply: string;
794
+ circulatingSupply: string;
795
+ szDecimals: number;
796
+ weiDecimals: number;
797
+ midPx: string;
798
+ markPx: string;
799
+ prevDayPx: string;
800
+ deployer: string;
801
+ deployTime: string;
802
+ } | null;
786
803
  this.log('tokenDetails response:', JSON.stringify(data).slice(0, 500));
787
804
  return data;
788
805
  } catch {
@@ -807,7 +824,10 @@ export class HyperliquidClient {
807
824
  headers: { 'Content-Type': 'application/json' },
808
825
  body: JSON.stringify({ type: 'predictedFundings' }),
809
826
  });
810
- const data = await response.json();
827
+ const data = await response.json() as Array<[
828
+ string,
829
+ Array<[string, { fundingRate: string; nextFundingTime: number }]>
830
+ ]>;
811
831
  this.log('predictedFundings response length:', data?.length);
812
832
  return data;
813
833
  }
@@ -827,7 +847,7 @@ export class HyperliquidClient {
827
847
  }> {
828
848
  this.log('Fetching l2Book for:', coin);
829
849
  // API accepts prefixed names directly (e.g., "xyz:CL")
830
- let response;
850
+ let response: Awaited<ReturnType<typeof this.info.l2Book>>;
831
851
  try {
832
852
  response = await this.info.l2Book({ coin });
833
853
  } catch (error) {
@@ -838,8 +858,12 @@ export class HyperliquidClient {
838
858
  throw new Error(`l2Book(${coin}) failed: ${this.describeError(error)}`);
839
859
  }
840
860
 
841
- const bids = response.levels[0] as Array<{ px: string; sz: string; n: number }>;
842
- const asks = response.levels[1] as Array<{ px: string; sz: string; n: number }>;
861
+ if (!response || !Array.isArray(response.levels)) {
862
+ throw new Error(`l2Book(${coin}) returned empty/malformed payload.`);
863
+ }
864
+
865
+ const bids = (response.levels[0] ?? []) as Array<{ px: string; sz: string; n: number }>;
866
+ const asks = (response.levels[1] ?? []) as Array<{ px: string; sz: string; n: number }>;
843
867
 
844
868
  const bestBid = bids.length > 0 ? parseFloat(bids[0].px) : 0;
845
869
  const bestAsk = asks.length > 0 ? parseFloat(asks[0].px) : 0;
@@ -1015,7 +1039,7 @@ export class HyperliquidClient {
1015
1039
  user: user ?? this.address,
1016
1040
  }),
1017
1041
  });
1018
- const data = await response.json();
1042
+ const data = await response.json() as string | { abstraction?: string; mode?: string } | null;
1019
1043
  this.log('userAbstraction response:', JSON.stringify(data));
1020
1044
 
1021
1045
  // API may return a bare string or an object. Normalize to string for matching.
@@ -1194,10 +1218,13 @@ export class HyperliquidClient {
1194
1218
  }
1195
1219
 
1196
1220
  try {
1221
+ // approveBuilderFee is a wallet-level authorization (not a vault action)
1222
+ // and the SDK's `opts` parameter only carries an AbortSignal. Do not pass
1223
+ // vaultParam here — the signature is { signal? }, not a vault wrapper.
1197
1224
  const response = await this.exchange.approveBuilderFee({
1198
1225
  builder: targetBuilder as `0x${string}`,
1199
1226
  maxFeeRate,
1200
- }, this.vaultParam);
1227
+ });
1201
1228
  this.log('approveBuilderFee response:', response);
1202
1229
  return { status: 'ok', response };
1203
1230
  } catch (error) {
@@ -1242,7 +1269,18 @@ export class HyperliquidClient {
1242
1269
  headers: { 'Content-Type': 'application/json' },
1243
1270
  body: JSON.stringify(body),
1244
1271
  });
1245
- const data = await response.json();
1272
+ const data = await response.json() as Array<{
1273
+ time: number;
1274
+ hash: string;
1275
+ delta: {
1276
+ type: 'funding';
1277
+ coin: string;
1278
+ usdc: string;
1279
+ szi: string;
1280
+ fundingRate: string;
1281
+ nSamples: number | null;
1282
+ };
1283
+ }>;
1246
1284
  this.log('userFunding response length:', data?.length);
1247
1285
  return data;
1248
1286
  }
@@ -1285,7 +1323,25 @@ export class HyperliquidClient {
1285
1323
  headers: { 'Content-Type': 'application/json' },
1286
1324
  body: JSON.stringify(body),
1287
1325
  });
1288
- const data = await response.json();
1326
+ const data = await response.json() as Array<{
1327
+ coin: string;
1328
+ px: string;
1329
+ sz: string;
1330
+ side: 'B' | 'A';
1331
+ time: number;
1332
+ startPosition: string;
1333
+ dir: string;
1334
+ closedPnl: string;
1335
+ fee: string;
1336
+ hash: string;
1337
+ oid: number;
1338
+ tid: number;
1339
+ crossed: boolean;
1340
+ feeToken: string;
1341
+ twapId: number | null;
1342
+ cloid: string | null;
1343
+ builderFee: string | null;
1344
+ }>;
1289
1345
  this.log('userFills response length:', data?.length);
1290
1346
  return data;
1291
1347
  }
@@ -1328,7 +1384,28 @@ export class HyperliquidClient {
1328
1384
  user: user ?? this.address,
1329
1385
  }),
1330
1386
  });
1331
- const data = await response.json();
1387
+ const data = await response.json() as Array<{
1388
+ order: {
1389
+ coin: string;
1390
+ side: string;
1391
+ limitPx: string;
1392
+ sz: string;
1393
+ origSz: string;
1394
+ oid: number;
1395
+ timestamp: number;
1396
+ orderType: string;
1397
+ tif: string | null;
1398
+ cloid: string | null;
1399
+ triggerCondition: string;
1400
+ triggerPx: string;
1401
+ isTrigger: boolean;
1402
+ isPositionTpsl: boolean;
1403
+ reduceOnly: boolean;
1404
+ children: unknown[];
1405
+ };
1406
+ status: string;
1407
+ statusTimestamp: number;
1408
+ }>;
1332
1409
  this.log('historicalOrders response length:', data?.length);
1333
1410
  return data;
1334
1411
  }
@@ -1374,7 +1451,30 @@ export class HyperliquidClient {
1374
1451
  oid: typeof oid === 'string' ? oid : oid,
1375
1452
  }),
1376
1453
  });
1377
- const data = await response.json();
1454
+ const data = await response.json() as {
1455
+ status: string;
1456
+ order?: {
1457
+ order: {
1458
+ coin: string;
1459
+ side: string;
1460
+ limitPx: string;
1461
+ sz: string;
1462
+ origSz: string;
1463
+ oid: number;
1464
+ timestamp: number;
1465
+ orderType: string;
1466
+ tif: string | null;
1467
+ cloid: string | null;
1468
+ triggerCondition: string;
1469
+ triggerPx: string;
1470
+ isTrigger: boolean;
1471
+ isPositionTpsl: boolean;
1472
+ reduceOnly: boolean;
1473
+ };
1474
+ status: string;
1475
+ statusTimestamp: number;
1476
+ };
1477
+ };
1378
1478
  this.log('orderStatus response:', JSON.stringify(data).slice(0, 500));
1379
1479
  return data;
1380
1480
  }
@@ -1409,7 +1509,20 @@ export class HyperliquidClient {
1409
1509
  user: user ?? this.address,
1410
1510
  }),
1411
1511
  });
1412
- const data = await response.json();
1512
+ const data = await response.json() as {
1513
+ dailyUserVlm: Array<{ date: string; exchange: string; userCross: string; userAdd: string }>;
1514
+ feeSchedule: Record<string, unknown>;
1515
+ userCrossRate: string;
1516
+ userAddRate: string;
1517
+ userSpotCrossRate: string;
1518
+ userSpotAddRate: string;
1519
+ activeReferralDiscount: string;
1520
+ trial: unknown;
1521
+ feeTrialEscrow: string;
1522
+ nextTrialAvailableTimestamp: unknown;
1523
+ stakingLink: { stakingUser: string; status: string } | null;
1524
+ activeStakingDiscount: { basisPoints: number; discountRate: string } | null;
1525
+ };
1413
1526
  this.log('userFees response:', JSON.stringify(data).slice(0, 500));
1414
1527
  return data;
1415
1528
  }
@@ -1448,7 +1561,18 @@ export class HyperliquidClient {
1448
1561
  headers: { 'Content-Type': 'application/json' },
1449
1562
  body: JSON.stringify({ type: 'candleSnapshot', req }),
1450
1563
  });
1451
- const data = await response.json();
1564
+ const data = await response.json() as Array<{
1565
+ t: number;
1566
+ T: number;
1567
+ s: string;
1568
+ i: string;
1569
+ o: string;
1570
+ c: string;
1571
+ h: string;
1572
+ l: string;
1573
+ v: string;
1574
+ n: number;
1575
+ }>;
1452
1576
  this.log('candleSnapshot response length:', data?.length);
1453
1577
  return data;
1454
1578
  }
@@ -1480,7 +1604,12 @@ export class HyperliquidClient {
1480
1604
  headers: { 'Content-Type': 'application/json' },
1481
1605
  body: JSON.stringify(body),
1482
1606
  });
1483
- const data = await response.json();
1607
+ const data = await response.json() as Array<{
1608
+ coin: string;
1609
+ fundingRate: string;
1610
+ premium: string;
1611
+ time: number;
1612
+ }>;
1484
1613
  this.log('fundingHistory response length:', data?.length);
1485
1614
  return data;
1486
1615
  }
@@ -1510,7 +1639,15 @@ export class HyperliquidClient {
1510
1639
  headers: { 'Content-Type': 'application/json' },
1511
1640
  body: JSON.stringify(body),
1512
1641
  });
1513
- const data = await response.json();
1642
+ const data = await response.json() as Array<{
1643
+ coin: string;
1644
+ side: 'B' | 'A';
1645
+ px: string;
1646
+ sz: string;
1647
+ time: number;
1648
+ hash: string;
1649
+ tid: number;
1650
+ }>;
1514
1651
  this.log('recentTrades response length:', data?.length);
1515
1652
  return data;
1516
1653
  }
@@ -1537,7 +1674,12 @@ export class HyperliquidClient {
1537
1674
  user: user ?? this.address,
1538
1675
  }),
1539
1676
  });
1540
- const data = await response.json();
1677
+ const data = await response.json() as {
1678
+ cumVlm: string;
1679
+ nRequestsUsed: number;
1680
+ nRequestsCap: number;
1681
+ nRequestsSurplus: number;
1682
+ };
1541
1683
  this.log('userRateLimit response:', JSON.stringify(data));
1542
1684
  return data;
1543
1685
  }
@@ -2137,7 +2279,9 @@ export class HyperliquidClient {
2137
2279
  // Use the exact spot market key from spotMeta (e.g. "@230", "PURR/USDC").
2138
2280
  // On testnet the tradable asset id and displayed market key can diverge.
2139
2281
  const mids = await this.getAllMids();
2140
- let midStr = mids[spotCoinKey];
2282
+ // Record<string, string> lookup returns `string` under TS defaults but
2283
+ // is runtime-undefined when the key is absent — hence the explicit union.
2284
+ let midStr: string | undefined = mids[spotCoinKey];
2141
2285
 
2142
2286
  // Fallback: allMids may omit spot pairs (especially on testnet).
2143
2287
  // Try spotMetaAndAssetCtxs which returns markPx directly.
@@ -2295,7 +2439,7 @@ export class HyperliquidClient {
2295
2439
  await this.getMetaAndAssetCtxs();
2296
2440
 
2297
2441
  if (leverage) {
2298
- await this.setLeverage(coin, leverage);
2442
+ await this.updateLeverage(coin, leverage);
2299
2443
  }
2300
2444
 
2301
2445
  const assetIndex = this.getAssetIndex(coin);
@@ -112,7 +112,7 @@ export function loadConfig(): OpenBrokerConfig {
112
112
  // Show warning once
113
113
  if (!readOnlyWarningShown) {
114
114
  readOnlyWarningShown = true;
115
- 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');
116
116
  }
117
117
  }
118
118
 
@@ -81,11 +81,18 @@ export interface CancelResponse {
81
81
 
82
82
  // ============ Market Data Types ============
83
83
 
84
+ // Shape matches the upstream @nktkas/hyperliquid SDK response. Fields like
85
+ // `onlyIsolated` and `premium` are absent-or-null in practice for some assets,
86
+ // so they're modelled as optional / nullable. Index signature absorbs any
87
+ // additional SDK fields we don't consume here (marginTableId, growthMode, …).
84
88
  export interface AssetMeta {
85
89
  name: string;
86
90
  szDecimals: number;
87
91
  maxLeverage: number;
88
- onlyIsolated: boolean;
92
+ onlyIsolated?: boolean;
93
+ isDelisted?: boolean;
94
+ marginTableId?: number;
95
+ [key: string]: unknown;
89
96
  }
90
97
 
91
98
  export interface AssetCtx {
@@ -93,11 +100,13 @@ export interface AssetCtx {
93
100
  openInterest: string;
94
101
  prevDayPx: string;
95
102
  dayNtlVlm: string;
96
- premium: string;
103
+ premium: string | null;
97
104
  oraclePx: string;
98
105
  markPx: string;
99
- midPx?: string;
100
- impactPxs?: [string, string];
106
+ midPx?: string | null;
107
+ impactPxs?: [string, string] | string[] | null;
108
+ dayBaseVlm?: string;
109
+ [key: string]: unknown;
101
110
  }
102
111
 
103
112
  export interface Meta {
@@ -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,7 +119,7 @@ async function main() {
108
119
 
109
120
  // Fetch HIP-3 perps
110
121
  if (args.type === 'all' || args.type === 'hip3') {
111
- if (client.isTestnet) {
122
+ if (client.isTestnet && !args.json) {
112
123
  console.log('Note: Testnet — HIP-3 dexes not auto-loaded. Use "dexName:COIN" syntax to load a specific dex.\n');
113
124
  }
114
125
  try {
@@ -123,10 +134,13 @@ async function main() {
123
134
  const ctx = dexData.assetCtxs[i];
124
135
  if (!asset || !ctx) continue;
125
136
 
137
+ let assetId = -1;
138
+ try { assetId = client.getAssetIndex(asset.name); } catch { /* not registered */ }
126
139
  allMarkets.push({
127
140
  type: 'hip3',
128
141
  provider: dexData.dexName || `HIP-3 DEX ${dexIdx}`,
129
142
  coin: asset.name,
143
+ assetId,
130
144
  price: ctx.markPx,
131
145
  volume24h: parseFloat(ctx.dayNtlVlm || '0'),
132
146
  funding: ctx.funding,
@@ -143,15 +157,23 @@ async function main() {
143
157
  if (args.type === 'all' || args.type === 'spot') {
144
158
  try {
145
159
  const spotData = await client.getSpotMetaAndAssetCtxs();
146
- for (let i = 0; i < spotData.meta.universe.length; i++) {
147
- const pair = spotData.meta.universe[i];
148
- const ctx = spotData.assetCtxs[i];
149
- 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;
150
171
 
151
172
  allMarkets.push({
152
173
  type: 'spot',
153
174
  provider: 'Spot',
154
175
  coin: pair.name,
176
+ assetId: 10000 + pair.index,
155
177
  price: ctx.markPx,
156
178
  volume24h: parseFloat(ctx.dayNtlVlm || '0'),
157
179
  });
@@ -167,6 +189,11 @@ async function main() {
167
189
  // Apply top filter
168
190
  const markets = args.top ? allMarkets.slice(0, args.top) : allMarkets;
169
191
 
192
+ if (args.json) {
193
+ console.log(JSON.stringify(markets, null, 2));
194
+ return;
195
+ }
196
+
170
197
  // Group by type for display
171
198
  const perps = markets.filter((m) => m.type === 'perp');
172
199
  const hip3 = markets.filter((m) => m.type === 'hip3');
@@ -183,11 +210,11 @@ async function main() {
183
210
  // Print perps
184
211
  if (perps.length > 0) {
185
212
  console.log('=== Main Perpetuals ===\n');
186
- console.log('Coin Price 24h Volume Funding (Ann.) Leverage');
187
- console.log('-'.repeat(75));
213
+ console.log('Coin AssetID Price 24h Volume Funding (Ann.) Leverage');
214
+ console.log('-'.repeat(87));
188
215
  for (const m of perps) {
189
216
  console.log(
190
- `${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)}`
191
218
  );
192
219
  }
193
220
  console.log();
@@ -196,11 +223,11 @@ async function main() {
196
223
  // Print HIP-3 markets
197
224
  if (hip3.length > 0) {
198
225
  console.log('=== HIP-3 Perpetuals ===\n');
199
- console.log('Coin Provider Price 24h Volume Funding (Ann.)');
200
- console.log('-'.repeat(80));
226
+ console.log('Coin Provider AssetID Price 24h Volume Funding (Ann.)');
227
+ console.log('-'.repeat(92));
201
228
  for (const m of hip3) {
202
229
  console.log(
203
- `${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)}`
204
231
  );
205
232
  }
206
233
  console.log();
@@ -209,11 +236,11 @@ async function main() {
209
236
  // Print spot markets
210
237
  if (spots.length > 0) {
211
238
  console.log('=== Spot Markets ===\n');
212
- console.log('Pair Price 24h Volume');
213
- console.log('-'.repeat(50));
239
+ console.log('Pair AssetID Price 24h Volume');
240
+ console.log('-'.repeat(62));
214
241
  for (const m of spots) {
215
242
  console.log(
216
- `${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)}`
217
244
  );
218
245
  }
219
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,10 +60,13 @@ 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)
@@ -75,6 +79,13 @@ async function main() {
75
79
  const startTime = now - (bars * (INTERVAL_MS[interval] || 3_600_000));
76
80
  const candles = await client.getCandleSnapshot(normalizeCoin(coin), interval, startTime);
77
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
+
78
89
  if (candles.length === 0) {
79
90
  console.log('No candle data found');
80
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,10 +36,13 @@ 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)
@@ -51,6 +55,13 @@ async function main() {
51
55
  const startTime = now - (hours * 3_600_000);
52
56
  const history = await client.getFundingHistory(normalizeCoin(coin), startTime);
53
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
+
54
65
  if (history.length === 0) {
55
66
  console.log('No funding history found');
56
67
  return;