openbroker 1.0.45 → 1.0.48

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to Open Broker will be documented in this file.
4
4
 
5
+ ## [1.0.48] - 2026-03-09
6
+
7
+ ### Fixed
8
+ - **HIP-3 Perp Trading**: All trading commands now work with HIP-3 assets using `dex:COIN` syntax (e.g., `--coin xyz:CL`)
9
+ - `getMetaAndAssetCtxs()` loads HIP-3 dex assets into asset/szDecimals maps (asset index = `10000 * dexIdx + assetIdx`)
10
+ - `getAllMids()` fetches and merges mid prices from all HIP-3 dexes
11
+ - Market, limit, trigger, bracket, TWAP, scale, chase orders all work with HIP-3 assets
12
+ - **HIP-3 Info Commands**: `funding`, `funding-history`, `candles`, `trades` now return data for HIP-3 assets (previously returned "No data" / null)
13
+ - **API Name Format**: Hyperliquid API returns HIP-3 names already prefixed (e.g., `xyz:CL`); fixed double-prefixing bug that caused all HIP-3 lookups to fail
14
+ - **Case Normalization**: Added `normalizeCoin()` helper — keeps dex prefix lowercase, uppercases asset (`xyz:cl` → `xyz:CL`). Fixes `toUpperCase()` mangling HIP-3 tickers to `XYZ:CL`
15
+ - **Better Error Messages**: When a bare coin name (e.g., `CL`) matches HIP-3 assets, the error now suggests the prefixed ticker (e.g., `xyz:CL`)
16
+
17
+ ### Added
18
+ - **Funding Rate Scanner**: New `funding-scan` command for cross-dex funding rate scanning
19
+ - Scans all dexes (main + HIP-3) for high funding opportunities
20
+ - `--pairs` flag identifies opposing funding pairs for delta-neutral strategies
21
+ - `--watch --interval N` for periodic re-scanning
22
+ - `--json` output for piping to alerting systems
23
+ - `--main-only` / `--hip3-only` scope filters
24
+ - **HIP-3 Funding Rates**: `funding` and `markets` commands now support `--include-hip3` flag
25
+ - **HIP-3 Funding Arb**: `funding-arb` strategy now works with HIP-3 assets (monitoring loop correctly resolves HIP-3 funding data)
26
+ - **Plugin**: New `ob_funding_scan` agent tool for cross-dex funding scanning with pairs support
27
+ - **Plugin**: `ob_search` now searches HIP-3 perps in addition to main perps and spot
28
+ - **Client**: Added `getCoinDex()`, `getCoinLocalName()`, `isHip3()`, `getAllAssetNames()`, `getHip3AssetNames()`, `invalidateMetaCache()` methods
29
+ - **Client**: `getPerpDexs()` results are now cached to reduce redundant API calls
30
+
5
31
  ## [1.0.44] - 2026-02-25
6
32
 
7
33
  ### Added
package/README.md CHANGED
@@ -31,10 +31,15 @@ openbroker setup # One-command setup (wallet + config + builder a
31
31
  ```
32
32
 
33
33
  The setup command handles everything automatically:
34
- - Generates a new wallet or accepts your existing private key
34
+ - Generates a fresh trading wallet (recommended for agents) or accepts your existing private key
35
35
  - Saves configuration to `~/.openbroker/.env` (permissions `0600`)
36
36
  - Approves the builder fee (required for trading)
37
37
 
38
+ Setup offers three modes:
39
+ 1. **Generate fresh wallet** (recommended for agents) — cleanest option, no browser steps
40
+ 2. **Import existing key** — use a key you already have
41
+ 3. **Generate API wallet** — restricted wallet requiring browser approval from a master wallet
42
+
38
43
  ---
39
44
 
40
45
  ### Info Commands
@@ -636,16 +641,24 @@ export HYPERLIQUID_NETWORK=mainnet # Optional: mainnet (default) or testne
636
641
  export HYPERLIQUID_ACCOUNT_ADDRESS=0x... # Optional: for API wallets
637
642
  ```
638
643
 
