openbroker 1.1.1 → 1.2.0
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/README.md +54 -5
- package/bin/cli.ts +26 -2
- package/package.json +3 -1
- package/scripts/auto/runtime.ts +12 -3
- package/scripts/core/client.ts +358 -0
- package/scripts/core/types.ts +50 -0
- package/scripts/info/all-markets.ts +47 -4
- package/scripts/info/outcomes.ts +200 -0
- package/scripts/info/search-markets.ts +40 -5
- package/scripts/operations/outcome-order.ts +185 -0
package/README.md
CHANGED
|
@@ -106,36 +106,38 @@ openbroker markets --sort change --top 10 # Top movers
|
|
|
106
106
|
|
|
107
107
|
#### `all-markets` — All Markets
|
|
108
108
|
|
|
109
|
-
Browse all available markets across main perps, HIP-3 perps, and
|
|
109
|
+
Browse all available markets across main perps, HIP-3 perps, spot, and HIP-4 outcomes — grouped by type.
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
openbroker all-markets # Everything
|
|
113
113
|
openbroker all-markets --type perp # Main perps only
|
|
114
114
|
openbroker all-markets --type hip3 # HIP-3 perps only
|
|
115
115
|
openbroker all-markets --type spot # Spot only
|
|
116
|
+
openbroker all-markets --type outcome # HIP-4 outcomes only
|
|
116
117
|
openbroker all-markets --top 20 # Top 20 by volume
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
| Flag | Description | Default |
|
|
120
121
|
|------|-------------|---------|
|
|
121
|
-
| `--type` | Filter: `perp`, `spot`, `hip3`, or `all` | `all` |
|
|
122
|
+
| `--type` | Filter: `perp`, `spot`, `hip3`, `outcome`, or `all` | `all` |
|
|
122
123
|
| `--top` | Limit to top N by volume | — |
|
|
123
124
|
| `--verbose` | Show detailed output | — |
|
|
124
125
|
|
|
125
126
|
#### `search` — Search Markets
|
|
126
127
|
|
|
127
|
-
Search for assets by name across all providers (perps, HIP-3, spot). Shows funding comparison when an asset is listed on multiple venues.
|
|
128
|
+
Search for assets by name across all providers (perps, HIP-3, spot, HIP-4 outcomes). Shows funding comparison when an asset is listed on multiple venues.
|
|
128
129
|
|
|
129
130
|
```bash
|
|
130
131
|
openbroker search --query GOLD # Find all GOLD markets
|
|
131
132
|
openbroker search --query ETH --type perp # ETH perps only
|
|
132
133
|
openbroker search --query PURR --type spot # PURR spot only
|
|
134
|
+
openbroker search --query BTC --type outcome # HIP-4 outcomes only
|
|
133
135
|
```
|
|
134
136
|
|
|
135
137
|
| Flag | Description | Default |
|
|
136
138
|
|------|-------------|---------|
|
|
137
139
|
| `--query` | Search term (matches coin name) | **required** |
|
|
138
|
-
| `--type` | Filter: `perp`, `spot`, `hip3`, or `all` | `all` |
|
|
140
|
+
| `--type` | Filter: `perp`, `spot`, `hip3`, `outcome`, or `all` | `all` |
|
|
139
141
|
| `--verbose` | Show detailed output | — |
|
|
140
142
|
|
|
141
143
|
#### `spot` — Spot Markets & Balances
|
|
@@ -154,6 +156,26 @@ openbroker spot --top 20 # Top 20 by volume
|
|
|
154
156
|
| `--top` | Limit to top N by volume | — |
|
|
155
157
|
| `--verbose` | Show token metadata | — |
|
|
156
158
|
|
|
159
|
+
#### `outcomes` — HIP-4 Outcome Markets
|
|
160
|
+
|
|
161
|
+
Search and inspect prediction/outcome markets. Outcome sides use Hyperliquid's encoded spot-like coin form: `#<encoding>`, where `encoding = 10 * outcomeId + side`; side `0` is usually YES and side `1` is usually NO.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
openbroker outcomes --query BTC
|
|
165
|
+
openbroker outcomes --outcome 123
|
|
166
|
+
openbroker outcomes --outcome 123 --side yes --json
|
|
167
|
+
openbroker outcomes --balances
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Flag | Description | Default |
|
|
171
|
+
|------|-------------|---------|
|
|
172
|
+
| `--query` | Search market name, description, underlying, expiry, or target price | — |
|
|
173
|
+
| `--outcome` | Outcome id, `#<encoding>`, or `+<encoding>` | — |
|
|
174
|
+
| `--side` | Outcome side for plain ids: `yes`, `no`, `0`, or `1` | `yes` |
|
|
175
|
+
| `--balances` | Show outcome token balances | — |
|
|
176
|
+
| `--top` | Limit to top N matches | — |
|
|
177
|
+
| `--json` | Machine-readable output | — |
|
|
178
|
+
|
|
157
179
|
#### `fills` — Trade Fill History
|
|
158
180
|
|
|
159
181
|
View your trade executions with prices, fees, and realized PnL.
|
|
@@ -379,6 +401,30 @@ openbroker cancel --all --dry # Preview what would be cancelled
|
|
|
379
401
|
| `--all` | Cancel all open orders | — |
|
|
380
402
|
| `--dry` | Show orders without cancelling | — |
|
|
381
403
|
|
|
404
|
+
#### `outcome-buy` / `outcome-sell` / `outcome-order` — HIP-4 Outcome Orders
|
|
405
|
+
|
|
406
|
+
Buy or sell a YES/NO outcome token. Buying opens exposure to that side; selling reduces or closes that token balance. Market orders are IOC limits with slippage protection.
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
openbroker outcomes --query BTC
|
|
410
|
+
openbroker outcome-buy --outcome 123 --outcome-side yes --size 10 --dry
|
|
411
|
+
openbroker outcome-buy --outcome 123 --outcome-side no --size 5 --price 0.42
|
|
412
|
+
openbroker outcome-sell --outcome #1230 --size 10
|
|
413
|
+
openbroker outcome-order --outcome 123 --outcome-side yes --side buy --size 10
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
| Flag | Description | Default |
|
|
417
|
+
|------|-------------|---------|
|
|
418
|
+
| `--outcome` | Outcome id, `#<encoding>`, or `+<encoding>` | **required** |
|
|
419
|
+
| `--outcome-side` | `yes`, `no`, `0`, or `1` when using a plain outcome id | `yes` |
|
|
420
|
+
| `--side` | `buy` or `sell` (auto-set by shortcuts) | **required** |
|
|
421
|
+
| `--size` | Size in outcome token units | **required** |
|
|
422
|
+
| `--price` | Limit price between 0 and 1 (omit for market IOC) | market |
|
|
423
|
+
| `--tif` | Time in force for limit orders: `Gtc`, `Ioc`, `Alo` | `Gtc` |
|
|
424
|
+
| `--slippage` | Slippage tolerance in bps for market orders | `50` |
|
|
425
|
+
| `--sz-decimals` | Override size decimals if metadata omits token decimals | metadata / `0` |
|
|
426
|
+
| `--dry` | Preview without executing | — |
|
|
427
|
+
|
|
382
428
|
---
|
|
383
429
|
|
|
384
430
|
### Advanced Execution
|
|
@@ -742,8 +788,9 @@ When loaded, the plugin registers these agent tools:
|
|
|
742
788
|
| Info | `ob_candles` | OHLCV candle data for an asset |
|
|
743
789
|
| Info | `ob_trades` | Recent trades (tape) for an asset |
|
|
744
790
|
| Info | `ob_markets` | Market data (price, volume, OI) |
|
|
745
|
-
| Info | `ob_search` | Search assets across perps, HIP-3, and
|
|
791
|
+
| Info | `ob_search` | Search assets across perps, HIP-3, spot, and HIP-4 outcomes |
|
|
746
792
|
| Info | `ob_spot` | Spot markets and token balances |
|
|
793
|
+
| Info | `ob_outcomes` | HIP-4 outcome markets and outcome token balances |
|
|
747
794
|
| Info | `ob_rate_limit` | API rate limit usage and capacity |
|
|
748
795
|
| Trading | `ob_buy` | Market buy |
|
|
749
796
|
| Trading | `ob_sell` | Market sell |
|
|
@@ -751,6 +798,8 @@ When loaded, the plugin registers these agent tools:
|
|
|
751
798
|
| Trading | `ob_trigger` | Trigger order (TP/SL) |
|
|
752
799
|
| Trading | `ob_tpsl` | Set TP/SL on existing position |
|
|
753
800
|
| Trading | `ob_cancel` | Cancel orders |
|
|
801
|
+
| Trading | `ob_outcome_buy` | Buy a HIP-4 YES/NO outcome token |
|
|
802
|
+
| Trading | `ob_outcome_sell` | Sell or close a HIP-4 YES/NO outcome token |
|
|
754
803
|
| Advanced | `ob_twap` | Native TWAP order (exchange-managed) |
|
|
755
804
|
| Advanced | `ob_twap_cancel` | Cancel a running TWAP order |
|
|
756
805
|
| Advanced | `ob_twap_status` | View TWAP order history/status |
|
package/bin/cli.ts
CHANGED
|
@@ -20,7 +20,7 @@ const commands: Record<string, { script: string; description: string }> = {
|
|
|
20
20
|
'positions': { script: 'info/positions.ts', description: 'View open positions' },
|
|
21
21
|
'funding': { script: 'info/funding.ts', description: 'View funding rates' },
|
|
22
22
|
'markets': { script: 'info/markets.ts', description: 'View market data' },
|
|
23
|
-
'all-markets': { script: 'info/all-markets.ts', description: 'View all markets (perps, HIP-3, spot)' },
|
|
23
|
+
'all-markets': { script: 'info/all-markets.ts', description: 'View all markets (perps, HIP-3, spot, HIP-4)' },
|
|
24
24
|
'search': { script: 'info/search-markets.ts', description: 'Search for assets across providers' },
|
|
25
25
|
'spot': { script: 'info/spot.ts', description: 'View spot markets and balances' },
|
|
26
26
|
'fills': { script: 'info/fills.ts', description: 'View trade fill history' },
|
|
@@ -32,6 +32,7 @@ const commands: Record<string, { script: string; description: string }> = {
|
|
|
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
34
|
'funding-scan': { script: 'info/funding-scan.ts', description: 'Scan funding rates across all dexes' },
|
|
35
|
+
'outcomes': { script: 'info/outcomes.ts', description: 'Search and inspect HIP-4 outcome markets' },
|
|
35
36
|
|
|
36
37
|
// Operations
|
|
37
38
|
'buy': { script: 'operations/market-order.ts', description: 'Market buy order' },
|
|
@@ -50,6 +51,11 @@ const commands: Record<string, { script: string; description: string }> = {
|
|
|
50
51
|
'spot-buy': { script: 'operations/spot-order.ts', description: 'Spot buy order' },
|
|
51
52
|
'spot-sell': { script: 'operations/spot-order.ts', description: 'Spot sell order' },
|
|
52
53
|
'spot-order': { script: 'operations/spot-order.ts', description: 'Spot order (market or limit)' },
|
|
54
|
+
'outcome-buy': { script: 'operations/outcome-order.ts', description: 'Buy a HIP-4 outcome token' },
|
|
55
|
+
'outcome-sell': { script: 'operations/outcome-order.ts', description: 'Sell a HIP-4 outcome token' },
|
|
56
|
+
'outcome-open': { script: 'operations/outcome-order.ts', description: 'Open a HIP-4 outcome position' },
|
|
57
|
+
'outcome-close': { script: 'operations/outcome-order.ts', description: 'Close a HIP-4 outcome position' },
|
|
58
|
+
'outcome-order': { script: 'operations/outcome-order.ts', description: 'HIP-4 outcome order (market or limit)' },
|
|
53
59
|
|
|
54
60
|
// Automations
|
|
55
61
|
'auto': { script: 'auto/cli.ts', description: 'Run/manage trading automations' },
|
|
@@ -76,11 +82,12 @@ Info Commands:
|
|
|
76
82
|
candles View OHLCV candle data
|
|
77
83
|
trades View recent trades (tape) for an asset
|
|
78
84
|
markets View market data for main perps
|
|
79
|
-
all-markets View all markets (perps, HIP-3, spot)
|
|
85
|
+
all-markets View all markets (perps, HIP-3, spot, HIP-4)
|
|
80
86
|
search Search for assets across all providers
|
|
81
87
|
spot View spot markets and balances
|
|
82
88
|
rate-limit View API rate limit status
|
|
83
89
|
funding-scan Scan funding rates across all dexes (main + HIP-3)
|
|
90
|
+
outcomes Search and inspect HIP-4 outcome markets
|
|
84
91
|
|
|
85
92
|
Trading Commands:
|
|
86
93
|
buy Market buy order
|
|
@@ -96,6 +103,13 @@ Spot Trading:
|
|
|
96
103
|
spot-sell Spot sell order
|
|
97
104
|
spot-order Spot order (market or limit, specify --side)
|
|
98
105
|
|
|
106
|
+
HIP-4 Outcome Trading:
|
|
107
|
+
outcome-buy Buy a YES/NO outcome token
|
|
108
|
+
outcome-sell Sell a YES/NO outcome token
|
|
109
|
+
outcome-open Alias for outcome-buy
|
|
110
|
+
outcome-close Alias for outcome-sell
|
|
111
|
+
outcome-order Outcome order (market or limit, specify --side)
|
|
112
|
+
|
|
99
113
|
Advanced Execution:
|
|
100
114
|
twap Native TWAP order (exchange-managed)
|
|
101
115
|
twap-cancel Cancel a running TWAP order
|
|
@@ -127,6 +141,8 @@ Examples:
|
|
|
127
141
|
openbroker buy --coin ETH --size 0.1 # Market buy 0.1 ETH
|
|
128
142
|
openbroker limit --coin BTC --side buy --size 0.01 --price 60000
|
|
129
143
|
openbroker search --query GOLD # Find GOLD across providers
|
|
144
|
+
openbroker outcomes --query BTC # Find HIP-4 outcome markets
|
|
145
|
+
openbroker outcome-buy --outcome 123 --outcome-side yes --size 10 --dry
|
|
130
146
|
openbroker tpsl --coin HYPE --tp 40 --sl 30 # Set TP/SL on position
|
|
131
147
|
|
|
132
148
|
Documentation: https://github.com/aurracloud/open-broker
|
|
@@ -204,6 +220,14 @@ function main() {
|
|
|
204
220
|
runScript(commands['spot-order'].script, ['--side', 'sell', ...commandArgs]);
|
|
205
221
|
return;
|
|
206
222
|
}
|
|
223
|
+
if (command === 'outcome-buy' || command === 'outcome-open') {
|
|
224
|
+
runScript(commands['outcome-order'].script, ['--side', 'buy', ...commandArgs]);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (command === 'outcome-sell' || command === 'outcome-close') {
|
|
228
|
+
runScript(commands['outcome-order'].script, ['--side', 'sell', ...commandArgs]);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
207
231
|
|
|
208
232
|
// Handle version
|
|
209
233
|
if (command === '--version' || command === '-v') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"search-markets": "tsx scripts/info/search-markets.ts",
|
|
31
31
|
"spot": "tsx scripts/info/spot.ts",
|
|
32
32
|
"market-order": "tsx scripts/operations/market-order.ts",
|
|
33
|
+
"outcome-order": "tsx scripts/operations/outcome-order.ts",
|
|
33
34
|
"limit-order": "tsx scripts/operations/limit-order.ts",
|
|
34
35
|
"trigger-order": "tsx scripts/operations/trigger-order.ts",
|
|
35
36
|
"set-tpsl": "tsx scripts/operations/set-tpsl.ts",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"bracket": "tsx scripts/operations/bracket.ts",
|
|
42
43
|
"chase": "tsx scripts/operations/chase.ts",
|
|
43
44
|
"funding-scan": "tsx scripts/info/funding-scan.ts",
|
|
45
|
+
"outcomes": "tsx scripts/info/outcomes.ts",
|
|
44
46
|
"prepublishOnly": "npm run test:cli",
|
|
45
47
|
"test:cli": "node --import tsx bin/cli.ts --help"
|
|
46
48
|
},
|
package/scripts/auto/runtime.ts
CHANGED
|
@@ -53,12 +53,21 @@ async function loadConventionObservers(log: AutomationLogger): Promise<Automatio
|
|
|
53
53
|
const observer = typeof exported === 'function' ? exported() : exported;
|
|
54
54
|
if (observer && typeof observer === 'object') {
|
|
55
55
|
observers.push(observer as AutomationAuditObserver);
|
|
56
|
-
log.
|
|
56
|
+
log.info(`[audit-observer] loaded: ${name}`);
|
|
57
|
+
} else {
|
|
58
|
+
// Package resolved but its factory returned null/undefined — typically
|
|
59
|
+
// because required env (e.g. OB_DASHBOARD_URL) is missing. Surface this
|
|
60
|
+
// so operators don't silently lose telemetry.
|
|
61
|
+
log.warn(
|
|
62
|
+
`[audit-observer] ${name} resolved but factory returned no observer — check the package's required env vars`
|
|
63
|
+
);
|
|
57
64
|
}
|
|
58
65
|
} catch (err) {
|
|
59
66
|
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
|
60
|
-
if (code
|
|
61
|
-
log.
|
|
67
|
+
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
|
68
|
+
log.info(`[audit-observer] ${name} not installed — skipping (install it alongside openbroker to forward telemetry)`);
|
|
69
|
+
} else {
|
|
70
|
+
log.warn(`[audit-observer] failed to load "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
}
|
package/scripts/core/client.ts
CHANGED
|
@@ -13,6 +13,9 @@ import type {
|
|
|
13
13
|
AssetCtx,
|
|
14
14
|
ClearinghouseState,
|
|
15
15
|
OpenOrder,
|
|
16
|
+
OutcomeMetaResponse,
|
|
17
|
+
OutcomeMarket,
|
|
18
|
+
OutcomeQuestion,
|
|
16
19
|
} from './types.js';
|
|
17
20
|
import { loadConfig, isMainnet } from './config.js';
|
|
18
21
|
import { roundPrice, roundSize } from './utils.js';
|
|
@@ -51,6 +54,8 @@ export class HyperliquidClient {
|
|
|
51
54
|
private spotSzDecimalsMap: Map<string, number> = new Map();
|
|
52
55
|
/** Whether spot metadata has been loaded */
|
|
53
56
|
private spotMetaLoaded: boolean = false;
|
|
57
|
+
/** HIP-4 outcome metadata cache */
|
|
58
|
+
private outcomeMeta: OutcomeMetaResponse | null = null;
|
|
54
59
|
public verbose: boolean = false;
|
|
55
60
|
|
|
56
61
|
constructor(config?: OpenBrokerConfig) {
|
|
@@ -658,6 +663,247 @@ export class HyperliquidClient {
|
|
|
658
663
|
};
|
|
659
664
|
}
|
|
660
665
|
|
|
666
|
+
// ============ HIP-4 Outcomes ============
|
|
667
|
+
|
|
668
|
+
private parseOutcomeDescription(description: string): Record<string, string> {
|
|
669
|
+
const parsed: Record<string, string> = {};
|
|
670
|
+
for (const part of description.split('|')) {
|
|
671
|
+
const idx = part.indexOf(':');
|
|
672
|
+
if (idx <= 0) continue;
|
|
673
|
+
parsed[part.slice(0, idx)] = part.slice(idx + 1);
|
|
674
|
+
}
|
|
675
|
+
return parsed;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private normalizeOutcomeSide(side: string | number): 0 | 1 {
|
|
679
|
+
if (typeof side === 'number') {
|
|
680
|
+
if (side === 0 || side === 1) return side;
|
|
681
|
+
} else {
|
|
682
|
+
const normalized = side.trim().toLowerCase();
|
|
683
|
+
if (normalized === '0' || normalized === 'yes' || normalized === 'y') return 0;
|
|
684
|
+
if (normalized === '1' || normalized === 'no' || normalized === 'n') return 1;
|
|
685
|
+
}
|
|
686
|
+
throw new Error(`Invalid outcome side "${side}". Use yes/no or 0/1.`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
getOutcomeEncoding(outcome: number, side: 0 | 1): number {
|
|
690
|
+
return 10 * outcome + side;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
getOutcomeCoin(outcome: number, side: 0 | 1): string {
|
|
694
|
+
return `#${this.getOutcomeEncoding(outcome, side)}`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
getOutcomeAssetId(outcome: number, side: 0 | 1): number {
|
|
698
|
+
return 100_000_000 + this.getOutcomeEncoding(outcome, side);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
resolveOutcomeRef(ref: string | number, side?: string | number): {
|
|
702
|
+
outcome: number;
|
|
703
|
+
side: 0 | 1;
|
|
704
|
+
encoding: number;
|
|
705
|
+
coin: string;
|
|
706
|
+
tokenName: string;
|
|
707
|
+
assetId: number;
|
|
708
|
+
} {
|
|
709
|
+
if (typeof ref === 'number') {
|
|
710
|
+
const resolvedSide = this.normalizeOutcomeSide(side ?? 0);
|
|
711
|
+
const encoding = this.getOutcomeEncoding(ref, resolvedSide);
|
|
712
|
+
return {
|
|
713
|
+
outcome: ref,
|
|
714
|
+
side: resolvedSide,
|
|
715
|
+
encoding,
|
|
716
|
+
coin: `#${encoding}`,
|
|
717
|
+
tokenName: `+${encoding}`,
|
|
718
|
+
assetId: 100_000_000 + encoding,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const trimmed = ref.trim();
|
|
723
|
+
const encoded = trimmed.startsWith('#') || trimmed.startsWith('+')
|
|
724
|
+
? parseInt(trimmed.slice(1), 10)
|
|
725
|
+
: NaN;
|
|
726
|
+
|
|
727
|
+
if (!Number.isNaN(encoded)) {
|
|
728
|
+
const resolvedSide = encoded % 10;
|
|
729
|
+
if (resolvedSide !== 0 && resolvedSide !== 1) {
|
|
730
|
+
throw new Error(`Invalid outcome encoding "${ref}". Outcome side must encode to 0 or 1.`);
|
|
731
|
+
}
|
|
732
|
+
const outcome = Math.floor(encoded / 10);
|
|
733
|
+
return {
|
|
734
|
+
outcome,
|
|
735
|
+
side: resolvedSide as 0 | 1,
|
|
736
|
+
encoding: encoded,
|
|
737
|
+
coin: `#${encoded}`,
|
|
738
|
+
tokenName: `+${encoded}`,
|
|
739
|
+
assetId: 100_000_000 + encoded,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const outcome = parseInt(trimmed, 10);
|
|
744
|
+
if (!Number.isFinite(outcome) || outcome < 0) {
|
|
745
|
+
throw new Error(`Invalid outcome reference "${ref}". Use an outcome id, #<encoding>, or +<encoding>.`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const resolvedSide = this.normalizeOutcomeSide(side ?? 0);
|
|
749
|
+
const encoding = this.getOutcomeEncoding(outcome, resolvedSide);
|
|
750
|
+
return {
|
|
751
|
+
outcome,
|
|
752
|
+
side: resolvedSide,
|
|
753
|
+
encoding,
|
|
754
|
+
coin: `#${encoding}`,
|
|
755
|
+
tokenName: `+${encoding}`,
|
|
756
|
+
assetId: 100_000_000 + encoding,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async getOutcomeMeta(): Promise<OutcomeMetaResponse> {
|
|
761
|
+
if (this.outcomeMeta) return this.outcomeMeta;
|
|
762
|
+
|
|
763
|
+
this.log('Fetching outcomeMeta...');
|
|
764
|
+
const data = await this.postInfo<OutcomeMetaResponse>({ type: 'outcomeMeta' }, 'outcomeMeta');
|
|
765
|
+
if (!data || !Array.isArray(data.outcomes)) {
|
|
766
|
+
throw new Error('outcomeMeta returned empty/malformed payload.');
|
|
767
|
+
}
|
|
768
|
+
this.outcomeMeta = {
|
|
769
|
+
outcomes: data.outcomes,
|
|
770
|
+
questions: data.questions ?? [],
|
|
771
|
+
};
|
|
772
|
+
this.log(`Loaded ${this.outcomeMeta.outcomes.length} outcome markets`);
|
|
773
|
+
return this.outcomeMeta;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private async getOutcomeCtxMap(): Promise<Map<string, {
|
|
777
|
+
coin?: string;
|
|
778
|
+
dayNtlVlm?: string;
|
|
779
|
+
markPx?: string;
|
|
780
|
+
midPx?: string | null;
|
|
781
|
+
prevDayPx?: string;
|
|
782
|
+
}>> {
|
|
783
|
+
const ctxMap = new Map<string, {
|
|
784
|
+
coin?: string;
|
|
785
|
+
dayNtlVlm?: string;
|
|
786
|
+
markPx?: string;
|
|
787
|
+
midPx?: string | null;
|
|
788
|
+
prevDayPx?: string;
|
|
789
|
+
}>();
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
const spotData = await this.getSpotMetaAndAssetCtxs();
|
|
793
|
+
for (const ctx of spotData.assetCtxs) {
|
|
794
|
+
if (ctx.coin) ctxMap.set(ctx.coin, ctx);
|
|
795
|
+
}
|
|
796
|
+
} catch (e) {
|
|
797
|
+
this.log('Unable to load spot/outcome contexts:', e);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
try {
|
|
801
|
+
const mids = await this.getAllMids();
|
|
802
|
+
for (const [coin, midPx] of Object.entries(mids)) {
|
|
803
|
+
if (!coin.startsWith('#')) continue;
|
|
804
|
+
const existing = ctxMap.get(coin) ?? { coin };
|
|
805
|
+
existing.midPx = existing.midPx ?? midPx;
|
|
806
|
+
existing.markPx = existing.markPx ?? midPx;
|
|
807
|
+
ctxMap.set(coin, existing);
|
|
808
|
+
}
|
|
809
|
+
} catch (e) {
|
|
810
|
+
this.log('Unable to load allMids for outcomes:', e);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return ctxMap;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async getOutcomeMarkets(): Promise<OutcomeMarket[]> {
|
|
817
|
+
const [meta, spotMeta, ctxMap] = await Promise.all([
|
|
818
|
+
this.getOutcomeMeta(),
|
|
819
|
+
this.getSpotMeta().catch(() => null),
|
|
820
|
+
this.getOutcomeCtxMap(),
|
|
821
|
+
]);
|
|
822
|
+
|
|
823
|
+
const tokenDecimals = new Map<number, number>();
|
|
824
|
+
if (spotMeta) {
|
|
825
|
+
for (const token of spotMeta.tokens) {
|
|
826
|
+
tokenDecimals.set(token.index, token.szDecimals);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const questions = new Map<number, OutcomeQuestion>();
|
|
831
|
+
for (const question of meta.questions ?? []) {
|
|
832
|
+
for (const outcome of question.namedOutcomes) {
|
|
833
|
+
questions.set(outcome, question);
|
|
834
|
+
}
|
|
835
|
+
questions.set(question.fallbackOutcome, question);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return meta.outcomes.map((outcome) => {
|
|
839
|
+
const sides = outcome.sideSpecs.map((sideSpec, idx) => {
|
|
840
|
+
const side = idx as 0 | 1;
|
|
841
|
+
const encoding = this.getOutcomeEncoding(outcome.outcome, side);
|
|
842
|
+
const coin = `#${encoding}`;
|
|
843
|
+
const ctx = ctxMap.get(coin);
|
|
844
|
+
return {
|
|
845
|
+
side,
|
|
846
|
+
name: sideSpec.name,
|
|
847
|
+
encoding,
|
|
848
|
+
coin,
|
|
849
|
+
tokenName: `+${encoding}`,
|
|
850
|
+
assetId: 100_000_000 + encoding,
|
|
851
|
+
token: sideSpec.token,
|
|
852
|
+
szDecimals: sideSpec.token !== undefined ? tokenDecimals.get(sideSpec.token) : undefined,
|
|
853
|
+
midPx: ctx?.midPx ?? undefined,
|
|
854
|
+
markPx: ctx?.markPx,
|
|
855
|
+
prevDayPx: ctx?.prevDayPx,
|
|
856
|
+
dayNtlVlm: ctx?.dayNtlVlm,
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
return {
|
|
861
|
+
outcome: outcome.outcome,
|
|
862
|
+
name: outcome.name,
|
|
863
|
+
description: outcome.description,
|
|
864
|
+
parsedDescription: this.parseOutcomeDescription(outcome.description),
|
|
865
|
+
sides,
|
|
866
|
+
question: questions.get(outcome.outcome),
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async getOutcomeMarket(outcomeId: number): Promise<OutcomeMarket | null> {
|
|
872
|
+
const markets = await this.getOutcomeMarkets();
|
|
873
|
+
return markets.find((market) => market.outcome === outcomeId) ?? null;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async getOutcomeSzDecimals(outcome: number, side: 0 | 1): Promise<number> {
|
|
877
|
+
const market = await this.getOutcomeMarket(outcome);
|
|
878
|
+
return market?.sides.find((s) => s.side === side)?.szDecimals ?? 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async getOutcomeMidPrice(outcome: number, side: 0 | 1): Promise<number> {
|
|
882
|
+
const coin = this.getOutcomeCoin(outcome, side);
|
|
883
|
+
const markets = await this.getOutcomeMarkets();
|
|
884
|
+
const marketSide = markets
|
|
885
|
+
.find((market) => market.outcome === outcome)
|
|
886
|
+
?.sides.find((s) => s.side === side);
|
|
887
|
+
const fromMeta = marketSide?.midPx ?? marketSide?.markPx;
|
|
888
|
+
if (fromMeta) {
|
|
889
|
+
const mid = parseFloat(fromMeta);
|
|
890
|
+
if (mid > 0) return mid;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const mids = await this.getAllMids();
|
|
894
|
+
const mid = parseFloat(mids[coin] || '0');
|
|
895
|
+
if (mid > 0) return mid;
|
|
896
|
+
|
|
897
|
+
try {
|
|
898
|
+
const book = await this.getL2Book(coin);
|
|
899
|
+
if (book.midPrice > 0) return book.midPrice;
|
|
900
|
+
} catch (e) {
|
|
901
|
+
this.log(`Unable to fetch outcome L2 book for ${coin}:`, e);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
throw new Error(`No outcome price for ${coin}. The market may not be open or may have no liquidity.`);
|
|
905
|
+
}
|
|
906
|
+
|
|
661
907
|
/**
|
|
662
908
|
* Load spot metadata into lookup maps.
|
|
663
909
|
* Spot asset index for orders = 10000 + universe[i].index
|
|
@@ -2346,6 +2592,118 @@ export class HyperliquidClient {
|
|
|
2346
2592
|
);
|
|
2347
2593
|
}
|
|
2348
2594
|
|
|
2595
|
+
/**
|
|
2596
|
+
* Place a HIP-4 outcome order.
|
|
2597
|
+
* Outcome assets are spot-like, but encoded as:
|
|
2598
|
+
* encoding = 10 * outcome + side
|
|
2599
|
+
* assetId = 100_000_000 + encoding
|
|
2600
|
+
* coin = #<encoding>
|
|
2601
|
+
*
|
|
2602
|
+
* Side 0 is usually YES and side 1 is usually NO, per outcomeMeta.sideSpecs.
|
|
2603
|
+
*/
|
|
2604
|
+
async outcomeOrder(
|
|
2605
|
+
outcomeRef: string | number,
|
|
2606
|
+
outcomeSide: string | number | undefined,
|
|
2607
|
+
isBuy: boolean,
|
|
2608
|
+
size: number,
|
|
2609
|
+
price: number,
|
|
2610
|
+
orderType: { limit: { tif: 'Gtc' | 'Ioc' | 'Alo' } },
|
|
2611
|
+
includeBuilder: boolean = true,
|
|
2612
|
+
szDecimalsOverride?: number,
|
|
2613
|
+
): Promise<OrderResponse> {
|
|
2614
|
+
await this.requireTrading();
|
|
2615
|
+
|
|
2616
|
+
const resolved = this.resolveOutcomeRef(outcomeRef, outcomeSide);
|
|
2617
|
+
const szDecimals = szDecimalsOverride ?? await this.getOutcomeSzDecimals(resolved.outcome, resolved.side);
|
|
2618
|
+
|
|
2619
|
+
const orderWire = {
|
|
2620
|
+
a: resolved.assetId,
|
|
2621
|
+
b: isBuy,
|
|
2622
|
+
p: roundPrice(price, szDecimals, true),
|
|
2623
|
+
s: roundSize(size, szDecimals),
|
|
2624
|
+
r: false,
|
|
2625
|
+
t: orderType,
|
|
2626
|
+
};
|
|
2627
|
+
|
|
2628
|
+
this.log('Placing outcome order:', JSON.stringify({ resolved, orderWire }, null, 2));
|
|
2629
|
+
|
|
2630
|
+
const orderRequest: {
|
|
2631
|
+
orders: typeof orderWire[];
|
|
2632
|
+
grouping: 'na';
|
|
2633
|
+
builder?: BuilderInfo;
|
|
2634
|
+
} = {
|
|
2635
|
+
orders: [orderWire],
|
|
2636
|
+
grouping: 'na',
|
|
2637
|
+
};
|
|
2638
|
+
|
|
2639
|
+
if (includeBuilder && !this.isTestnet && this.config.builderAddress !== '0x0000000000000000000000000000000000000000') {
|
|
2640
|
+
orderRequest.builder = this.builderInfo;
|
|
2641
|
+
this.log('Including builder fee:', this.builderInfo);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
try {
|
|
2645
|
+
const response = await this.exchange.order(orderRequest, this.vaultParam);
|
|
2646
|
+
this.log('Outcome order response:', JSON.stringify(response, null, 2));
|
|
2647
|
+
return response as unknown as OrderResponse;
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
this.log('Outcome order error:', error);
|
|
2650
|
+
return {
|
|
2651
|
+
status: 'err',
|
|
2652
|
+
response: error instanceof Error ? error.message : String(error),
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
async outcomeMarketOrder(
|
|
2658
|
+
outcomeRef: string | number,
|
|
2659
|
+
outcomeSide: string | number | undefined,
|
|
2660
|
+
isBuy: boolean,
|
|
2661
|
+
size: number,
|
|
2662
|
+
slippageBps?: number,
|
|
2663
|
+
szDecimalsOverride?: number,
|
|
2664
|
+
): Promise<OrderResponse> {
|
|
2665
|
+
const resolved = this.resolveOutcomeRef(outcomeRef, outcomeSide);
|
|
2666
|
+
const midPrice = await this.getOutcomeMidPrice(resolved.outcome, resolved.side);
|
|
2667
|
+
const slippage = (slippageBps ?? this.config.slippageBps) / 10000;
|
|
2668
|
+
const limitPrice = isBuy
|
|
2669
|
+
? midPrice * (1 + slippage)
|
|
2670
|
+
: midPrice * (1 - slippage);
|
|
2671
|
+
|
|
2672
|
+
this.log(`Outcome market order: ${resolved.coin} ${isBuy ? 'BUY' : 'SELL'} ${size} @ ${limitPrice} (mid: ${midPrice})`);
|
|
2673
|
+
|
|
2674
|
+
return this.outcomeOrder(
|
|
2675
|
+
outcomeRef,
|
|
2676
|
+
outcomeSide,
|
|
2677
|
+
isBuy,
|
|
2678
|
+
size,
|
|
2679
|
+
limitPrice,
|
|
2680
|
+
{ limit: { tif: 'Ioc' } },
|
|
2681
|
+
true,
|
|
2682
|
+
szDecimalsOverride,
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
async outcomeLimitOrder(
|
|
2687
|
+
outcomeRef: string | number,
|
|
2688
|
+
outcomeSide: string | number | undefined,
|
|
2689
|
+
isBuy: boolean,
|
|
2690
|
+
size: number,
|
|
2691
|
+
price: number,
|
|
2692
|
+
tif: 'Gtc' | 'Ioc' | 'Alo' = 'Gtc',
|
|
2693
|
+
szDecimalsOverride?: number,
|
|
2694
|
+
): Promise<OrderResponse> {
|
|
2695
|
+
return this.outcomeOrder(
|
|
2696
|
+
outcomeRef,
|
|
2697
|
+
outcomeSide,
|
|
2698
|
+
isBuy,
|
|
2699
|
+
size,
|
|
2700
|
+
price,
|
|
2701
|
+
{ limit: { tif } },
|
|
2702
|
+
true,
|
|
2703
|
+
szDecimalsOverride,
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2349
2707
|
/**
|
|
2350
2708
|
* Cancel a spot order by coin and order ID.
|
|
2351
2709
|
*/
|
package/scripts/core/types.ts
CHANGED
|
@@ -119,6 +119,56 @@ export interface MetaAndAssetCtxs {
|
|
|
119
119
|
assetCtxs: AssetCtx[];
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// ============ Outcome / HIP-4 Types ============
|
|
123
|
+
|
|
124
|
+
export interface OutcomeSideSpec {
|
|
125
|
+
name: string;
|
|
126
|
+
token?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface OutcomeMetaEntry {
|
|
130
|
+
outcome: number;
|
|
131
|
+
name: string;
|
|
132
|
+
description: string;
|
|
133
|
+
sideSpecs: OutcomeSideSpec[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface OutcomeQuestion {
|
|
137
|
+
question: number;
|
|
138
|
+
name: string;
|
|
139
|
+
description: string;
|
|
140
|
+
fallbackOutcome: number;
|
|
141
|
+
namedOutcomes: number[];
|
|
142
|
+
settledNamedOutcomes?: number[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface OutcomeMetaResponse {
|
|
146
|
+
outcomes: OutcomeMetaEntry[];
|
|
147
|
+
questions?: OutcomeQuestion[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface OutcomeMarket {
|
|
151
|
+
outcome: number;
|
|
152
|
+
name: string;
|
|
153
|
+
description: string;
|
|
154
|
+
parsedDescription: Record<string, string>;
|
|
155
|
+
sides: Array<{
|
|
156
|
+
side: 0 | 1;
|
|
157
|
+
name: string;
|
|
158
|
+
encoding: number;
|
|
159
|
+
coin: string;
|
|
160
|
+
tokenName: string;
|
|
161
|
+
assetId: number;
|
|
162
|
+
token?: number;
|
|
163
|
+
szDecimals?: number;
|
|
164
|
+
midPx?: string;
|
|
165
|
+
markPx?: string;
|
|
166
|
+
prevDayPx?: string;
|
|
167
|
+
dayNtlVlm?: string;
|
|
168
|
+
}>;
|
|
169
|
+
question?: OutcomeQuestion;
|
|
170
|
+
}
|
|
171
|
+
|
|
122
172
|
// ============ Account Types ============
|
|
123
173
|
|
|
124
174
|
export interface Position {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
6
6
|
interface Args {
|
|
7
|
-
type?: 'perp' | 'spot' | 'hip3' | 'all';
|
|
7
|
+
type?: 'perp' | 'spot' | 'hip3' | 'outcome' | 'all';
|
|
8
8
|
top?: number;
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
json?: boolean;
|
|
@@ -17,7 +17,7 @@ function parseArgs(): Args {
|
|
|
17
17
|
const arg = process.argv[i];
|
|
18
18
|
if (arg === '--type' && process.argv[i + 1]) {
|
|
19
19
|
const val = process.argv[++i].toLowerCase();
|
|
20
|
-
if (['perp', 'spot', 'hip3', 'all'].includes(val)) {
|
|
20
|
+
if (['perp', 'spot', 'hip3', 'outcome', 'all'].includes(val)) {
|
|
21
21
|
args.type = val as Args['type'];
|
|
22
22
|
}
|
|
23
23
|
} else if (arg === '--top' && process.argv[i + 1]) {
|
|
@@ -33,7 +33,7 @@ All Markets - View all available markets on Hyperliquid
|
|
|
33
33
|
Usage: npx tsx scripts/info/all-markets.ts [options]
|
|
34
34
|
|
|
35
35
|
Options:
|
|
36
|
-
--type <type> Market type: perp, spot, hip3, or all (default: all)
|
|
36
|
+
--type <type> Market type: perp, spot, hip3, outcome, or all (default: all)
|
|
37
37
|
--top <n> Show only top N markets by volume
|
|
38
38
|
--json Output as JSON (machine-readable)
|
|
39
39
|
--verbose Show detailed output
|
|
@@ -44,6 +44,7 @@ Examples:
|
|
|
44
44
|
npx tsx scripts/info/all-markets.ts --type perp # Show only main perps
|
|
45
45
|
npx tsx scripts/info/all-markets.ts --type hip3 # Show only HIP-3 perps
|
|
46
46
|
npx tsx scripts/info/all-markets.ts --type spot # Show only spot markets
|
|
47
|
+
npx tsx scripts/info/all-markets.ts --type outcome # Show only HIP-4 outcomes
|
|
47
48
|
npx tsx scripts/info/all-markets.ts --top 20 # Show top 20 by volume
|
|
48
49
|
npx tsx scripts/info/all-markets.ts --json # JSON output
|
|
49
50
|
`);
|
|
@@ -77,7 +78,7 @@ function formatFunding(rate: string): string {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
interface MarketRow {
|
|
80
|
-
type: 'perp' | 'spot' | 'hip3';
|
|
81
|
+
type: 'perp' | 'spot' | 'hip3' | 'outcome';
|
|
81
82
|
provider: string;
|
|
82
83
|
coin: string;
|
|
83
84
|
assetId: number;
|
|
@@ -85,6 +86,9 @@ interface MarketRow {
|
|
|
85
86
|
volume24h: number;
|
|
86
87
|
funding?: string;
|
|
87
88
|
maxLeverage?: number;
|
|
89
|
+
outcome?: number;
|
|
90
|
+
outcomeSide?: string;
|
|
91
|
+
description?: string;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
async function main() {
|
|
@@ -183,6 +187,30 @@ async function main() {
|
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
|
|
190
|
+
// Fetch HIP-4 outcomes
|
|
191
|
+
if (args.type === 'all' || args.type === 'outcome') {
|
|
192
|
+
try {
|
|
193
|
+
const outcomes = await client.getOutcomeMarkets();
|
|
194
|
+
for (const market of outcomes) {
|
|
195
|
+
for (const side of market.sides) {
|
|
196
|
+
allMarkets.push({
|
|
197
|
+
type: 'outcome',
|
|
198
|
+
provider: 'HIP-4',
|
|
199
|
+
coin: side.coin,
|
|
200
|
+
assetId: side.assetId,
|
|
201
|
+
price: side.midPx ?? side.markPx ?? '0',
|
|
202
|
+
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
203
|
+
outcome: market.outcome,
|
|
204
|
+
outcomeSide: side.name,
|
|
205
|
+
description: market.description,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
if (args.verbose) console.error('Failed to fetch HIP-4 outcomes:', e);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
186
214
|
// Sort by volume
|
|
187
215
|
allMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
188
216
|
|
|
@@ -198,6 +226,7 @@ async function main() {
|
|
|
198
226
|
const perps = markets.filter((m) => m.type === 'perp');
|
|
199
227
|
const hip3 = markets.filter((m) => m.type === 'hip3');
|
|
200
228
|
const spots = markets.filter((m) => m.type === 'spot');
|
|
229
|
+
const outcomes = markets.filter((m) => m.type === 'outcome');
|
|
201
230
|
|
|
202
231
|
// Print summary
|
|
203
232
|
console.log('=== Market Summary ===\n');
|
|
@@ -205,6 +234,7 @@ async function main() {
|
|
|
205
234
|
console.log(` - Main Perps: ${perps.length}`);
|
|
206
235
|
console.log(` - HIP-3 Perps: ${hip3.length}`);
|
|
207
236
|
console.log(` - Spot Markets: ${spots.length}`);
|
|
237
|
+
console.log(` - HIP-4 Outcomes: ${outcomes.length}`);
|
|
208
238
|
console.log();
|
|
209
239
|
|
|
210
240
|
// Print perps
|
|
@@ -245,6 +275,19 @@ async function main() {
|
|
|
245
275
|
}
|
|
246
276
|
console.log();
|
|
247
277
|
}
|
|
278
|
+
|
|
279
|
+
// Print HIP-4 outcome markets
|
|
280
|
+
if (outcomes.length > 0) {
|
|
281
|
+
console.log('=== HIP-4 Outcomes ===\n');
|
|
282
|
+
console.log('Coin Outcome Side AssetID Price 24h Volume');
|
|
283
|
+
console.log('-'.repeat(78));
|
|
284
|
+
for (const m of outcomes) {
|
|
285
|
+
console.log(
|
|
286
|
+
`${m.coin.padEnd(14)} ${String(m.outcome ?? '-').padStart(7)} ${(m.outcomeSide ?? '-').padEnd(8)} ${String(m.assetId).padStart(9)} ${formatPrice(m.price).padStart(7)} ${formatVolume(m.volume24h).padStart(13)}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
console.log();
|
|
290
|
+
}
|
|
248
291
|
}
|
|
249
292
|
|
|
250
293
|
main().catch((e) => {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
// HIP-4 Outcomes - search and inspect prediction markets
|
|
3
|
+
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
|
|
6
|
+
interface Args {
|
|
7
|
+
query?: string;
|
|
8
|
+
outcome?: string;
|
|
9
|
+
side?: string;
|
|
10
|
+
balances?: boolean;
|
|
11
|
+
top?: number;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseArgs(): Args {
|
|
17
|
+
const args: Args = {};
|
|
18
|
+
|
|
19
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
20
|
+
const arg = process.argv[i];
|
|
21
|
+
if ((arg === '--query' || arg === '-q') && process.argv[i + 1]) {
|
|
22
|
+
args.query = process.argv[++i];
|
|
23
|
+
} else if ((arg === '--outcome' || arg === '--id') && process.argv[i + 1]) {
|
|
24
|
+
args.outcome = process.argv[++i];
|
|
25
|
+
} else if (arg === '--side' && process.argv[i + 1]) {
|
|
26
|
+
args.side = process.argv[++i];
|
|
27
|
+
} else if (arg === '--balances') {
|
|
28
|
+
args.balances = true;
|
|
29
|
+
} else if (arg === '--top' && process.argv[i + 1]) {
|
|
30
|
+
args.top = parseInt(process.argv[++i], 10);
|
|
31
|
+
} else if (arg === '--verbose') {
|
|
32
|
+
args.verbose = true;
|
|
33
|
+
} else if (arg === '--json') {
|
|
34
|
+
args.json = true;
|
|
35
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
36
|
+
printUsage();
|
|
37
|
+
process.exit(0);
|
|
38
|
+
} else if (!args.query && !arg.startsWith('-')) {
|
|
39
|
+
args.query = arg;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return args;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printUsage() {
|
|
47
|
+
console.log(`
|
|
48
|
+
Open Broker - HIP-4 Outcomes
|
|
49
|
+
============================
|
|
50
|
+
|
|
51
|
+
Search and inspect Hyperliquid outcome markets.
|
|
52
|
+
|
|
53
|
+
Usage:
|
|
54
|
+
openbroker outcomes [--query <text>] [--outcome <id|#encoding|+encoding>] [options]
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--query, -q <text> Search market name, description, underlying, expiry, target
|
|
58
|
+
--outcome, --id <ref> Show one outcome by id or encoded coin (#1230 / +1230)
|
|
59
|
+
--side <yes|no|0|1> Select a side when using a plain outcome id
|
|
60
|
+
--balances Show outcome token balances for the configured account
|
|
61
|
+
--top <n> Show only top N matches
|
|
62
|
+
--json Output as JSON
|
|
63
|
+
--verbose Include raw descriptions and question metadata
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
openbroker outcomes --query BTC
|
|
67
|
+
openbroker outcomes --outcome 123
|
|
68
|
+
openbroker outcomes --outcome 123 --side yes --json
|
|
69
|
+
openbroker outcomes --balances
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatPrice(value?: string): string {
|
|
74
|
+
if (!value) return '-';
|
|
75
|
+
const n = parseFloat(value);
|
|
76
|
+
if (!Number.isFinite(n)) return value;
|
|
77
|
+
return n.toFixed(4);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatVolume(value?: string): string {
|
|
81
|
+
if (!value) return '-';
|
|
82
|
+
const n = parseFloat(value);
|
|
83
|
+
if (!Number.isFinite(n)) return value;
|
|
84
|
+
if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(2)}M`;
|
|
85
|
+
if (n >= 1_000) return `$${(n / 1_000).toFixed(2)}K`;
|
|
86
|
+
return `$${n.toFixed(2)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function main() {
|
|
90
|
+
const args = parseArgs();
|
|
91
|
+
const client = getClient();
|
|
92
|
+
client.verbose = args.verbose ?? false;
|
|
93
|
+
|
|
94
|
+
if (!args.json) {
|
|
95
|
+
console.log('Open Broker - HIP-4 Outcomes');
|
|
96
|
+
console.log('============================\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (args.balances) {
|
|
101
|
+
const balances = await client.getSpotBalances();
|
|
102
|
+
const outcomeBalances = (balances.balances ?? []).filter((b) =>
|
|
103
|
+
b.coin.startsWith('+') || b.coin.startsWith('#')
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (args.json) {
|
|
107
|
+
console.log(JSON.stringify(outcomeBalances, null, 2));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (outcomeBalances.length === 0) {
|
|
112
|
+
console.log('No outcome token balances found.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('Outcome Balances');
|
|
117
|
+
console.log('----------------');
|
|
118
|
+
console.log('Token Total Hold Entry Value');
|
|
119
|
+
console.log('-'.repeat(70));
|
|
120
|
+
for (const b of outcomeBalances) {
|
|
121
|
+
console.log(
|
|
122
|
+
`${b.coin.padEnd(12)} ${parseFloat(b.total).toFixed(6).padStart(18)} ` +
|
|
123
|
+
`${parseFloat(b.hold).toFixed(6).padStart(18)} ${formatVolume(b.entryNtl).padStart(15)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let markets = await client.getOutcomeMarkets();
|
|
130
|
+
|
|
131
|
+
if (args.outcome) {
|
|
132
|
+
const resolved = client.resolveOutcomeRef(args.outcome, args.side);
|
|
133
|
+
markets = markets.filter((market) => market.outcome === resolved.outcome);
|
|
134
|
+
for (const market of markets) {
|
|
135
|
+
market.sides = market.sides.filter((side) => side.side === resolved.side);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (args.query) {
|
|
140
|
+
const query = args.query.toUpperCase();
|
|
141
|
+
markets = markets.filter((market) => {
|
|
142
|
+
const parsed = Object.values(market.parsedDescription).join(' ');
|
|
143
|
+
const searchable = `${market.name} ${market.description} ${parsed}`.toUpperCase();
|
|
144
|
+
return searchable.includes(query);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
markets.sort((a, b) => {
|
|
149
|
+
const aVol = Math.max(...a.sides.map((s) => parseFloat(s.dayNtlVlm ?? '0')));
|
|
150
|
+
const bVol = Math.max(...b.sides.map((s) => parseFloat(s.dayNtlVlm ?? '0')));
|
|
151
|
+
return bVol - aVol;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const displayMarkets = args.top ? markets.slice(0, args.top) : markets;
|
|
155
|
+
|
|
156
|
+
if (args.json) {
|
|
157
|
+
console.log(JSON.stringify(displayMarkets, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (displayMarkets.length === 0) {
|
|
162
|
+
console.log('No outcome markets found.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`Found ${displayMarkets.length} outcome market(s)\n`);
|
|
167
|
+
console.log('Outcome Side Coin AssetID Price 24h Volume Market');
|
|
168
|
+
console.log('-'.repeat(98));
|
|
169
|
+
|
|
170
|
+
for (const market of displayMarkets) {
|
|
171
|
+
const spec = market.parsedDescription;
|
|
172
|
+
const labelParts = [
|
|
173
|
+
spec.underlying,
|
|
174
|
+
spec.expiry ? `exp ${spec.expiry}` : undefined,
|
|
175
|
+
spec.targetPrice ? `target ${spec.targetPrice}` : undefined,
|
|
176
|
+
].filter(Boolean);
|
|
177
|
+
const label = labelParts.length > 0 ? labelParts.join(' | ') : market.description;
|
|
178
|
+
|
|
179
|
+
for (const side of market.sides) {
|
|
180
|
+
console.log(
|
|
181
|
+
`${String(market.outcome).padStart(7)} ${side.name.padEnd(4)} ${side.coin.padEnd(9)} ` +
|
|
182
|
+
`${String(side.assetId).padStart(10)} ${formatPrice(side.midPx ?? side.markPx).padStart(7)} ` +
|
|
183
|
+
`${formatVolume(side.dayNtlVlm).padStart(13)} ${label}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (args.verbose) {
|
|
188
|
+
console.log(` Description: ${market.description}`);
|
|
189
|
+
if (market.question) console.log(` Question: ${market.question.name}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
194
|
+
console.error(`Error: ${message}`);
|
|
195
|
+
console.error('Note: Hyperliquid currently documents outcomeMeta as testnet-only.');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main();
|
|
@@ -5,7 +5,7 @@ import { getClient } from '../core/client.js';
|
|
|
5
5
|
|
|
6
6
|
interface Args {
|
|
7
7
|
query: string;
|
|
8
|
-
type?: 'perp' | 'spot' | 'hip3' | 'all';
|
|
8
|
+
type?: 'perp' | 'spot' | 'hip3' | 'outcome' | 'all';
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
json?: boolean;
|
|
11
11
|
}
|
|
@@ -19,7 +19,7 @@ function parseArgs(): Args {
|
|
|
19
19
|
args.query = process.argv[++i];
|
|
20
20
|
} else if (arg === '--type' && process.argv[i + 1]) {
|
|
21
21
|
const val = process.argv[++i].toLowerCase();
|
|
22
|
-
if (['perp', 'spot', 'hip3', 'all'].includes(val)) {
|
|
22
|
+
if (['perp', 'spot', 'hip3', 'outcome', 'all'].includes(val)) {
|
|
23
23
|
args.type = val as Args['type'];
|
|
24
24
|
}
|
|
25
25
|
} else if (arg === '--verbose') {
|
|
@@ -34,7 +34,7 @@ Usage: npx tsx scripts/info/search-markets.ts --query <search> [options]
|
|
|
34
34
|
|
|
35
35
|
Options:
|
|
36
36
|
--query <search> Search term (required) - matches coin name
|
|
37
|
-
--type <type> Filter by market type: perp, spot, hip3, or all (default: all)
|
|
37
|
+
--type <type> Filter by market type: perp, spot, hip3, outcome, or all (default: all)
|
|
38
38
|
--json Output as JSON (machine-readable)
|
|
39
39
|
--verbose Show detailed output
|
|
40
40
|
--help Show this help
|
|
@@ -44,6 +44,7 @@ Examples:
|
|
|
44
44
|
npx tsx scripts/info/search-markets.ts --query BTC # Find all BTC markets
|
|
45
45
|
npx tsx scripts/info/search-markets.ts --query ETH --type perp # ETH perps only
|
|
46
46
|
npx tsx scripts/info/search-markets.ts --query PURR --type spot # PURR spot only
|
|
47
|
+
npx tsx scripts/info/search-markets.ts --query BTC --type outcome # HIP-4 outcomes only
|
|
47
48
|
npx tsx scripts/info/search-markets.ts --query HYPE --json # JSON output
|
|
48
49
|
`);
|
|
49
50
|
process.exit(0);
|
|
@@ -95,7 +96,7 @@ async function main() {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
interface Result {
|
|
98
|
-
type: 'perp' | 'spot' | 'hip3';
|
|
99
|
+
type: 'perp' | 'spot' | 'hip3' | 'outcome';
|
|
99
100
|
provider: string;
|
|
100
101
|
coin: string;
|
|
101
102
|
assetId: number;
|
|
@@ -104,6 +105,9 @@ async function main() {
|
|
|
104
105
|
funding?: string;
|
|
105
106
|
maxLeverage?: number;
|
|
106
107
|
openInterest?: string;
|
|
108
|
+
outcome?: number;
|
|
109
|
+
outcomeSide?: string;
|
|
110
|
+
description?: string;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
const results: Result[] = [];
|
|
@@ -224,6 +228,34 @@ async function main() {
|
|
|
224
228
|
}
|
|
225
229
|
}
|
|
226
230
|
|
|
231
|
+
// Search HIP-4 outcome markets
|
|
232
|
+
if (args.type === 'all' || args.type === 'outcome') {
|
|
233
|
+
try {
|
|
234
|
+
const outcomes = await client.getOutcomeMarkets();
|
|
235
|
+
for (const market of outcomes) {
|
|
236
|
+
const parsed = Object.values(market.parsedDescription).join(' ');
|
|
237
|
+
const searchable = `${market.name} ${market.description} ${parsed}`.toUpperCase();
|
|
238
|
+
if (!searchable.includes(query)) continue;
|
|
239
|
+
|
|
240
|
+
for (const side of market.sides) {
|
|
241
|
+
results.push({
|
|
242
|
+
type: 'outcome',
|
|
243
|
+
provider: 'HIP-4',
|
|
244
|
+
coin: side.coin,
|
|
245
|
+
assetId: side.assetId,
|
|
246
|
+
price: side.midPx ?? side.markPx ?? '0',
|
|
247
|
+
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
248
|
+
outcome: market.outcome,
|
|
249
|
+
outcomeSide: side.name,
|
|
250
|
+
description: market.description,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (e) {
|
|
255
|
+
if (args.verbose) console.error('Failed to fetch HIP-4 outcomes:', e);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
227
259
|
// Sort by volume
|
|
228
260
|
results.sort((a, b) => b.volume24h - a.volume24h);
|
|
229
261
|
|
|
@@ -242,11 +274,14 @@ async function main() {
|
|
|
242
274
|
console.log('-'.repeat(112));
|
|
243
275
|
|
|
244
276
|
for (const m of results) {
|
|
245
|
-
const typeStr = m.type === 'hip3' ? 'HIP-3' : m.type.charAt(0).toUpperCase() + m.type.slice(1);
|
|
277
|
+
const typeStr = m.type === 'hip3' ? 'HIP-3' : m.type === 'outcome' ? 'HIP-4' : m.type.charAt(0).toUpperCase() + m.type.slice(1);
|
|
246
278
|
const oi = m.openInterest ? formatVolume(parseFloat(m.openInterest)) : '-';
|
|
247
279
|
console.log(
|
|
248
280
|
`${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)}`
|
|
249
281
|
);
|
|
282
|
+
if (m.type === 'outcome' && args.verbose) {
|
|
283
|
+
console.log(` Outcome ${m.outcome} ${m.outcomeSide}: ${m.description}`);
|
|
284
|
+
}
|
|
250
285
|
}
|
|
251
286
|
|
|
252
287
|
// Show comparison if same asset on multiple providers
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Execute a HIP-4 outcome order on Hyperliquid
|
|
3
|
+
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
import { checkBuilderFeeApproval, formatUsd, parseArgs } from '../core/utils.js';
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.log(`
|
|
9
|
+
Open Broker - HIP-4 Outcome Order
|
|
10
|
+
=================================
|
|
11
|
+
|
|
12
|
+
Buy or sell a YES/NO outcome token.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
openbroker outcome-order --outcome <id|#encoding|+encoding> --outcome-side <yes|no> --side <buy|sell> --size <SIZE>
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--outcome Outcome id, outcome spot coin (#1230), or token name (+1230)
|
|
19
|
+
--outcome-side Outcome side when --outcome is a plain id: yes/no or 0/1 (default: yes)
|
|
20
|
+
--side Trade side: buy or sell
|
|
21
|
+
--size Order size in outcome token units
|
|
22
|
+
--price Limit price between 0.001 and 0.999 (omit for market IOC)
|
|
23
|
+
--tif Time-in-force for limit orders: Gtc, Ioc, Alo (default: Gtc)
|
|
24
|
+
--slippage Slippage tolerance in bps for market orders (default: config, usually 50)
|
|
25
|
+
--sz-decimals Override size decimals if outcome metadata omits token decimals
|
|
26
|
+
--dry Dry run - show order details without executing
|
|
27
|
+
--verbose Show full API request/response for debugging
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
openbroker outcomes --query BTC
|
|
31
|
+
openbroker outcome-order --outcome 123 --outcome-side yes --side buy --size 10 --dry
|
|
32
|
+
openbroker outcome-buy --outcome 123 --outcome-side no --size 5 --price 0.42
|
|
33
|
+
openbroker outcome-sell --outcome #1230 --size 10
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatOutcomePrice(price: number): string {
|
|
38
|
+
return price.toFixed(4);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
const args = parseArgs(process.argv.slice(2));
|
|
43
|
+
|
|
44
|
+
if (args.help || args.h) {
|
|
45
|
+
printUsage();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const outcomeRef = args.outcome as string;
|
|
50
|
+
const outcomeSide = args['outcome-side'] as string | undefined;
|
|
51
|
+
const side = args.side as string;
|
|
52
|
+
const size = parseFloat(args.size as string);
|
|
53
|
+
const price = args.price ? parseFloat(args.price as string) : undefined;
|
|
54
|
+
const tif = (args.tif as 'Gtc' | 'Ioc' | 'Alo') ?? 'Gtc';
|
|
55
|
+
const slippage = args.slippage ? parseInt(args.slippage as string) : undefined;
|
|
56
|
+
const szDecimals = args['sz-decimals'] ? parseInt(args['sz-decimals'] as string, 10) : undefined;
|
|
57
|
+
const dryRun = args.dry as boolean;
|
|
58
|
+
|
|
59
|
+
if (!outcomeRef || !side || isNaN(size)) {
|
|
60
|
+
printUsage();
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (side !== 'buy' && side !== 'sell') {
|
|
65
|
+
console.error('Error: --side must be "buy" or "sell"');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (size <= 0) {
|
|
70
|
+
console.error('Error: --size must be positive');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (price !== undefined && (price <= 0 || price >= 1)) {
|
|
75
|
+
console.error('Error: --price must be between 0 and 1 for outcome tokens');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (szDecimals !== undefined && (szDecimals < 0 || szDecimals > 8)) {
|
|
80
|
+
console.error('Error: --sz-decimals must be between 0 and 8');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const client = getClient();
|
|
85
|
+
if (args.verbose) client.verbose = true;
|
|
86
|
+
|
|
87
|
+
const isBuy = side === 'buy';
|
|
88
|
+
const isMarket = price === undefined;
|
|
89
|
+
|
|
90
|
+
console.log('Open Broker - HIP-4 Outcome Order');
|
|
91
|
+
console.log('=================================\n');
|
|
92
|
+
|
|
93
|
+
await checkBuilderFeeApproval(client);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const resolved = client.resolveOutcomeRef(outcomeRef, outcomeSide);
|
|
97
|
+
const market = await client.getOutcomeMarket(resolved.outcome);
|
|
98
|
+
const marketSide = market?.sides.find((s) => s.side === resolved.side);
|
|
99
|
+
const sideName = marketSide?.name ?? (resolved.side === 0 ? 'Yes' : 'No');
|
|
100
|
+
const midPrice = await client.getOutcomeMidPrice(resolved.outcome, resolved.side);
|
|
101
|
+
const slippageBps = slippage ?? 50;
|
|
102
|
+
const limitPrice = isMarket
|
|
103
|
+
? (isBuy ? midPrice * (1 + slippageBps / 10000) : midPrice * (1 - slippageBps / 10000))
|
|
104
|
+
: price;
|
|
105
|
+
const notional = midPrice * size;
|
|
106
|
+
|
|
107
|
+
console.log('Order Details');
|
|
108
|
+
console.log('-------------');
|
|
109
|
+
console.log(`Outcome: ${resolved.outcome}`);
|
|
110
|
+
console.log(`Market: ${market?.name ?? 'Unknown'}${market?.parsedDescription.underlying ? ` (${market.parsedDescription.underlying})` : ''}`);
|
|
111
|
+
if (market?.parsedDescription.expiry) console.log(`Expiry: ${market.parsedDescription.expiry}`);
|
|
112
|
+
if (market?.parsedDescription.targetPrice) console.log(`Target: ${market.parsedDescription.targetPrice}`);
|
|
113
|
+
console.log(`Outcome Side: ${sideName.toUpperCase()} (${resolved.side})`);
|
|
114
|
+
console.log(`Coin: ${resolved.coin}`);
|
|
115
|
+
console.log(`Asset ID: ${resolved.assetId}`);
|
|
116
|
+
console.log(`Trade Side: ${isBuy ? 'BUY' : 'SELL'}`);
|
|
117
|
+
console.log(`Size: ${size}`);
|
|
118
|
+
console.log(`Mid Price: ${formatOutcomePrice(midPrice)}`);
|
|
119
|
+
if (isMarket) {
|
|
120
|
+
console.log(`Type: Market (IOC)`);
|
|
121
|
+
console.log(`Limit Price: ${formatOutcomePrice(limitPrice)} (${slippageBps} bps slippage)`);
|
|
122
|
+
} else {
|
|
123
|
+
console.log(`Type: Limit (${tif})`);
|
|
124
|
+
console.log(`Limit Price: ${formatOutcomePrice(price)}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(`Notional: ~${formatUsd(notional)}`);
|
|
127
|
+
if (marketSide?.szDecimals !== undefined || szDecimals !== undefined) {
|
|
128
|
+
console.log(`Sz Decimals: ${szDecimals ?? marketSide?.szDecimals}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`);
|
|
131
|
+
|
|
132
|
+
if (dryRun) {
|
|
133
|
+
console.log('\nDry run - order not submitted');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('\nExecuting...');
|
|
138
|
+
|
|
139
|
+
const response = isMarket
|
|
140
|
+
? await client.outcomeMarketOrder(outcomeRef, outcomeSide, isBuy, size, slippage, szDecimals)
|
|
141
|
+
: await client.outcomeLimitOrder(outcomeRef, outcomeSide, isBuy, size, price!, tif, szDecimals);
|
|
142
|
+
|
|
143
|
+
console.log('\nResult');
|
|
144
|
+
console.log('------');
|
|
145
|
+
|
|
146
|
+
if (args.verbose || process.env.VERBOSE) {
|
|
147
|
+
console.log('\nFull Response:');
|
|
148
|
+
console.log(JSON.stringify(response, null, 2));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
152
|
+
const statuses = response.response.data.statuses;
|
|
153
|
+
for (const status of statuses) {
|
|
154
|
+
if (status.filled) {
|
|
155
|
+
const fillSz = parseFloat(status.filled.totalSz);
|
|
156
|
+
const avgPx = parseFloat(status.filled.avgPx);
|
|
157
|
+
console.log('Filled');
|
|
158
|
+
console.log(` Order ID: ${status.filled.oid}`);
|
|
159
|
+
console.log(` Size: ${fillSz}`);
|
|
160
|
+
console.log(` Avg Price: ${formatOutcomePrice(avgPx)}`);
|
|
161
|
+
console.log(` Notional: ${formatUsd(fillSz * avgPx)}`);
|
|
162
|
+
} else if (status.resting) {
|
|
163
|
+
console.log('Resting');
|
|
164
|
+
console.log(` Order ID: ${status.resting.oid}`);
|
|
165
|
+
} else if (status.error) {
|
|
166
|
+
console.log(`Error: ${status.error}`);
|
|
167
|
+
} else {
|
|
168
|
+
console.log('Unknown status:');
|
|
169
|
+
console.log(JSON.stringify(status, null, 2));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (response.status === 'err') {
|
|
173
|
+
console.log(`API Error: ${response.response || JSON.stringify(response)}`);
|
|
174
|
+
} else {
|
|
175
|
+
console.log('Unexpected response:');
|
|
176
|
+
console.log(JSON.stringify(response, null, 2));
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Error executing outcome order:', error);
|
|
180
|
+
console.error('Note: Hyperliquid currently documents outcomeMeta as testnet-only.');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
main();
|