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 +26 -0
- package/README.md +17 -4
- package/SKILL.md +34 -7
- package/bin/cli.ts +2 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/core/client.ts +182 -3
- package/scripts/core/utils.ts +12 -0
- package/scripts/info/candles.ts +6 -3
- package/scripts/info/fills.ts +2 -2
- package/scripts/info/funding-history.ts +6 -3
- package/scripts/info/funding-scan.ts +213 -0
- package/scripts/info/funding.ts +36 -0
- package/scripts/info/markets.ts +38 -0
- package/scripts/info/orders.ts +2 -2
- package/scripts/info/trades.ts +6 -3
- package/scripts/plugin/tools.ts +139 -16
- package/scripts/setup/onboard.ts +10 -8
- package/scripts/strategies/funding-arb.ts +43 -10
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
|
|
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
|
-
###
|
|
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
|
|
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:**
|
|
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.
|
|
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. **
|
|
44
|
-
2. **
|
|
45
|
-
3. **Generate API wallet**
|
|
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
|
-
###
|
|
50
|
+
### Fresh Wallet Setup (Recommended for Agents)
|
|
51
51
|
|
|
52
|
-
|
|
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
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
package/scripts/core/client.ts
CHANGED
|
@@ -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(
|
|
1029
|
+
body: JSON.stringify(body),
|
|
851
1030
|
});
|
|
852
1031
|
const data = await response.json();
|
|
853
1032
|
this.log('recentTrades response length:', data?.length);
|
package/scripts/core/utils.ts
CHANGED
|
@@ -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
|
*/
|
package/scripts/info/candles.ts
CHANGED
|
@@ -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
|
|
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
|
|
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');
|
package/scripts/info/fills.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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');
|