639
- ### API Wallet Setup
644
+ ### Fresh Wallet Setup (Recommended for Agents)
645
+
646
+ The simplest setup for agents — generates a dedicated wallet, auto-approves the builder fee, and is ready to trade after funding:
647
+
648
+ ```bash
649
+ openbroker setup # Choose option 1, fund with USDC, start trading
650
+ ```
651
+
652
+ ### API Wallet Setup (Alternative)
640
653
 
641
- For automated trading, use an API wallet:
654
+ For delegated trading without moving funds, use an API wallet:
642
655
 
643
656
  ```bash
644
657
  export HYPERLIQUID_PRIVATE_KEY="0x..." # API wallet private key
645
658
  export HYPERLIQUID_ACCOUNT_ADDRESS="0x..." # Main account address
646
659
  ```
647
660
 
648
- **Note:** Builder fee must be approved with the main wallet first. Sub-accounts cannot approve builder fees. After approval, you can switch to using the API wallet for trading.
661
+ **Note:** API wallets require browser approval from the master wallet. The master wallet signs `ApproveAgent` and `ApproveBuilderFee` transactions via the approval URL provided during setup.
649
662
 
650
663
  ## Builder Fee
651
664
 
package/SKILL.md CHANGED
@@ -4,8 +4,8 @@ description: Hyperliquid trading plugin with background position monitoring. Exe
4
4
  license: MIT
5
5
  compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
6
6
  homepage: https://www.npmjs.com/package/openbroker
