openbroker 1.0.82 → 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.
- package/SKILL.md +79 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/auto/cli.ts +1 -0
- package/scripts/auto/dashboard-forwarder.ts +77 -0
- package/scripts/auto/loader.ts +4 -2
- package/scripts/auto/runtime.ts +13 -2
- package/scripts/core/config.ts +1 -1
- package/scripts/info/all-markets.ts +51 -24
- package/scripts/info/candles.ts +13 -2
- package/scripts/info/fees.ts +16 -5
- package/scripts/info/funding-history.ts +13 -2
- package/scripts/info/funding-scan.ts +6 -0
- package/scripts/info/funding.ts +6 -0
- package/scripts/info/markets.ts +5 -0
- package/scripts/info/order-status.ts +11 -2
- package/scripts/info/rate-limit.ts +11 -2
- package/scripts/info/search-markets.ts +30 -10
- package/scripts/info/spot.ts +25 -5
- package/scripts/info/trades.ts +13 -2
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring and
|
|
|
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.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.85", "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
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_spot_buy ob_spot_sell ob_twap ob_twap_cancel ob_twap_status ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -46,28 +46,52 @@ Or with the `ob_search` plugin tool: `{ "query": "gold" }` or `{ "query": "oil",
|
|
|
46
46
|
|
|
47
47
|
**HIP-3 assets use `dex:COIN` format** — e.g., `xyz:CL` not just `CL`. If you get an error like "No market data found", search for the asset to find the correct prefixed ticker. Common HIP-3 dexes: `xyz`, `flx`, `km`, `hyna`, `vntl`, `cash`.
|
|
48
48
|
|
|
49
|
+
### Asset IDs (disambiguation)
|
|
50
|
+
|
|
51
|
+
Every info JSON output includes an `assetId` field — the canonical Hyperliquid asset index. Prefer it over the coin name when persisting references, because the same ticker can exist on multiple providers (e.g. `HYPE` perp, `hyna:HYPE` HIP-3, and `HYPE/USDC` spot all coexist).
|
|
52
|
+
|
|
53
|
+
| Scope | Formula | Example |
|
|
54
|
+
|-------|---------|---------|
|
|
55
|
+
| Main perps | universe index | `HYPE` → `159` |
|
|
56
|
+
| HIP-3 perps | `100000 + dexIdx * 10000 + assetIdx` | `hyna:HYPE` → `140002` |
|
|
57
|
+
| Spot | `10000 + pair.index` | `HYPE/USDC` → `10107` |
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
openbroker search HYPE --json | jq '.[] | {coin, assetId, type, provider}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Trading commands still take `--coin <name>` (including HIP-3 `dex:COIN`) — `assetId` is for queries, comparisons, and agent state, not order placement.
|
|
64
|
+
|
|
49
65
|
## Troubleshooting: CLI Fallback
|
|
50
66
|
|
|
51
67
|
If an `ob_*` plugin tool returns unexpected errors, empty results, or crashes, **fall back to the equivalent CLI command** via Bash. The CLI and plugin tools share the same core code, but the CLI has more mature error handling and output.
|
|
52
68
|
|
|
69
|
+
**Every info command supports `--json`** for structured output. The table below covers the commands with dedicated plugin tools; any other info command (e.g. `spot`, `trades`, `fees`, `order-status`, `rate-limit`, `funding-history`, `all-markets`) can be run as `openbroker <command> --json` for the same effect.
|
|
70
|
+
|
|
53
71
|
| Plugin Tool | CLI Equivalent |
|
|
54
72
|
|-------------|---------------|
|
|
55
73
|
| `ob_account` | `openbroker account --json` |
|
|
56
74
|
| `ob_positions` | `openbroker positions --json` |
|
|
57
75
|
| `ob_funding` | `openbroker funding --json --include-hip3` |
|
|
58
76
|
| `ob_markets` | `openbroker markets --json --include-hip3` |
|
|
59
|
-
| `ob_search` | `openbroker search --query <QUERY
|
|
77
|
+
| `ob_search` | `openbroker search --query <QUERY> --json` |
|
|
78
|
+
| `ob_spot` | `openbroker spot --json` (or `--balances --json`) |
|
|
79
|
+
| `ob_fills` | `openbroker fills --json` |
|
|
80
|
+
| `ob_orders` | `openbroker orders --json` |
|
|
81
|
+
| `ob_order_status` | `openbroker order-status --oid <OID> --json` |
|
|
82
|
+
| `ob_fees` | `openbroker fees --json` |
|
|
83
|
+
| `ob_candles` | `openbroker candles --coin <COIN> --json` |
|
|
84
|
+
| `ob_funding_history` | `openbroker funding-history --coin <COIN> --json` |
|
|
85
|
+
| `ob_trades` | `openbroker trades --coin <COIN> --json` |
|
|
86
|
+
| `ob_rate_limit` | `openbroker rate-limit --json` |
|
|
87
|
+
| `ob_funding_scan` | `openbroker funding-scan --json` |
|
|
60
88
|
| `ob_buy` | `openbroker buy --coin <COIN> --size <SIZE>` |
|
|
61
89
|
| `ob_sell` | `openbroker sell --coin <COIN> --size <SIZE>` |
|
|
62
90
|
| `ob_limit` | `openbroker limit --coin <COIN> --side <SIDE> --size <SIZE> --price <PRICE>` |
|
|
63
91
|
| `ob_tpsl` | `openbroker tpsl --coin <COIN> --tp <PRICE> --sl <PRICE>` |
|
|
64
92
|
| `ob_cancel` | `openbroker cancel --all` or `--coin <COIN>` |
|
|
65
|
-
| `ob_fills` | `openbroker fills --json` |
|
|
66
|
-
| `ob_orders` | `openbroker orders --json` |
|
|
67
|
-
| `ob_funding_scan` | `openbroker funding-scan --json` |
|
|
68
|
-
| `ob_candles` | `openbroker candles --coin <COIN> --json` |
|
|
69
93
|
| `ob_auto_run` | `openbroker auto run <script> [--dry]` |
|
|
70
|
-
| `ob_auto_stop` |
|
|
94
|
+
| `ob_auto_stop` | `openbroker auto stop <id>` (or SIGINT if run in foreground) |
|
|
71
95
|
| `ob_auto_list` | `openbroker auto list` |
|
|
72
96
|
|
|
73
97
|
**When to use CLI fallback:**
|
|
@@ -137,12 +161,14 @@ openbroker positions --address 0xabc... # Another account's positions
|
|
|
137
161
|
```bash
|
|
138
162
|
openbroker funding --top 20 # Top 20 by funding rate
|
|
139
163
|
openbroker funding --coin ETH # Specific coin
|
|
164
|
+
openbroker funding --top 20 --json # JSON (includes assetId)
|
|
140
165
|
```
|
|
141
166
|
|
|
142
167
|
### Markets
|
|
143
168
|
```bash
|
|
144
169
|
openbroker markets --top 30 # Top 30 main perps
|
|
145
170
|
openbroker markets --coin BTC # Specific coin
|
|
171
|
+
openbroker markets --coin BTC --json # JSON (includes assetId)
|
|
146
172
|
```
|
|
147
173
|
|
|
148
174
|
### All Markets (Perps + Spot + HIP-3)
|
|
@@ -152,6 +178,7 @@ openbroker all-markets --type perp # Main perps only
|
|
|
152
178
|
openbroker all-markets --type hip3 # HIP-3 perps only
|
|
153
179
|
openbroker all-markets --type spot # Spot markets only
|
|
154
180
|
openbroker all-markets --top 20 # Top 20 by volume
|
|
181
|
+
openbroker all-markets --json # JSON (includes assetId)
|
|
155
182
|
```
|
|
156
183
|
|
|
157
184
|
### Search Markets (Find assets across providers)
|
|
@@ -159,6 +186,7 @@ openbroker all-markets --top 20 # Top 20 by volume
|
|
|
159
186
|
openbroker search --query GOLD # Find all GOLD markets
|
|
160
187
|
openbroker search --query BTC # Find BTC across all providers
|
|
161
188
|
openbroker search --query ETH --type perp # ETH perps only
|
|
189
|
+
openbroker search HYPE --json # JSON with assetId per result
|
|
162
190
|
```
|
|
163
191
|
|
|
164
192
|
### Spot Markets
|
|
@@ -168,6 +196,7 @@ openbroker spot --coin PURR # Show PURR market info
|
|
|
168
196
|
openbroker spot --balances # Show your spot balances
|
|
169
197
|
openbroker spot --balances --address 0xabc... # Another account's spot balances
|
|
170
198
|
openbroker spot --top 20 # Top 20 by volume
|
|
199
|
+
openbroker spot --json # JSON (includes assetId, base, quote)
|
|
171
200
|
```
|
|
172
201
|
|
|
173
202
|
### Trade Fills
|
|
@@ -193,12 +222,14 @@ openbroker orders --address 0xabc... --open # Another account's open orders
|
|
|
193
222
|
openbroker order-status --oid 123456789 # Check specific order
|
|
194
223
|
openbroker order-status --oid 0x1234... # By client order ID
|
|
195
224
|
openbroker order-status --oid 123456789 --address 0xabc... # On another account
|
|
225
|
+
openbroker order-status --oid 123456789 --json
|
|
196
226
|
```
|
|
197
227
|
|
|
198
228
|
### Fee Schedule
|
|
199
229
|
```bash
|
|
200
230
|
openbroker fees # Fee tier, rates, and volume
|
|
201
231
|
openbroker fees --address 0xabc... # Another account's fees
|
|
232
|
+
openbroker fees --json
|
|
202
233
|
```
|
|
203
234
|
|
|
204
235
|
### Candle Data (OHLCV)
|
|
@@ -206,23 +237,27 @@ openbroker fees --address 0xabc... # Another account's fees
|
|
|
206
237
|
openbroker candles --coin ETH # 24 hourly candles
|
|
207
238
|
openbroker candles --coin BTC --interval 4h --bars 48 # 48 four-hour bars
|
|
208
239
|
openbroker candles --coin SOL --interval 1d --bars 30 # 30 daily bars
|
|
240
|
+
openbroker candles --coin ETH --json # JSON (coin, assetId, interval, candles)
|
|
209
241
|
```
|
|
210
242
|
|
|
211
243
|
### Funding History
|
|
212
244
|
```bash
|
|
213
245
|
openbroker funding-history --coin ETH # Last 24h
|
|
214
246
|
openbroker funding-history --coin BTC --hours 168 # Last 7 days
|
|
247
|
+
openbroker funding-history --coin ETH --json # JSON (coin, assetId, history)
|
|
215
248
|
```
|
|
216
249
|
|
|
217
250
|
### Recent Trades (Tape)
|
|
218
251
|
```bash
|
|
219
252
|
openbroker trades --coin ETH # Last 30 trades
|
|
220
253
|
openbroker trades --coin BTC --top 50 # Last 50 trades
|
|
254
|
+
openbroker trades --coin ETH --json # JSON (coin, assetId, trades)
|
|
221
255
|
```
|
|
222
256
|
|
|
223
257
|
### Rate Limit
|
|
224
258
|
```bash
|
|
225
259
|
openbroker rate-limit # API usage and capacity
|
|
260
|
+
openbroker rate-limit --json
|
|
226
261
|
```
|
|
227
262
|
|
|
228
263
|
### Funding Rate Scanner (Cross-Dex)
|
|
@@ -231,6 +266,7 @@ openbroker funding-scan # Scan all dexes, >25% threshol
|
|
|
231
266
|
openbroker funding-scan --threshold 50 --pairs # Show opposing funding pairs
|
|
232
267
|
openbroker funding-scan --hip3-only --top 20 # HIP-3 only
|
|
233
268
|
openbroker funding-scan --watch --interval 120 # Re-scan every 2 minutes
|
|
269
|
+
openbroker funding-scan --json # JSON (includes assetId per result)
|
|
234
270
|
```
|
|
235
271
|
|
|
236
272
|
## Trading Commands
|
|
@@ -293,6 +329,27 @@ openbroker cancel --coin ETH # Cancel ETH orders only
|
|
|
293
329
|
openbroker cancel --oid 123456 # Cancel specific order
|
|
294
330
|
```
|
|
295
331
|
|
|
332
|
+
### Spot Orders
|
|
333
|
+
Spot trading uses a separate order path with its own asset indices (see Asset IDs section). Pass the base token symbol as `--coin` — quote is always USDC.
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# Market orders (shortcuts)
|
|
337
|
+
openbroker spot-buy --coin PURR --size 1000
|
|
338
|
+
openbroker spot-sell --coin HYPE --size 5
|
|
339
|
+
|
|
340
|
+
# Full form (specify --side)
|
|
341
|
+
openbroker spot-order --coin PURR --side buy --size 1000
|
|
342
|
+
|
|
343
|
+
# Limit orders (add --price)
|
|
344
|
+
openbroker spot-order --coin HYPE --side sell --size 50 --price 25.50
|
|
345
|
+
openbroker spot-order --coin PURR --side buy --size 500 --price 0.20 --tif Alo
|
|
346
|
+
|
|
347
|
+
# Preview without executing
|
|
348
|
+
openbroker spot-buy --coin PURR --size 500 --dry
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Spot flags:** `--coin`, `--side`, `--size`, `--price` (omit → market order), `--tif` (`Gtc`/`Ioc`/`Alo`, default `Gtc`), `--slippage` (bps, market orders only), `--dry`, `--verbose`.
|
|
352
|
+
|
|
296
353
|
## Advanced Execution
|
|
297
354
|
|
|
298
355
|
### TWAP (Native Exchange Order)
|
|
@@ -303,6 +360,9 @@ openbroker twap --coin ETH --side buy --size 1 --duration 30
|
|
|
303
360
|
# Sell 0.5 BTC over 2 hours without randomized timing
|
|
304
361
|
openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
|
|
305
362
|
|
|
363
|
+
# Reduce-only (close position with TWAP). Note: TWAP uses `--reduce-only`, not `--reduce`
|
|
364
|
+
openbroker twap --coin ETH --side sell --size 1 --duration 30 --reduce-only
|
|
365
|
+
|
|
306
366
|
# Cancel a running TWAP
|
|
307
367
|
openbroker twap-cancel --coin ETH --twap-id 77738308
|
|
308
368
|
|
|
@@ -407,6 +467,7 @@ Run `openbroker setup` to create the global config interactively.
|
|
|
407
467
|
| `HYPERLIQUID_PRIVATE_KEY` | Yes | Wallet private key (0x...) |
|
|
408
468
|
| `HYPERLIQUID_NETWORK` | No | `mainnet` (default) or `testnet` |
|
|
409
469
|
| `HYPERLIQUID_ACCOUNT_ADDRESS` | No | Master account address (required for API wallets) |
|
|
470
|
+
| `OB_DASHBOARD_URL` | No | Dashboard API URL for forwarding audit notes, metrics, and agent actions (e.g. `http://localhost:3001`) |
|
|
410
471
|
|
|
411
472
|
The builder fee (1 bps / 0.01%) is hardcoded and not configurable.
|
|
412
473
|
|
|
@@ -591,6 +652,8 @@ export default function(api) {
|
|
|
591
652
|
|
|
592
653
|
Automations now write a local audit trail automatically to `~/.openbroker/automation-audit.sqlite`. The runtime records run config, logs, state changes, write actions, order updates, fills, user events, and per-poll account snapshots so you can generate performance reports later.
|
|
593
654
|
|
|
655
|
+
**Dashboard Forwarding:** When `OB_DASHBOARD_URL` is set (e.g. `http://localhost:3001`), audit notes, metrics, and trade actions are automatically forwarded to the OpenBroker Vaults dashboard API in real time. The vault address is read from `HYPERSTABLE_VAULT_ADDRESS` or `VAULT`. Forwarding is fire-and-forget — it never blocks the automation or causes errors if the dashboard is unreachable.
|
|
656
|
+
|
|
594
657
|
### Events
|
|
595
658
|
|
|
596
659
|
| Event | Payload | When |
|
|
@@ -1053,10 +1116,14 @@ export default function(api) {
|
|
|
1053
1116
|
openbroker auto run my-strategy --dry # Test without trading
|
|
1054
1117
|
openbroker auto run ./funding-scalp.ts # Run from path
|
|
1055
1118
|
openbroker auto run my-strategy --poll 5000 # Poll every 5s
|
|
1119
|
+
openbroker auto run my-strategy --no-ws # Disable WebSocket, pure REST polling
|
|
1056
1120
|
openbroker auto run --example dca --set coin=HYPE --set amount=50 --dry # Run bundled example
|
|
1057
1121
|
openbroker auto examples # List bundled examples with config
|
|
1058
1122
|
openbroker auto list # Show available scripts
|
|
1059
1123
|
openbroker auto status # Show running automations
|
|
1124
|
+
openbroker auto stop <id> # Unregister an automation (won't auto-restart)
|
|
1125
|
+
openbroker auto report <id> # Read the local audit report (logs, trades, metrics) for an automation
|
|
1126
|
+
openbroker auto clean # Remove stale entries from the registry
|
|
1060
1127
|
```
|
|
1061
1128
|
|
|
1062
1129
|
**Plugin tools (for OpenClaw agents):**
|
|
@@ -1072,9 +1139,14 @@ openbroker auto status # Show running automations
|
|
|
1072
1139
|
| `--verbose` | Show debug output | false |
|
|
1073
1140
|
| `--id <name>` | Custom automation ID | filename |
|
|
1074
1141
|
| `--poll <ms>` | Poll interval in milliseconds | 10000 |
|
|
1142
|
+
| `--no-ws` | Disable WebSocket; fall back to REST-only polling | WebSocket on |
|
|
1075
1143
|
| `--example <name>` | Run a bundled example automation | - |
|
|
1076
1144
|
| `--set key=value` | Set config values (repeatable) | - |
|
|
1077
1145
|
|
|
1146
|
+
**Inspecting automations after they run:**
|
|
1147
|
+
- `openbroker auto report <id>` — reads the local SQLite audit trail at `~/.openbroker/automation-audit.sqlite` and prints a summary of logs, write actions, fills, PnL, and custom metrics recorded via `api.audit.record()` / `api.audit.metric()`. Use this to review what a strategy actually did.
|
|
1148
|
+
- `openbroker auto clean` — prunes registry entries for automations that are no longer running or whose script file is gone. Safe to run anytime.
|
|
1149
|
+
|
|
1078
1150
|
**Guidelines for agents writing automations:**
|
|
1079
1151
|
|
|
1080
1152
|
**Risk & Safety (mandatory):**
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/auto/cli.ts
CHANGED
|
@@ -32,6 +32,7 @@ Options (for run):
|
|
|
32
32
|
--verbose Show debug output
|
|
33
33
|
--id <name> Custom automation ID (default: filename)
|
|
34
34
|
--poll <ms> Poll interval in milliseconds (default: 10000)
|
|
35
|
+
--no-ws Disable WebSocket; fall back to REST-only polling
|
|
35
36
|
|
|
36
37
|
Scripts are loaded from:
|
|
37
38
|
1. Absolute or relative path
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard audit forwarder.
|
|
3
|
+
*
|
|
4
|
+
* When OB_DASHBOARD_URL is set, wraps the AutomationAudit API to also POST
|
|
5
|
+
* audit notes, metrics, and agent action logs to the ob-app backend.
|
|
6
|
+
*
|
|
7
|
+
* Fires HTTP requests in the background — never blocks the automation loop.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AutomationAudit } from './types.js';
|
|
11
|
+
|
|
12
|
+
const DASHBOARD_URL = process.env.OB_DASHBOARD_URL; // e.g. "http://localhost:3001"
|
|
13
|
+
const DASHBOARD_API_KEY = process.env.OB_DASHBOARD_API_KEY || '';
|
|
14
|
+
const VAULT_ADDRESS = process.env.HYPERSTABLE_VAULT_ADDRESS || process.env.VAULT || '';
|
|
15
|
+
|
|
16
|
+
function postJSON(path: string, body: unknown): void {
|
|
17
|
+
if (!DASHBOARD_URL || !VAULT_ADDRESS) return;
|
|
18
|
+
|
|
19
|
+
const url = `${DASHBOARD_URL}/api/vaults/${VAULT_ADDRESS.toLowerCase()}${path}`;
|
|
20
|
+
|
|
21
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
22
|
+
if (DASHBOARD_API_KEY) {
|
|
23
|
+
headers['Authorization'] = `Bearer ${DASHBOARD_API_KEY}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers,
|
|
29
|
+
body: JSON.stringify(body),
|
|
30
|
+
signal: AbortSignal.timeout(5_000),
|
|
31
|
+
}).catch(() => {
|
|
32
|
+
// Silently ignore — dashboard may be down, automation must not be affected.
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wrap an existing AutomationAudit to forward calls to the dashboard API.
|
|
38
|
+
* If OB_DASHBOARD_URL is not set, returns the original audit object unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function withDashboardForwarder(audit: AutomationAudit): AutomationAudit {
|
|
41
|
+
if (!DASHBOARD_URL || !VAULT_ADDRESS) return audit;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
record(kind: string, payload?: unknown): void {
|
|
45
|
+
audit.record(kind, payload);
|
|
46
|
+
postJSON('/audit/notes', {
|
|
47
|
+
category: kind,
|
|
48
|
+
label: typeof payload === 'object' && payload !== null && 'reason' in payload
|
|
49
|
+
? String((payload as Record<string, unknown>).reason)
|
|
50
|
+
: kind,
|
|
51
|
+
data: payload ?? {},
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
metric(name: string, value: number, tags?: Record<string, unknown>): void {
|
|
56
|
+
audit.metric(name, value, tags);
|
|
57
|
+
postJSON('/audit/metrics', {
|
|
58
|
+
name,
|
|
59
|
+
value,
|
|
60
|
+
tags: tags ?? {},
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Forward an agent action log to the dashboard.
|
|
68
|
+
* Call this from audited client wrappers or directly from automation code.
|
|
69
|
+
*/
|
|
70
|
+
export function forwardAgentAction(
|
|
71
|
+
action: string,
|
|
72
|
+
status: 'success' | 'error' | 'pending',
|
|
73
|
+
details: Record<string, unknown>,
|
|
74
|
+
txHash?: string,
|
|
75
|
+
): void {
|
|
76
|
+
postJSON('/agent/logs', { action, status, details, txHash });
|
|
77
|
+
}
|
package/scripts/auto/loader.ts
CHANGED
|
@@ -22,8 +22,10 @@ export function resolveScriptPath(nameOrPath: string): string {
|
|
|
22
22
|
return nameOrPath;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// Relative to cwd
|
|
26
|
-
|
|
25
|
+
// Relative to the user's original cwd (not the openbroker package root that
|
|
26
|
+
// `bin/openbroker.js` chdirs to before spawning tsx).
|
|
27
|
+
const userCwd = process.env.OPENBROKER_CWD || process.cwd();
|
|
28
|
+
const cwdPath = path.resolve(userCwd, nameOrPath);
|
|
27
29
|
if (existsSync(cwdPath)) return cwdPath;
|
|
28
30
|
|
|
29
31
|
// Relative to ~/.openbroker/automations/
|
package/scripts/auto/runtime.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { AutomationEventBus } from './events.js';
|
|
|
15
15
|
import { loadAutomation } from './loader.js';
|
|
16
16
|
import { registerAutomation, unregisterAutomation, getRegisteredAutomations as getRegisteredFromFile } from './registry.js';
|
|
17
17
|
import { createAutomationAudit, toSerializable, type AutomationAuditSink } from './audit.js';
|
|
18
|
+
import { withDashboardForwarder, forwardAgentAction } from './dashboard-forwarder.js';
|
|
18
19
|
import type {
|
|
19
20
|
AutomationAPI,
|
|
20
21
|
AutomationEventPayloads,
|
|
@@ -178,6 +179,11 @@ function createAuditedClient(
|
|
|
178
179
|
result,
|
|
179
180
|
dryRun,
|
|
180
181
|
});
|
|
182
|
+
forwardAgentAction(
|
|
183
|
+
prop,
|
|
184
|
+
'success',
|
|
185
|
+
{ args: toSerializable(args), result: toSerializable(result), dryRun },
|
|
186
|
+
);
|
|
181
187
|
return result;
|
|
182
188
|
} catch (error) {
|
|
183
189
|
audit.recordAction({
|
|
@@ -187,6 +193,11 @@ function createAuditedClient(
|
|
|
187
193
|
error,
|
|
188
194
|
dryRun,
|
|
189
195
|
});
|
|
196
|
+
forwardAgentAction(
|
|
197
|
+
prop,
|
|
198
|
+
'error',
|
|
199
|
+
{ args: toSerializable(args), error: String(error), dryRun },
|
|
200
|
+
);
|
|
190
201
|
throw error;
|
|
191
202
|
}
|
|
192
203
|
};
|
|
@@ -424,10 +435,10 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
424
435
|
|
|
425
436
|
// Build the API object
|
|
426
437
|
const publish = createAuditedPublish(createPublish(id, log, gatewayPort, hooksToken), audit);
|
|
427
|
-
const auditApi: AutomationAudit = {
|
|
438
|
+
const auditApi: AutomationAudit = withDashboardForwarder({
|
|
428
439
|
record: (kind: string, payload?: unknown) => audit.recordNote(kind, payload),
|
|
429
440
|
metric: (name: string, value: number, tags?: Record<string, unknown>) => audit.recordMetric(name, value, tags),
|
|
430
|
-
};
|
|
441
|
+
});
|
|
431
442
|
const api: AutomationAPI = {
|
|
432
443
|
client,
|
|
433
444
|
utils: { roundPrice, roundSize, sleep, normalizeCoin, formatUsd, formatPercent, annualizeFundingRate },
|
package/scripts/core/config.ts
CHANGED
|
@@ -112,7 +112,7 @@ export function loadConfig(): OpenBrokerConfig {
|
|
|
112
112
|
// Show warning once
|
|
113
113
|
if (!readOnlyWarningShown) {
|
|
114
114
|
readOnlyWarningShown = true;
|
|
115
|
-
console.
|
|
115
|
+
console.error('\x1b[33m⚠️ Not configured for trading. Run "openbroker setup" to enable trades.\x1b[0m\n');
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -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
|
-
|
|
95
|
+
if (!args.json) {
|
|
96
|
+
console.log('Fetching market data...\n');
|
|
97
|
+
}
|
|
80
98
|
|
|
81
|
-
const allMarkets:
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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();
|
package/scripts/info/candles.ts
CHANGED
|
@@ -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
|
-
|
|
65
|
-
|
|
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;
|
package/scripts/info/fees.ts
CHANGED
|
@@ -9,10 +9,13 @@ function printUsage() {
|
|
|
9
9
|
Usage: openbroker fees [options]
|
|
10
10
|
|
|
11
11
|
Options:
|
|
12
|
-
--
|
|
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
|
-
|
|
32
|
-
|
|
35
|
+
if (!jsonOutput) {
|
|
36
|
+
console.log('Open Broker - Fee Schedule');
|
|
37
|
+
console.log('=========================\n');
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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;
|
|
@@ -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,
|
package/scripts/info/funding.ts
CHANGED
|
@@ -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,
|
|
@@ -84,8 +86,12 @@ async function main() {
|
|
|
84
86
|
|
|
85
87
|
if (!showAll && openInterest < 1000) continue;
|
|
86
88
|
|
|
89
|
+
let assetId = -1;
|
|
90
|
+
try { assetId = client.getAssetIndex(coinName); } catch { /* not registered */ }
|
|
91
|
+
|
|
87
92
|
fundingData.push({
|
|
88
93
|
coin: coinName,
|
|
94
|
+
assetId,
|
|
89
95
|
hourlyRate,
|
|
90
96
|
annualizedRate,
|
|
91
97
|
premium: 0,
|
package/scripts/info/markets.ts
CHANGED
|
@@ -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,
|
|
@@ -86,9 +88,12 @@ async function main() {
|
|
|
86
88
|
const volume24h = parseFloat(ctx.dayNtlVlm);
|
|
87
89
|
const openInterest = parseFloat(ctx.openInterest);
|
|
88
90
|
const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0;
|
|
91
|
+
let assetId = -1;
|
|
92
|
+
try { assetId = client.getAssetIndex(coinName); } catch { /* not registered */ }
|
|
89
93
|
|
|
90
94
|
markets.push({
|
|
91
95
|
coin: coinName,
|
|
96
|
+
assetId,
|
|
92
97
|
markPx,
|
|
93
98
|
oraclePx,
|
|
94
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
|
-
|
|
44
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
30
|
-
|
|
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;
|
|
@@ -7,6 +7,7 @@ interface Args {
|
|
|
7
7
|
query: string;
|
|
8
8
|
type?: 'perp' | 'spot' | 'hip3' | 'all';
|
|
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
|
}
|
|
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
|
Search Markets - Find assets across all Hyperliquid markets
|
|
@@ -32,6 +35,7 @@ Usage: npx tsx scripts/info/search-markets.ts --query <search> [options]
|
|
|
32
35
|
Options:
|
|
33
36
|
--query <search> Search term (required) - matches coin name
|
|
34
37
|
--type <type> Filter by market type: perp, spot, hip3, or all (default: all)
|
|
38
|
+
--json Output as JSON (machine-readable)
|
|
35
39
|
--verbose Show detailed output
|
|
36
40
|
--help Show this help
|
|
37
41
|
|
|
@@ -40,6 +44,7 @@ Examples:
|
|
|
40
44
|
npx tsx scripts/info/search-markets.ts --query BTC # Find all BTC markets
|
|
41
45
|
npx tsx scripts/info/search-markets.ts --query ETH --type perp # ETH perps only
|
|
42
46
|
npx tsx scripts/info/search-markets.ts --query PURR --type spot # PURR spot only
|
|
47
|
+
npx tsx scripts/info/search-markets.ts --query HYPE --json # JSON output
|
|
43
48
|
`);
|
|
44
49
|
process.exit(0);
|
|
45
50
|
} else if (!args.query && !arg.startsWith('-')) {
|
|
@@ -85,18 +90,23 @@ async function main() {
|
|
|
85
90
|
client.verbose = args.verbose ?? false;
|
|
86
91
|
|
|
87
92
|
const query = args.query.toUpperCase();
|
|
88
|
-
|
|
93
|
+
if (!args.json) {
|
|
94
|
+
console.log(`Searching for "${query}" across all markets...\n`);
|
|
95
|
+
}
|
|
89
96
|
|
|
90
|
-
|
|
97
|
+
interface Result {
|
|
91
98
|
type: 'perp' | 'spot' | 'hip3';
|
|
92
99
|
provider: string;
|
|
93
100
|
coin: string;
|
|
101
|
+
assetId: number;
|
|
94
102
|
price: string;
|
|
95
103
|
volume24h: number;
|
|
96
104
|
funding?: string;
|
|
97
105
|
maxLeverage?: number;
|
|
98
106
|
openInterest?: string;
|
|
99
|
-
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const results: Result[] = [];
|
|
100
110
|
|
|
101
111
|
// Search main perps
|
|
102
112
|
if (args.type === 'all' || args.type === 'perp') {
|
|
@@ -110,6 +120,7 @@ async function main() {
|
|
|
110
120
|
type: 'perp',
|
|
111
121
|
provider: 'Hyperliquid',
|
|
112
122
|
coin: asset.name,
|
|
123
|
+
assetId: i,
|
|
113
124
|
price: ctx.markPx,
|
|
114
125
|
volume24h: parseFloat(ctx.dayNtlVlm),
|
|
115
126
|
funding: ctx.funding,
|
|
@@ -126,7 +137,7 @@ async function main() {
|
|
|
126
137
|
// On testnet, load specific dex if query is "dex:COIN" format
|
|
127
138
|
if (args.query.includes(':')) {
|
|
128
139
|
await client.loadSingleHip3Dex(args.query.split(':')[0]);
|
|
129
|
-
} else {
|
|
140
|
+
} else if (!args.json) {
|
|
130
141
|
console.log(' (Testnet: HIP-3 dexes not auto-loaded. Use "dexName:COIN" to search a specific dex.)\n');
|
|
131
142
|
}
|
|
132
143
|
}
|
|
@@ -143,10 +154,13 @@ async function main() {
|
|
|
143
154
|
if (!asset || !ctx) continue;
|
|
144
155
|
|
|
145
156
|
if (asset.name.toUpperCase().includes(query)) {
|
|
157
|
+
let assetId = -1;
|
|
158
|
+
try { assetId = client.getAssetIndex(asset.name); } catch { /* not registered */ }
|
|
146
159
|
results.push({
|
|
147
160
|
type: 'hip3',
|
|
148
161
|
provider: dexData.dexName || `HIP-3 DEX ${dexIdx}`,
|
|
149
162
|
coin: asset.name,
|
|
163
|
+
assetId,
|
|
150
164
|
price: ctx.markPx,
|
|
151
165
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
152
166
|
funding: ctx.funding,
|
|
@@ -199,6 +213,7 @@ async function main() {
|
|
|
199
213
|
type: 'spot',
|
|
200
214
|
provider: 'Spot',
|
|
201
215
|
coin: displayName,
|
|
216
|
+
assetId: 10000 + pair.index,
|
|
202
217
|
price: ctx.markPx,
|
|
203
218
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
204
219
|
});
|
|
@@ -212,20 +227,25 @@ async function main() {
|
|
|
212
227
|
// Sort by volume
|
|
213
228
|
results.sort((a, b) => b.volume24h - a.volume24h);
|
|
214
229
|
|
|
230
|
+
if (args.json) {
|
|
231
|
+
console.log(JSON.stringify(results, null, 2));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
if (results.length === 0) {
|
|
216
236
|
console.log(`No markets found matching "${query}"`);
|
|
217
237
|
return;
|
|
218
238
|
}
|
|
219
239
|
|
|
220
240
|
console.log(`Found ${results.length} market(s) matching "${query}":\n`);
|
|
221
|
-
console.log('Type Provider Coin Price 24h Volume Funding (Ann.) OI');
|
|
222
|
-
console.log('-'.repeat(
|
|
241
|
+
console.log('Type Provider Coin AssetID Price 24h Volume Funding (Ann.) OI');
|
|
242
|
+
console.log('-'.repeat(112));
|
|
223
243
|
|
|
224
244
|
for (const m of results) {
|
|
225
245
|
const typeStr = m.type === 'hip3' ? 'HIP-3' : m.type.charAt(0).toUpperCase() + m.type.slice(1);
|
|
226
246
|
const oi = m.openInterest ? formatVolume(parseFloat(m.openInterest)) : '-';
|
|
227
247
|
console.log(
|
|
228
|
-
`${typeStr.padEnd(8)} ${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)} ${oi.padStart(10)}`
|
|
248
|
+
`${typeStr.padEnd(8)} ${m.provider.padEnd(16)} ${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)} ${oi.padStart(10)}`
|
|
229
249
|
);
|
|
230
250
|
}
|
|
231
251
|
|
|
@@ -245,11 +265,11 @@ async function main() {
|
|
|
245
265
|
const perpsWithFunding = markets.filter((m) => m.funding && m.type !== 'spot');
|
|
246
266
|
if (perpsWithFunding.length > 1) {
|
|
247
267
|
console.log(`\n=== ${coin} Funding Comparison ===\n`);
|
|
248
|
-
console.log('Provider Coin Funding (Ann.) Price');
|
|
249
|
-
console.log('-'.repeat(
|
|
268
|
+
console.log('Provider Coin AssetID Funding (Ann.) Price');
|
|
269
|
+
console.log('-'.repeat(78));
|
|
250
270
|
for (const m of perpsWithFunding.sort((a, b) => parseFloat(b.funding!) - parseFloat(a.funding!))) {
|
|
251
271
|
console.log(
|
|
252
|
-
`${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${formatFunding(m.funding!).padStart(14)} ${formatPrice(m.price)}`
|
|
272
|
+
`${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatFunding(m.funding!).padStart(14)} ${formatPrice(m.price)}`
|
|
253
273
|
);
|
|
254
274
|
}
|
|
255
275
|
}
|
package/scripts/info/spot.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface Args {
|
|
|
9
9
|
top?: number;
|
|
10
10
|
verbose?: boolean;
|
|
11
11
|
address?: string;
|
|
12
|
+
json?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function parseArgs(): Args {
|
|
@@ -26,6 +27,8 @@ function parseArgs(): Args {
|
|
|
26
27
|
args.address = process.argv[++i].toLowerCase();
|
|
27
28
|
} else if (arg === '--verbose') {
|
|
28
29
|
args.verbose = true;
|
|
30
|
+
} else if (arg === '--json') {
|
|
31
|
+
args.json = true;
|
|
29
32
|
} else if (arg === '--help' || arg === '-h') {
|
|
30
33
|
console.log(`
|
|
31
34
|
Spot Markets - View Hyperliquid spot markets and balances
|
|
@@ -37,6 +40,7 @@ Options:
|
|
|
37
40
|
--balances Show your spot token balances
|
|
38
41
|
--address <0x...> Look up another account's spot balances (with --balances)
|
|
39
42
|
--top <n> Show only top N markets by volume
|
|
43
|
+
--json Output as JSON (machine-readable)
|
|
40
44
|
--verbose Show detailed output
|
|
41
45
|
--help Show this help
|
|
42
46
|
|
|
@@ -85,10 +89,15 @@ async function main() {
|
|
|
85
89
|
// Show balances
|
|
86
90
|
if (args.balances) {
|
|
87
91
|
const lookupAddress = args.address ?? client.address;
|
|
88
|
-
console.log(`Fetching spot balances for ${lookupAddress}...\n`);
|
|
92
|
+
if (!args.json) console.log(`Fetching spot balances for ${lookupAddress}...\n`);
|
|
89
93
|
|
|
90
94
|
const balances = await client.getSpotBalances(args.address);
|
|
91
95
|
|
|
96
|
+
if (args.json) {
|
|
97
|
+
console.log(JSON.stringify(balances.balances ?? [], null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
if (!balances.balances || balances.balances.length === 0) {
|
|
93
102
|
console.log('No spot token balances found.');
|
|
94
103
|
return;
|
|
@@ -112,17 +121,20 @@ async function main() {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
// Show markets
|
|
115
|
-
console.log('Fetching spot market data...\n');
|
|
124
|
+
if (!args.json) console.log('Fetching spot market data...\n');
|
|
116
125
|
|
|
117
126
|
const spotData = await client.getSpotMetaAndAssetCtxs();
|
|
118
127
|
|
|
119
128
|
interface SpotMarket {
|
|
120
129
|
name: string;
|
|
121
130
|
index: number;
|
|
131
|
+
assetId: number;
|
|
122
132
|
price: string;
|
|
123
133
|
volume24h: number;
|
|
124
134
|
change24h: string;
|
|
125
135
|
tokens: [number, number];
|
|
136
|
+
base?: string;
|
|
137
|
+
quote?: string;
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
const markets: SpotMarket[] = [];
|
|
@@ -155,10 +167,13 @@ async function main() {
|
|
|
155
167
|
markets.push({
|
|
156
168
|
name: pair.name,
|
|
157
169
|
index: pair.index,
|
|
170
|
+
assetId: 10000 + pair.index,
|
|
158
171
|
price: ctx.markPx,
|
|
159
172
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
160
173
|
change24h: formatChange(ctx.markPx, ctx.prevDayPx),
|
|
161
174
|
tokens: pair.tokens,
|
|
175
|
+
base: tokenNameMap.get(pair.tokens[0]),
|
|
176
|
+
quote: tokenNameMap.get(pair.tokens[1]),
|
|
162
177
|
});
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -168,6 +183,11 @@ async function main() {
|
|
|
168
183
|
// Apply top filter
|
|
169
184
|
const displayMarkets = args.top ? markets.slice(0, args.top) : markets;
|
|
170
185
|
|
|
186
|
+
if (args.json) {
|
|
187
|
+
console.log(JSON.stringify(displayMarkets, null, 2));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
if (displayMarkets.length === 0) {
|
|
172
192
|
console.log(args.coin ? `No spot markets found for "${args.coin}"` : 'No spot markets found');
|
|
173
193
|
return;
|
|
@@ -180,8 +200,8 @@ async function main() {
|
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
console.log(`=== Spot Markets (${displayMarkets.length} total) ===\n`);
|
|
183
|
-
console.log('Pair Price 24h Volume 24h Change Base/Quote');
|
|
184
|
-
console.log('-'.repeat(
|
|
203
|
+
console.log('Pair AssetID Price 24h Volume 24h Change Base/Quote');
|
|
204
|
+
console.log('-'.repeat(92));
|
|
185
205
|
|
|
186
206
|
for (const m of displayMarkets) {
|
|
187
207
|
const baseToken = tokenMap.get(m.tokens[0]);
|
|
@@ -189,7 +209,7 @@ async function main() {
|
|
|
189
209
|
const pairStr = `${baseToken?.name || '?'}/${quoteToken?.name || '?'}`;
|
|
190
210
|
|
|
191
211
|
console.log(
|
|
192
|
-
`${m.name.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${m.change24h.padStart(11)} ${pairStr}`
|
|
212
|
+
`${m.name.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${m.change24h.padStart(11)} ${pairStr}`
|
|
193
213
|
);
|
|
194
214
|
}
|
|
195
215
|
|
package/scripts/info/trades.ts
CHANGED
|
@@ -11,6 +11,7 @@ Usage: openbroker trades --coin <symbol> [options]
|
|
|
11
11
|
Options:
|
|
12
12
|
--coin <symbol> Asset symbol (required, e.g. ETH, BTC)
|
|
13
13
|
--top <n> Show last N trades (default: 30)
|
|
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 top = parseInt(args.top as string) || 30;
|
|
39
|
+
const jsonOutput = args.json as boolean;
|
|
38
40
|
const client = getClient();
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
if (!jsonOutput) {
|
|
43
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Recent Trades`);
|
|
44
|
+
console.log('='.repeat(40) + '\n');
|
|
45
|
+
}
|
|
42
46
|
|
|
43
47
|
try {
|
|
44
48
|
// Load metadata (needed for HIP-3 coin resolution)
|
|
@@ -53,6 +57,13 @@ async function main() {
|
|
|
53
57
|
trades.sort((a, b) => b.time - a.time);
|
|
54
58
|
trades = trades.slice(0, top);
|
|
55
59
|
|
|
60
|
+
if (jsonOutput) {
|
|
61
|
+
let assetId = -1;
|
|
62
|
+
try { assetId = client.getAssetIndex(normalizeCoin(coin)); } catch { /* noop */ }
|
|
63
|
+
console.log(JSON.stringify({ coin: normalizeCoin(coin), assetId, trades }, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
if (trades.length === 0) {
|
|
57
68
|
console.log('No recent trades found');
|
|
58
69
|
return;
|