openbroker 1.1.2 → 1.3.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/CHANGELOG.md +14 -0
- package/README.md +54 -5
- package/bin/cli.ts +26 -2
- package/bin/openbroker.js +4 -0
- package/package.json +4 -1
- package/scripts/auto/cli.ts +89 -3
- package/scripts/auto/prune.ts +252 -0
- package/scripts/auto/runtime.ts +26 -11
- package/scripts/core/client.ts +363 -1
- 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
|
@@ -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();
|