7
- metadata: {"author": "monemetrics", "version": "1.0.45", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
8
- allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_bracket ob_chase ob_watcher_status Bash(openbroker:*)
7
+ metadata: {"author": "monemetrics", "version": "1.0.48", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
8
+ allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_bracket ob_chase ob_watcher_status Bash(openbroker:*)
9
9
  ---
10
10
 
11
11
  # Open Broker - Hyperliquid Trading CLI
@@ -40,16 +40,26 @@ openbroker approve-builder --check # Check builder fee status (for troubleshoot
40
40
  ```
41
41
 
42
42
  The `setup` command offers three modes:
43
- 1. **Import existing key** — use a private key you already have (master wallet)
44
- 2. **Generate new wallet** — create a fresh master wallet
45
- 3. **Generate API wallet** (recommended for agents) — creates a restricted wallet that can trade but cannot withdraw
43
+ 1. **Generate fresh wallet** (recommended for agents) creates a dedicated trading wallet with builder fee auto-approved. No browser steps needed — just fund with USDC and start trading.
44
+ 2. **Import existing key** — use a private key you already have
45
+ 3. **Generate API wallet** — creates a restricted wallet that can trade but cannot withdraw. Requires browser approval from a master wallet.
46
46
 
47
47
  For options 1 and 2, setup saves config and approves the builder fee automatically.
48
48
  For option 3 (API wallet), see the API Wallet Setup section below.
49
49
 
50
- ### API Wallet Setup (Recommended for Agents)
50
+ ### Fresh Wallet Setup (Recommended for Agents)
51
51
 
52
- API wallets can place trades on behalf of a master account but **cannot withdraw funds**. This is the safest option for automated agents.
52
+ The simplest setup for agents. A fresh wallet is generated, the builder fee is auto-approved, and the agent is ready to trade immediately after funding.
53
+
54
+ **Flow:**
55
+ 1. Run `openbroker setup` and choose option 1 ("Generate a fresh wallet")
56
+ 2. The CLI generates a wallet, saves the config, and approves the builder fee automatically
57
+ 3. Fund the wallet with USDC on Arbitrum, then deposit at https://app.hyperliquid.xyz/
58
+ 4. Start trading
59
+
60
+ ### API Wallet Setup (Alternative)
61
+
62
+ API wallets can place trades on behalf of a master account but **cannot withdraw funds**. Use this if you prefer to keep funds in your existing wallet and only delegate trading access.
53
63
 
54
64
  **Flow:**
55
65
  1. Run `openbroker setup` and choose option 3 ("Generate API wallet")
@@ -160,8 +170,25 @@ openbroker trades --coin BTC --top 50 # Last 50 trades
160
170
  openbroker rate-limit # API usage and capacity
161
171
  ```
162
172
 
173
+ ### Funding Rate Scanner (Cross-Dex)
174
+ ```bash
175
+ openbroker funding-scan # Scan all dexes, >25% threshold
176
+ openbroker funding-scan --threshold 50 --pairs # Show opposing funding pairs
177
+ openbroker funding-scan --hip3-only --top 20 # HIP-3 only
178
+ openbroker funding-scan --watch --interval 120 # Re-scan every 2 minutes
179
+ ```
180
+
163
181
  ## Trading Commands
164
182
 
183
+ ### HIP-3 Perp Trading
184
+ All trading commands support HIP-3 assets using `dex:COIN` syntax:
185
+ ```bash
186
+ openbroker buy --coin xyz:CL --size 1 # Buy crude oil on xyz dex
187
+ openbroker sell --coin xyz:BRENTOIL --size 1 # Sell brent oil
188
+ openbroker limit --coin xyz:GOLD --side buy --size 0.1 --price 2500
189
+ openbroker funding-arb --coin xyz:CL --size 5000 # Funding arb on HIP-3
190
+ ```
191
+
165
192
  ### Market Orders (Quick)
166
193
  ```bash
167
194
  openbroker buy --coin ETH --size 0.1
package/bin/cli.ts CHANGED
@@ -31,6 +31,7 @@ const commands: Record<string, { script: string; description: string }> = {
31
31
  'funding-history': { script: 'info/funding-history.ts', description: 'View historical funding rates' },
32
32
  'trades': { script: 'info/trades.ts', description: 'View recent trades for an asset' },
33
33
  'rate-limit': { script: 'info/rate-limit.ts', description: 'View API rate limit status' },
34
+ 'funding-scan': { script: 'info/funding-scan.ts', description: 'Scan funding rates across all dexes' },
34
35
 
35
36
  // Operations
36
37
  'buy': { script: 'operations/market-order.ts', description: 'Market buy order' },
@@ -78,6 +79,7 @@ Info Commands:
78
79
  search Search for assets across all providers
79
80
  spot View spot markets and balances
80
81
  rate-limit View API rate limit status
82
+ funding-scan Scan funding rates across all dexes (main + HIP-3)
81
83
 
82
84
  Trading Commands:
83
85
  buy Market buy order
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openbroker",
3
3
  "name": "OpenBroker — Hyperliquid Trading",
4
- "version": "1.0.45",
4
+ "version": "1.0.48",
5
5
  "description": "Trade on Hyperliquid DEX with background position monitoring",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.45",
3
+ "version": "1.0.48",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "scale": "tsx scripts/operations/scale.ts",
39
39
  "bracket": "tsx scripts/operations/bracket.ts",
40
40
  "chase": "tsx scripts/operations/chase.ts",
41
+ "funding-scan": "tsx scripts/info/funding-scan.ts",
41
42
  "funding-arb": "tsx scripts/strategies/funding-arb.ts",
42
43
  "grid": "tsx scripts/strategies/grid.ts",
43
44
  "dca": "tsx scripts/strategies/dca.ts",
@@ -25,6 +25,12 @@ export class HyperliquidClient {
25
25
  private meta: MetaAndAssetCtxs | null = null;
26
26
  private assetMap: Map<string, number> = new Map();
27
27
  private szDecimalsMap: Map<string, number> = new Map();
28
+ /** Maps coin name → dex info for HIP-3 assets. Main dex assets have dexName=null */
29
+ private coinDexMap: Map<string, { dexName: string | null; dexIdx: number; localName: string }> = new Map();
30
+ /** Cache of perpDexs list */
31
+ private perpDexsCache: Array<{ name: string; fullName: string; deployer: string } | null> | null = null;
32
+ /** Whether HIP-3 assets have been loaded into maps */
33
+ private hip3Loaded: boolean = false;
28
34
  public verbose: boolean = false;
29
35
 
30
36
  constructor(config?: OpenBrokerConfig) {
@@ -106,18 +112,106 @@ export class HyperliquidClient {
106
112
  assetCtxs: response[1],
107
113
  };
108
114
 
109
- // Build lookup maps
115
+ // Build lookup maps for main dex
110
116
  this.meta.meta.universe.forEach((asset, index) => {
111
117
  this.assetMap.set(asset.name, index);
112
118
  this.szDecimalsMap.set(asset.name, asset.szDecimals);
119
+ this.coinDexMap.set(asset.name, { dexName: null, dexIdx: 0, localName: asset.name });
113
120
  });
114
121
 
122
+ // Load HIP-3 dex assets (only once - maps persist across meta cache invalidation)
123
+ if (!this.hip3Loaded) {
124
+ await this.loadHip3Assets();
125
+ this.hip3Loaded = true;
126
+ }
127
+
115
128
  return this.meta;
116
129
  }
117
130
 
131
+ /**
132
+ * Load HIP-3 perp dex assets into the asset/szDecimals maps.
133
+ * Asset index formula: 10000 * dexIdx + assetIdx
134
+ * Coins are keyed as "dexName:COIN" (e.g., "xyz:CL")
135
+ */
136
+ private async loadHip3Assets(): Promise<void> {
137
+ try {
138
+ const dexs = await this.getPerpDexs();
139
+ const baseUrl = isMainnet()
140
+ ? 'https://api.hyperliquid.xyz'
141
+ : 'https://api.hyperliquid-testnet.xyz';
142
+
143
+ for (let dexIdx = 1; dexIdx < dexs.length; dexIdx++) {
144
+ const dex = dexs[dexIdx];
145
+ if (!dex) continue;
146
+
147
+ try {
148
+ const dexResponse = await fetch(baseUrl + '/info', {
149
+ method: 'POST',
150
+ headers: { 'Content-Type': 'application/json' },
151
+ body: JSON.stringify({ type: 'metaAndAssetCtxs', dex: dex.name }),
152
+ });
153
+ const dexData = await dexResponse.json();
154
+
155
+ if (dexData && dexData[0]?.universe) {
156
+ const universe = dexData[0].universe as Array<{ name: string; szDecimals: number; maxLeverage: number; onlyIsolated?: boolean }>;
157
+ this.log(`Loading HIP-3 dex: ${dex.name} with ${universe.length} markets`);
158
+
159
+ universe.forEach((asset, assetIdx) => {
160
+ // API returns names already prefixed (e.g., "xyz:CL"), use as-is
161
+ const coinName = asset.name;
162
+ // Extract local name by stripping dex prefix if present
163
+ const localName = coinName.startsWith(dex.name + ':') ? coinName.slice(dex.name.length + 1) : coinName;
164
+ const globalIndex = 10000 * dexIdx + assetIdx;
165
+
166
+ this.assetMap.set(coinName, globalIndex);
167
+ this.szDecimalsMap.set(coinName, asset.szDecimals);
168
+ this.coinDexMap.set(coinName, { dexName: dex.name, dexIdx, localName });
169
+ });
170
+ }
171
+ } catch (e) {
172
+ this.log(`Failed to load HIP-3 dex ${dex.name}:`, e);
173
+ }
174
+ }
175
+ } catch (e) {
176
+ this.log('Failed to load HIP-3 assets:', e);
177
+ }
178
+ }
179
+
118
180
  async getAllMids(): Promise<Record<string, string>> {
119
181
  this.log('Fetching allMids...');
120
- const response = await this.info.allMids();
182
+ const response = await this.info.allMids() as Record<string, string>;
183
+
184
+ // Also fetch HIP-3 dex mids
185
+ try {
186
+ const dexs = await this.getPerpDexs();
187
+ const baseUrl = isMainnet()
188
+ ? 'https://api.hyperliquid.xyz'
189
+ : 'https://api.hyperliquid-testnet.xyz';
190
+
191
+ for (let i = 1; i < dexs.length; i++) {
192
+ const dex = dexs[i];
193
+ if (!dex) continue;
194
+
195
+ try {
196
+ const dexResponse = await fetch(baseUrl + '/info', {
197
+ method: 'POST',
198
+ headers: { 'Content-Type': 'application/json' },
199
+ body: JSON.stringify({ type: 'allMids', dex: dex.name }),
200
+ });
201
+ const dexMids = await dexResponse.json() as Record<string, string>;
202
+
203
+ // Merge directly — API already returns prefixed keys (e.g., "xyz:CL")
204
+ for (const [coin, mid] of Object.entries(dexMids)) {
205
+ response[coin] = mid;
206
+ }
207
+ } catch (e) {
208
+ this.log(`Failed to fetch mids for HIP-3 dex ${dex.name}:`, e);
209
+ }
210
+ }
211
+ } catch (e) {
212
+ this.log('Failed to fetch HIP-3 mids:', e);
213
+ }
214
+
121
215
  return response;
122
216
  }
123
217
 
@@ -130,6 +224,8 @@ export class HyperliquidClient {
130
224
  fullName: string;
131
225
  deployer: string;
132
226
  } | null>> {
227
+ if (this.perpDexsCache) return this.perpDexsCache;
228
+
133
229
  this.log('Fetching perpDexs...');
134
230
  const baseUrl = isMainnet()
135
231
  ? 'https://api.hyperliquid.xyz'
@@ -142,6 +238,7 @@ export class HyperliquidClient {
142
238
  });
143
239
  const data = await response.json();
144
240
  this.log('perpDexs response:', JSON.stringify(data).slice(0, 500));
241
+ this.perpDexsCache = data;
145
242
  return data;
146
243
  }
147
244
 
@@ -412,6 +509,7 @@ export class HyperliquidClient {
412
509
  spreadBps: number;
413
510
  }> {
414
511
  this.log('Fetching l2Book for:', coin);
512
+ // API accepts prefixed names directly (e.g., "xyz:CL")
415
513
  const response = await this.info.l2Book({ coin });
416
514
 
417
515
  const bids = response.levels[0] as Array<{ px: string; sz: string; n: number }>;
@@ -437,6 +535,15 @@ export class HyperliquidClient {
437
535
  getAssetIndex(coin: string): number {
438
536
  const index = this.assetMap.get(coin);
439
537
  if (index === undefined) {
538
+ // Check if bare name exists in HIP-3 dexes and suggest prefixed version
539
+ const hip3Matches = this.findHip3Matches(coin);
540
+ if (hip3Matches.length > 0) {
541
+ const suggestions = hip3Matches.map(m => `${m}`).join(', ');
542
+ throw new Error(
543
+ `Unknown asset: ${coin}. Did you mean one of these HIP-3 assets? ${suggestions}\n` +
544
+ `Use "openbroker search --query ${coin}" to find the full ticker.`
545
+ );
546
+ }
440
547
  throw new Error(`Unknown asset: ${coin}. Available: ${Array.from(this.assetMap.keys()).slice(0, 10).join(', ')}...`);
441
548
  }
442
549
  return index;
@@ -445,11 +552,78 @@ export class HyperliquidClient {
445
552
  getSzDecimals(coin: string): number {
446
553
  const decimals = this.szDecimalsMap.get(coin);
447
554
  if (decimals === undefined) {
555
+ const hip3Matches = this.findHip3Matches(coin);
556
+ if (hip3Matches.length > 0) {
557
+ throw new Error(
558
+ `Unknown asset: ${coin}. Did you mean: ${hip3Matches.join(', ')}?`
559
+ );
560
+ }
448
561
  throw new Error(`Unknown asset: ${coin}`);
449
562
  }
450
563
  return decimals;
451
564
  }
452
565
 
566
+ /**
567
+ * Find HIP-3 assets matching a bare coin name (without dex prefix)
568
+ */
569
+ private findHip3Matches(bareName: string): string[] {
570
+ const matches: string[] = [];
571
+ const upperName = bareName.toUpperCase();
572
+ for (const [key, info] of this.coinDexMap.entries()) {
573
+ if (info.dexName && info.localName.toUpperCase() === upperName) {
574
+ matches.push(key);
575
+ }
576
+ }
577
+ return matches;
578
+ }
579
+
580
+ /**
581
+ * Get the dex name for a coin (null for main dex assets)
582
+ */
583
+ getCoinDex(coin: string): string | null {
584
+ return this.coinDexMap.get(coin)?.dexName ?? null;
585
+ }
586
+
587
+ /**
588
+ * Get the local (unprefixed) coin name for API calls that need it
589
+ * e.g., "xyz:CL" → "CL", "ETH" → "ETH"
590
+ */
591
+ getCoinLocalName(coin: string): string {
592
+ return this.coinDexMap.get(coin)?.localName ?? coin;
593
+ }
594
+
595
+ /**
596
+ * Check if a coin is a HIP-3 asset
597
+ */
598
+ isHip3(coin: string): boolean {
599
+ return this.coinDexMap.get(coin)?.dexName != null;
600
+ }
601
+
602
+ /**
603
+ * Invalidate cached metadata so next call fetches fresh data.
604
+ * Useful for long-running strategies that need updated funding rates.
605
+ */
606
+ invalidateMetaCache(): void {
607
+ this.meta = null;
608
+ // Keep the asset/szDecimals/coinDex maps - they don't change
609
+ }
610
+
611
+ /**
612
+ * Get all loaded asset names (main + HIP-3)
613
+ */
614
+ getAllAssetNames(): string[] {
615
+ return Array.from(this.assetMap.keys());
616
+ }
617
+
618
+ /**
619
+ * Get all HIP-3 asset names
620
+ */
621
+ getHip3AssetNames(): string[] {
622
+ return Array.from(this.coinDexMap.entries())
623
+ .filter(([_, info]) => info.dexName !== null)
624
+ .map(([name]) => name);
625
+ }
626
+
453
627
  // ============ Account Info ============
454
628
 
455
629
  /**
@@ -783,6 +957,7 @@ export class HyperliquidClient {
783
957
  ? 'https://api.hyperliquid.xyz'
784
958
  : 'https://api.hyperliquid-testnet.xyz';
785
959
 
960
+ // API accepts prefixed names directly (e.g., "xyz:CL")
786
961
  const req: Record<string, unknown> = { coin, interval, startTime };
787
962
  if (endTime !== undefined) req.endTime = endTime;
788
963
 
@@ -814,6 +989,7 @@ export class HyperliquidClient {
814
989
  ? 'https://api.hyperliquid.xyz'
815
990
  : 'https://api.hyperliquid-testnet.xyz';
816
991
 
992
+ // API accepts prefixed names directly (e.g., "xyz:CL")
817
993
  const body: Record<string, unknown> = { type: 'fundingHistory', coin, startTime };
818
994
  if (endTime !== undefined) body.endTime = endTime;
819
995
 
@@ -844,10 +1020,13 @@ export class HyperliquidClient {
844
1020
  ? 'https://api.hyperliquid.xyz'
845
1021
  : 'https://api.hyperliquid-testnet.xyz';
846
1022
 
1023
+ // API accepts prefixed names directly (e.g., "xyz:CL")
1024
+ const body: Record<string, unknown> = { type: 'recentTrades', coin };
1025
+
847
1026
  const response = await fetch(baseUrl + '/info', {
848
1027
  method: 'POST',
849
1028
  headers: { 'Content-Type': 'application/json' },
850
- body: JSON.stringify({ type: 'recentTrades', coin }),
1029
+ body: JSON.stringify(body),
851
1030
  });
852
1031
  const data = await response.json();
853
1032
  this.log('recentTrades response length:', data?.length);
@@ -119,6 +119,18 @@ export function parseArgs(args: string[]): Record<string, string | boolean> {
119
119
  return result;
120
120
  }
121
121
 
122
+ /**
123
+ * Normalize a coin name: uppercase the asset, keep dex prefix lowercase.
124
+ * "eth" → "ETH", "xyz:cl" → "xyz:CL", "xyz:CL" → "xyz:CL"
125
+ */
126
+ export function normalizeCoin(coin: string): string {
127
+ const colonIdx = coin.indexOf(':');
128
+ if (colonIdx >= 0) {
129
+ return coin.slice(0, colonIdx).toLowerCase() + ':' + coin.slice(colonIdx + 1).toUpperCase();
130
+ }
131
+ return coin.toUpperCase();
132
+ }
133
+
122
134
  /**
123
135
  * Sleep for specified milliseconds
124
136
  */
@@ -2,7 +2,7 @@
2
2
  // View OHLCV candle data from Hyperliquid
3
3
 
4
4
  import { getClient } from '../core/client.js';
5
- import { formatUsd, parseArgs } from '../core/utils.js';
5
+ import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
6
6
 
7
7
  const VALID_INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '3d', '1w', '1M'];
8
8
 
@@ -61,13 +61,16 @@ async function main() {
61
61
  const bars = parseInt(args.bars as string) || 24;
62
62
  const client = getClient();
63
63
 
64
- console.log(`Open Broker - ${coin.toUpperCase()} Candles (${interval})`);
64
+ console.log(`Open Broker - ${normalizeCoin(coin)} Candles (${interval})`);
65
65
  console.log('='.repeat(40) + '\n');
66
66
 
67
67
  try {
68
+ // Load metadata (needed for HIP-3 coin resolution)
69
+ await client.getMetaAndAssetCtxs();
70
+
68
71
  const now = Date.now();
69
72
  const startTime = now - (bars * (INTERVAL_MS[interval] || 3_600_000));
70
- const candles = await client.getCandleSnapshot(coin.toUpperCase(), interval, startTime);
73
+ const candles = await client.getCandleSnapshot(normalizeCoin(coin), interval, startTime);
71
74
 
72
75
  if (candles.length === 0) {
73
76
  console.log('No candle data found');
@@ -2,7 +2,7 @@
2
2
  // View trade fill history from Hyperliquid
3
3
 
4
4
  import { getClient } from '../core/client.js';
5
- import { formatUsd, parseArgs } from '../core/utils.js';
5
+ import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
6
6
 
7
7
  function printUsage() {
8
8
  console.log(`
@@ -41,7 +41,7 @@ async function main() {
41
41
  let fills = await client.getUserFills();
42
42
 
43
43
  if (filterCoin) {
44
- fills = fills.filter(f => f.coin === filterCoin.toUpperCase());
44
+ fills = fills.filter(f => f.coin === normalizeCoin(filterCoin));
45
45
  }
46
46
  if (filterSide) {
47
47
  const sideCode = filterSide.toLowerCase() === 'buy' ? 'B' : 'A';
@@ -2,7 +2,7 @@
2
2
  // View historical funding rates for an asset on Hyperliquid
3
3
 
4
4
  import { getClient } from '../core/client.js';
5
- import { formatPercent, annualizeFundingRate, parseArgs } from '../core/utils.js';
5
+ import { formatPercent, annualizeFundingRate, parseArgs, normalizeCoin } from '../core/utils.js';
6
6
 
7
7
  function printUsage() {
8
8
  console.log(`
@@ -37,13 +37,16 @@ async function main() {
37
37
  const hours = parseInt(args.hours as string) || 24;
38
38
  const client = getClient();
39
39
 
40
- console.log(`Open Broker - ${coin.toUpperCase()} Funding History (${hours}h)`);
40
+ console.log(`Open Broker - ${normalizeCoin(coin)} Funding History (${hours}h)`);
41
41
  console.log('='.repeat(40) + '\n');
42
42
 
43
43
  try {
44
+ // Load metadata (needed for HIP-3 coin resolution)
45
+ await client.getMetaAndAssetCtxs();
46
+
44
47
  const now = Date.now();
45
48
  const startTime = now - (hours * 3_600_000);
46
- const history = await client.getFundingHistory(coin.toUpperCase(), startTime);
49
+ const history = await client.getFundingHistory(normalizeCoin(coin), startTime);
47
50
 
48
51
  if (history.length === 0) {
49
52
  console.log('No funding history found');