openbroker 1.0.33

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.
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Cancel orders on Hyperliquid
3
+
4
+ import { getClient } from '../core/client.js';
5
+ import { formatUsd, parseArgs } from '../core/utils.js';
6
+
7
+ function printUsage() {
8
+ console.log(`
9
+ Open Broker - Cancel Orders
10
+ ===========================
11
+
12
+ Cancel open orders.
13
+
14
+ Usage:
15
+ npx tsx scripts/operations/cancel.ts [--coin <COIN>] [--oid <ORDER_ID>] [--all]
16
+
17
+ Options:
18
+ --coin Cancel orders for specific coin only
19
+ --oid Cancel specific order by ID
20
+ --all Cancel all open orders
21
+ --dry Dry run - show what would be cancelled
22
+
23
+ Examples:
24
+ npx tsx scripts/operations/cancel.ts --all # Cancel all orders
25
+ npx tsx scripts/operations/cancel.ts --coin ETH # Cancel all ETH orders
26
+ npx tsx scripts/operations/cancel.ts --coin ETH --oid 123456 # Cancel specific order
27
+ npx tsx scripts/operations/cancel.ts --all --dry # Show all orders (dry run)
28
+ `);
29
+ }
30
+
31
+ async function main() {
32
+ const args = parseArgs(process.argv.slice(2));
33
+
34
+ const coin = args.coin as string | undefined;
35
+ const oid = args.oid ? parseInt(args.oid as string) : undefined;
36
+ const all = args.all as boolean;
37
+ const dryRun = args.dry as boolean;
38
+
39
+ // Must specify something to cancel
40
+ if (!coin && !oid && !all) {
41
+ printUsage();
42
+ process.exit(1);
43
+ }
44
+
45
+ const client = getClient();
46
+
47
+ console.log('Open Broker - Cancel Orders');
48
+ console.log('===========================\n');
49
+
50
+ try {
51
+ // Get open orders
52
+ const orders = await client.getOpenOrders();
53
+
54
+ // Filter orders based on arguments
55
+ let targetOrders = orders;
56
+ if (coin) {
57
+ targetOrders = orders.filter(o => o.coin === coin);
58
+ }
59
+ if (oid) {
60
+ targetOrders = orders.filter(o => o.oid === oid);
61
+ }
62
+
63
+ if (targetOrders.length === 0) {
64
+ if (oid) {
65
+ console.log(`No order found with ID ${oid}`);
66
+ } else if (coin) {
67
+ console.log(`No open orders for ${coin}`);
68
+ } else {
69
+ console.log('No open orders to cancel');
70
+ }
71
+ return;
72
+ }
73
+
74
+ // Display orders to be cancelled
75
+ console.log('Orders to Cancel');
76
+ console.log('----------------');
77
+ console.log('Coin | Side | Size | Price | Order ID');
78
+ console.log('---------|------|------------|------------|----------');
79
+
80
+ for (const order of targetOrders) {
81
+ const side = order.side === 'B' ? 'BUY ' : 'SELL';
82
+ console.log(
83
+ `${order.coin.padEnd(8)} | ${side} | ${parseFloat(order.sz).toFixed(4).padStart(10)} | ` +
84
+ `${formatUsd(parseFloat(order.limitPx)).padStart(10)} | ${order.oid}`
85
+ );
86
+ }
87
+
88
+ console.log(`\nTotal: ${targetOrders.length} order(s)`);
89
+
90
+ if (dryRun) {
91
+ console.log('\nšŸ” Dry run - orders not cancelled');
92
+ return;
93
+ }
94
+
95
+ console.log('\nCancelling...');
96
+
97
+ let successCount = 0;
98
+ let failCount = 0;
99
+
100
+ for (const order of targetOrders) {
101
+ try {
102
+ const response = await client.cancel(order.coin, order.oid);
103
+ if (response.status === 'ok') {
104
+ console.log(`āœ… Cancelled ${order.coin} order ${order.oid}`);
105
+ successCount++;
106
+ } else {
107
+ console.log(`āŒ Failed to cancel ${order.coin} order ${order.oid}`);
108
+ failCount++;
109
+ }
110
+ } catch (err) {
111
+ console.log(`āŒ Error cancelling ${order.coin} order ${order.oid}: ${err}`);
112
+ failCount++;
113
+ }
114
+ }
115
+
116
+ console.log(`\nResult: ${successCount} cancelled, ${failCount} failed`);
117
+
118
+ } catch (error) {
119
+ console.error('Error:', error);
120
+ process.exit(1);
121
+ }
122
+ }
123
+
124
+ main();
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Chase Order - Follow price with limit orders until filled
3
+
4
+ import { getClient } from '../core/client.js';
5
+ import { formatUsd, parseArgs, sleep } from '../core/utils.js';
6
+
7
+ function printUsage() {
8
+ console.log(`
9
+ Open Broker - Chase Order
10
+ =========================
11
+
12
+ Place a limit order that chases the price until filled.
13
+ Keeps adjusting the order to stay near the best price while avoiding taker fees.
14
+
15
+ Usage:
16
+ npx tsx scripts/operations/chase.ts --coin <COIN> --side <buy|sell> --size <SIZE>
17
+
18
+ Options:
19
+ --coin Asset to trade (e.g., ETH, BTC)
20
+ --side Order side: buy or sell
21
+ --size Order size in base asset
22
+ --offset Offset from mid price in bps (default: 5 = 0.05%)
23
+ --timeout Max time to chase in seconds (default: 300 = 5 min)
24
+ --interval Update interval in ms (default: 2000)
25
+ --max-chase Max price to chase to in bps from start (default: 100 = 1%)
26
+ --reduce Reduce-only order
27
+ --dry Dry run - show chase parameters without executing
28
+
29
+ Strategy:
30
+ - Places ALO (post-only) order at mid ± offset
31
+ - If not filled, cancels and replaces closer to mid
32
+ - Stops when filled or timeout/max-chase reached
33
+ - Uses ALO to ensure maker rebates
34
+
35
+ Examples:
36
+ # Chase buy 0.5 ETH with 5 bps offset, 5 min timeout
37
+ npx tsx scripts/operations/chase.ts --coin ETH --side buy --size 0.5
38
+
39
+ # Chase sell with tighter offset and longer timeout
40
+ npx tsx scripts/operations/chase.ts --coin BTC --side sell --size 0.1 --offset 2 --timeout 600
41
+
42
+ # Quick aggressive chase (1 bps offset, 1 min timeout, 50 bps max chase)
43
+ npx tsx scripts/operations/chase.ts --coin SOL --side buy --size 10 --offset 1 --timeout 60 --max-chase 50
44
+ `);
45
+ }
46
+
47
+ async function main() {
48
+ const args = parseArgs(process.argv.slice(2));
49
+
50
+ const coin = args.coin as string;
51
+ const side = args.side as string;
52
+ const size = parseFloat(args.size as string);
53
+ const offsetBps = args.offset ? parseInt(args.offset as string) : 5;
54
+ const timeoutSec = args.timeout ? parseInt(args.timeout as string) : 300;
55
+ const intervalMs = args.interval ? parseInt(args.interval as string) : 2000;
56
+ const maxChaseBps = args['max-chase'] ? parseInt(args['max-chase'] as string) : 100;
57
+ const reduceOnly = args.reduce as boolean;
58
+ const dryRun = args.dry as boolean;
59
+
60
+ if (!coin || !side || isNaN(size)) {
61
+ printUsage();
62
+ process.exit(1);
63
+ }
64
+
65
+ if (side !== 'buy' && side !== 'sell') {
66
+ console.error('Error: --side must be "buy" or "sell"');
67
+ process.exit(1);
68
+ }
69
+
70
+ const isBuy = side === 'buy';
71
+ const client = getClient();
72
+
73
+ if (args.verbose) {
74
+ client.verbose = true;
75
+ }
76
+
77
+ console.log('Open Broker - Chase Order');
78
+ console.log('=========================\n');
79
+
80
+ try {
81
+ const mids = await client.getAllMids();
82
+ const startMid = parseFloat(mids[coin]);
83
+ if (!startMid) {
84
+ console.error(`Error: No market data for ${coin}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ const maxChasePrice = isBuy
89
+ ? startMid * (1 + maxChaseBps / 10000)
90
+ : startMid * (1 - maxChaseBps / 10000);
91
+
92
+ console.log('Chase Parameters');
93
+ console.log('----------------');
94
+ console.log(`Coin: ${coin}`);
95
+ console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
96
+ console.log(`Size: ${size}`);
97
+ console.log(`Start Mid: ${formatUsd(startMid)}`);
98
+ console.log(`Offset: ${offsetBps} bps (${(offsetBps / 100).toFixed(2)}%)`);
99
+ console.log(`Max Chase: ${maxChaseBps} bps to ${formatUsd(maxChasePrice)}`);
100
+ console.log(`Timeout: ${timeoutSec}s`);
101
+ console.log(`Update Rate: ${intervalMs}ms`);
102
+ console.log(`Order Type: ALO (post-only)`);
103
+
104
+ if (dryRun) {
105
+ console.log('\nšŸ” Dry run - chase not started');
106
+ return;
107
+ }
108
+
109
+ console.log('\nChasing...\n');
110
+
111
+ const startTime = Date.now();
112
+ let currentOid: number | null = null;
113
+ let lastPrice: number | null = null;
114
+ let iteration = 0;
115
+ let filled = false;
116
+
117
+ while (Date.now() - startTime < timeoutSec * 1000) {
118
+ iteration++;
119
+
120
+ // Get current mid
121
+ const currentMids = await client.getAllMids();
122
+ const currentMid = parseFloat(currentMids[coin]);
123
+
124
+ // Check max chase limit
125
+ if (isBuy && currentMid > maxChasePrice) {
126
+ console.log(`\nāš ļø Price ${formatUsd(currentMid)} exceeded max chase ${formatUsd(maxChasePrice)}`);
127
+ break;
128
+ }
129
+ if (!isBuy && currentMid < maxChasePrice) {
130
+ console.log(`\nāš ļø Price ${formatUsd(currentMid)} exceeded max chase ${formatUsd(maxChasePrice)}`);
131
+ break;
132
+ }
133
+
134
+ // Calculate order price with offset
135
+ const orderPrice = isBuy
136
+ ? currentMid * (1 - offsetBps / 10000)
137
+ : currentMid * (1 + offsetBps / 10000);
138
+
139
+ // Check if price moved enough to update
140
+ const priceChanged = !lastPrice || Math.abs(orderPrice - lastPrice) / lastPrice > 0.0001;
141
+
142
+ if (priceChanged) {
143
+ // Cancel existing order if any
144
+ if (currentOid !== null) {
145
+ try {
146
+ await client.cancel(coin, currentOid);
147
+ } catch {
148
+ // Order might have filled
149
+ }
150
+ currentOid = null;
151
+ }
152
+
153
+ // Check if we got filled while cancelling
154
+ const orders = await client.getOpenOrders();
155
+ const ourOrder = orders.find(o => o.coin === coin && o.oid === currentOid);
156
+ if (!ourOrder && currentOid !== null) {
157
+ // Order gone - might have filled
158
+ // Check position to confirm
159
+ const state = await client.getUserState();
160
+ const pos = state.assetPositions.find(p => p.position.coin === coin);
161
+ if (pos && Math.abs(parseFloat(pos.position.szi)) >= size * 0.99) {
162
+ filled = true;
163
+ console.log(`\nāœ… Order filled!`);
164
+ break;
165
+ }
166
+ }
167
+
168
+ // Place new order
169
+ process.stdout.write(`[${iteration}] Mid: ${formatUsd(currentMid)} → Order: ${formatUsd(orderPrice)}... `);
170
+
171
+ const response = await client.limitOrder(coin, isBuy, size, orderPrice, 'Alo', reduceOnly);
172
+
173
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
174
+ const status = response.response.data.statuses[0];
175
+ if (status?.resting) {
176
+ currentOid = status.resting.oid;
177
+ lastPrice = orderPrice;
178
+ console.log(`OID: ${currentOid}`);
179
+ } else if (status?.filled) {
180
+ filled = true;
181
+ console.log(`āœ… Filled @ ${formatUsd(parseFloat(status.filled.avgPx))}`);
182
+ break;
183
+ } else if (status?.error) {
184
+ console.log(`āŒ ${status.error}`);
185
+ // If ALO rejected (would be taker), try again next iteration
186
+ }
187
+ } else {
188
+ console.log(`āŒ Failed`);
189
+ }
190
+ } else {
191
+ // Price stable, check if filled
192
+ if (currentOid !== null) {
193
+ const orders = await client.getOpenOrders();
194
+ const ourOrder = orders.find(o => o.oid === currentOid);
195
+ if (!ourOrder) {
196
+ filled = true;
197
+ console.log(`\nāœ… Order filled!`);
198
+ break;
199
+ }
200
+ }
201
+ process.stdout.write('.');
202
+ }
203
+
204
+ await sleep(intervalMs);
205
+ }
206
+
207
+ // Cleanup
208
+ if (currentOid !== null && !filled) {
209
+ console.log(`\nCancelling unfilled order...`);
210
+ try {
211
+ await client.cancel(coin, currentOid);
212
+ console.log(`āœ… Cancelled`);
213
+ } catch {
214
+ console.log(`āš ļø Could not cancel (may have filled)`);
215
+ }
216
+ }
217
+
218
+ // Summary
219
+ const elapsed = (Date.now() - startTime) / 1000;
220
+ const endMid = parseFloat((await client.getAllMids())[coin]);
221
+ const priceMove = ((endMid - startMid) / startMid) * 10000;
222
+
223
+ console.log('\n========== Chase Summary ==========');
224
+ console.log(`Duration: ${elapsed.toFixed(1)}s`);
225
+ console.log(`Iterations: ${iteration}`);
226
+ console.log(`Start Mid: ${formatUsd(startMid)}`);
227
+ console.log(`End Mid: ${formatUsd(endMid)} (${priceMove >= 0 ? '+' : ''}${priceMove.toFixed(1)} bps)`);
228
+ console.log(`Result: ${filled ? 'āœ… Filled' : 'āŒ Not filled'}`);
229
+
230
+ } catch (error) {
231
+ console.error('Error:', error);
232
+ process.exit(1);
233
+ }
234
+ }
235
+
236
+ main();
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Execute a limit order on Hyperliquid
3
+
4
+ import { getClient } from '../core/client.js';
5
+ import { formatUsd, parseArgs } from '../core/utils.js';
6
+
7
+ function printUsage() {
8
+ console.log(`
9
+ Open Broker - Limit Order
10
+ =========================
11
+
12
+ Place a limit order with specified price.
13
+
14
+ Usage:
15
+ npx tsx scripts/operations/limit-order.ts --coin <COIN> --side <buy|sell> --size <SIZE> --price <PRICE>
16
+
17
+ Options:
18
+ --coin Asset to trade (e.g., ETH, BTC)
19
+ --side Order side: buy or sell
20
+ --size Order size in base asset
21
+ --price Limit price
22
+ --tif Time in force: GTC, IOC, or ALO (default: GTC)
23
+ GTC = Good Till Cancel (rests on book)
24
+ IOC = Immediate Or Cancel (fills or cancels)
25
+ ALO = Add Liquidity Only (post-only, maker only)
26
+ --reduce Reduce-only order (default: false)
27
+ --dry Dry run - show order details without executing
28
+
29
+ Examples:
30
+ npx tsx scripts/operations/limit-order.ts --coin ETH --side buy --size 1 --price 3000
31
+ npx tsx scripts/operations/limit-order.ts --coin BTC --side sell --size 0.1 --price 100000 --tif ALO
32
+ npx tsx scripts/operations/limit-order.ts --coin SOL --side buy --size 10 --price 150 --reduce
33
+ `);
34
+ }
35
+
36
+ async function main() {
37
+ const args = parseArgs(process.argv.slice(2));
38
+
39
+ // Parse and validate arguments
40
+ const coin = args.coin as string;
41
+ const side = args.side as string;
42
+ const size = parseFloat(args.size as string);
43
+ const price = parseFloat(args.price as string);
44
+ const tifArg = (args.tif as string)?.toUpperCase() || 'GTC';
45
+ const reduceOnly = args.reduce as boolean;
46
+ const dryRun = args.dry as boolean;
47
+
48
+ if (!coin || !side || isNaN(size) || isNaN(price)) {
49
+ printUsage();
50
+ process.exit(1);
51
+ }
52
+
53
+ if (side !== 'buy' && side !== 'sell') {
54
+ console.error('Error: --side must be "buy" or "sell"');
55
+ process.exit(1);
56
+ }
57
+
58
+ if (size <= 0) {
59
+ console.error('Error: --size must be positive');
60
+ process.exit(1);
61
+ }
62
+
63
+ if (price <= 0) {
64
+ console.error('Error: --price must be positive');
65
+ process.exit(1);
66
+ }
67
+
68
+ // Map uppercase CLI input to Pascal case for SDK
69
+ const tifMap: Record<string, 'Gtc' | 'Ioc' | 'Alo'> = {
70
+ 'GTC': 'Gtc',
71
+ 'IOC': 'Ioc',
72
+ 'ALO': 'Alo'
73
+ };
74
+
75
+ const tif = tifMap[tifArg];
76
+ if (!tif) {
77
+ console.error(`Error: --tif must be one of: GTC, IOC, ALO`);
78
+ process.exit(1);
79
+ }
80
+ const isBuy = side === 'buy';
81
+ const client = getClient();
82
+
83
+ console.log('Open Broker - Limit Order');
84
+ console.log('=========================\n');
85
+
86
+ try {
87
+ // Get current price for reference
88
+ const mids = await client.getAllMids();
89
+ const midPrice = parseFloat(mids[coin]);
90
+
91
+ if (!midPrice) {
92
+ console.error(`Error: No market data for ${coin}`);
93
+ process.exit(1);
94
+ }
95
+
96
+ const notional = price * size;
97
+ const distanceFromMid = ((price - midPrice) / midPrice) * 100;
98
+
99
+ console.log('Order Details');
100
+ console.log('-------------');
101
+ console.log(`Coin: ${coin}`);
102
+ console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
103
+ console.log(`Size: ${size}`);
104
+ console.log(`Limit Price: ${formatUsd(price)}`);
105
+ console.log(`Current Mid: ${formatUsd(midPrice)}`);
106
+ console.log(`Distance: ${distanceFromMid >= 0 ? '+' : ''}${distanceFromMid.toFixed(2)}% from mid`);
107
+ console.log(`Notional: ${formatUsd(notional)}`);
108
+ console.log(`Time in Force: ${tif}`);
109
+ console.log(`Reduce Only: ${reduceOnly ? 'Yes' : 'No'}`);
110
+ console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`);
111
+
112
+ // Warning if order would be aggressively priced
113
+ if ((isBuy && price > midPrice) || (!isBuy && price < midPrice)) {
114
+ console.log(`\nāš ļø Order is priced aggressively - may fill immediately as taker`);
115
+ }
116
+
117
+ if (dryRun) {
118
+ console.log('\nšŸ” Dry run - order not submitted');
119
+ return;
120
+ }
121
+
122
+ console.log('\nSubmitting...');
123
+
124
+ const response = await client.limitOrder(coin, isBuy, size, price, tif, reduceOnly);
125
+
126
+ console.log('\nResult');
127
+ console.log('------');
128
+
129
+ if (response.status === 'ok' && response.response) {
130
+ const statuses = response.response.data.statuses;
131
+ for (const status of statuses) {
132
+ if (status.filled) {
133
+ const fillSz = parseFloat(status.filled.totalSz);
134
+ const avgPx = parseFloat(status.filled.avgPx);
135
+ const fillNotional = fillSz * avgPx;
136
+
137
+ console.log(`āœ… Filled`);
138
+ console.log(` Order ID: ${status.filled.oid}`);
139
+ console.log(` Size: ${fillSz}`);
140
+ console.log(` Avg Price: ${formatUsd(avgPx)}`);
141
+ console.log(` Notional: ${formatUsd(fillNotional)}`);
142
+ } else if (status.resting) {
143
+ console.log(`āœ… Order placed`);
144
+ console.log(` Order ID: ${status.resting.oid}`);
145
+ console.log(` Status: Resting on book`);
146
+ } else if (status.error) {
147
+ console.log(`āŒ Error: ${status.error}`);
148
+ }
149
+ }
150
+ } else {
151
+ console.log(`āŒ Error: ${response.error || 'Unknown error'}`);
152
+ }
153
+
154
+ } catch (error) {
155
+ console.error('Error submitting order:', error);
156
+ process.exit(1);
157
+ }
158
+ }
159
+
160
+ main();
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Execute a market order on Hyperliquid
3
+
4
+ import { getClient } from '../core/client.js';
5
+ import { formatUsd, parseArgs, checkBuilderFeeApproval } from '../core/utils.js';
6
+
7
+ function printUsage() {
8
+ console.log(`
9
+ Open Broker - Market Order
10
+ ==========================
11
+
12
+ Execute a market order with slippage protection.
13
+
14
+ Usage:
15
+ npx tsx scripts/operations/market-order.ts --coin <COIN> --side <buy|sell> --size <SIZE>
16
+
17
+ Options:
18
+ --coin Asset to trade (e.g., ETH, BTC)
19
+ --side Order side: buy or sell
20
+ --size Order size in base asset
21
+ --slippage Slippage tolerance in bps (default: from config, usually 50 = 0.5%)
22
+ --reduce Reduce-only order (default: false)
23
+ --dry Dry run - show order details without executing
24
+ --verbose Show full API request/response for debugging
25
+
26
+ Environment:
27
+ HYPERLIQUID_PRIVATE_KEY Your wallet private key (0x...)
28
+ HYPERLIQUID_NETWORK "mainnet" or "testnet" (default: mainnet)
29
+ VERBOSE=1 Enable verbose logging
30
+
31
+ Examples:
32
+ npx tsx scripts/operations/market-order.ts --coin ETH --side buy --size 0.1
33
+ npx tsx scripts/operations/market-order.ts --coin BTC --side sell --size 0.01 --slippage 100
34
+ npx tsx scripts/operations/market-order.ts --coin SOL --side buy --size 10 --dry
35
+ npx tsx scripts/operations/market-order.ts --coin ETH --side buy --size 0.1 --verbose
36
+ `);
37
+ }
38
+
39
+ async function main() {
40
+ const args = parseArgs(process.argv.slice(2));
41
+
42
+ // Parse and validate arguments
43
+ const coin = args.coin as string;
44
+ const side = args.side as string;
45
+ const size = parseFloat(args.size as string);
46
+ const slippage = args.slippage ? parseInt(args.slippage as string) : undefined;
47
+ const reduceOnly = args.reduce as boolean;
48
+ const dryRun = args.dry as boolean;
49
+
50
+ if (!coin || !side || isNaN(size)) {
51
+ printUsage();
52
+ process.exit(1);
53
+ }
54
+
55
+ if (side !== 'buy' && side !== 'sell') {
56
+ console.error('Error: --side must be "buy" or "sell"');
57
+ process.exit(1);
58
+ }
59
+
60
+ if (size <= 0) {
61
+ console.error('Error: --size must be positive');
62
+ process.exit(1);
63
+ }
64
+
65
+ const isBuy = side === 'buy';
66
+ const client = getClient();
67
+
68
+ // Enable verbose mode if requested
69
+ if (args.verbose) {
70
+ client.verbose = true;
71
+ }
72
+
73
+ console.log('Open Broker - Market Order');
74
+ console.log('==========================\n');
75
+
76
+ // Check builder fee approval (warning only, don't block)
77
+ await checkBuilderFeeApproval(client);
78
+
79
+ try {
80
+ // Get current price
81
+ const mids = await client.getAllMids();
82
+ const midPrice = parseFloat(mids[coin]);
83
+
84
+ if (!midPrice) {
85
+ console.error(`Error: No market data for ${coin}`);
86
+ process.exit(1);
87
+ }
88
+
89
+ // Calculate order details
90
+ const slippageBps = slippage ?? 50;
91
+ const slippageMultiplier = slippageBps / 10000;
92
+ const limitPrice = isBuy
93
+ ? midPrice * (1 + slippageMultiplier)
94
+ : midPrice * (1 - slippageMultiplier);
95
+ const notional = midPrice * size;
96
+
97
+ console.log('Order Details');
98
+ console.log('-------------');
99
+ console.log(`Coin: ${coin}`);
100
+ console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
101
+ console.log(`Size: ${size}`);
102
+ console.log(`Mid Price: ${formatUsd(midPrice)}`);
103
+ console.log(`Limit Price: ${formatUsd(limitPrice)} (${slippageBps} bps slippage)`);
104
+ console.log(`Notional: ~${formatUsd(notional)}`);
105
+ console.log(`Reduce Only: ${reduceOnly ? 'Yes' : 'No'}`);
106
+ console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`);
107
+
108
+ if (dryRun) {
109
+ console.log('\nšŸ” Dry run - order not submitted');
110
+ return;
111
+ }
112
+
113
+ console.log('\nExecuting...');
114
+
115
+ const response = await client.marketOrder(coin, isBuy, size, slippage);
116
+
117
+ console.log('\nResult');
118
+ console.log('------');
119
+
120
+ // Log full response for debugging
121
+ if (args.verbose || process.env.VERBOSE) {
122
+ console.log('\nFull Response:');
123
+ console.log(JSON.stringify(response, null, 2));
124
+ }
125
+
126
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
127
+ const statuses = response.response.data.statuses;
128
+ for (const status of statuses) {
129
+ if (status.filled) {
130
+ const fillSz = parseFloat(status.filled.totalSz);
131
+ const avgPx = parseFloat(status.filled.avgPx);
132
+ const fillNotional = fillSz * avgPx;
133
+ const slippageActual = isBuy
134
+ ? (avgPx - midPrice) / midPrice
135
+ : (midPrice - avgPx) / midPrice;
136
+
137
+ console.log(`āœ… Filled`);
138
+ console.log(` Order ID: ${status.filled.oid}`);
139
+ console.log(` Size: ${fillSz}`);
140
+ console.log(` Avg Price: ${formatUsd(avgPx)}`);
141
+ console.log(` Notional: ${formatUsd(fillNotional)}`);
142
+ console.log(` Slippage: ${(slippageActual * 10000).toFixed(1)} bps`);
143
+ } else if (status.resting) {
144
+ console.log(`ā³ Resting (partial fill)`);
145
+ console.log(` Order ID: ${status.resting.oid}`);
146
+ } else if (status.error) {
147
+ console.log(`āŒ Error: ${status.error}`);
148
+ } else {
149
+ // Unknown status - show the whole thing
150
+ console.log(`āš ļø Unknown status:`);
151
+ console.log(JSON.stringify(status, null, 2));
152
+ }
153
+ }
154
+ } else if (response.status === 'err') {
155
+ console.log(`āŒ API Error: ${response.response || JSON.stringify(response)}`);
156
+ } else {
157
+ console.log(`āŒ Unexpected response:`);
158
+ console.log(JSON.stringify(response, null, 2));
159
+ }
160
+
161
+ } catch (error) {
162
+ console.error('Error executing order:', error);
163
+ process.exit(1);
164
+ }
165
+ }
166
+
167
+ main();