openbroker 1.0.70 → 1.0.71
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 +42 -13
- package/SKILL.md +14 -8
- package/bin/cli.ts +6 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -1
- package/scripts/core/client.ts +84 -0
- package/scripts/operations/twap-cancel.ts +65 -0
- package/scripts/operations/twap-status.ts +96 -0
- package/scripts/operations/twap.ts +64 -137
- package/scripts/plugin/tools.ts +120 -18
package/README.md
CHANGED
|
@@ -383,16 +383,19 @@ openbroker cancel --all --dry # Preview what would be cancelled
|
|
|
383
383
|
|
|
384
384
|
### Advanced Execution
|
|
385
385
|
|
|
386
|
-
#### `twap` —
|
|
386
|
+
#### `twap` — Native TWAP Order
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
Place a native Hyperliquid TWAP order. The exchange handles order slicing and execution timing server-side — returns immediately with a TWAP ID.
|
|
389
389
|
|
|
390
390
|
```bash
|
|
391
|
-
# Buy 1 ETH over
|
|
392
|
-
openbroker twap --coin ETH --side buy --size 1 --duration
|
|
391
|
+
# Buy 1 ETH over 30 minutes
|
|
392
|
+
openbroker twap --coin ETH --side buy --size 1 --duration 30
|
|
393
393
|
|
|
394
|
-
# Sell 0.5 BTC over
|
|
395
|
-
openbroker twap --coin BTC --side sell --size 0.5 --duration
|
|
394
|
+
# Sell 0.5 BTC over 2 hours without randomized timing
|
|
395
|
+
openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
|
|
396
|
+
|
|
397
|
+
# Preview order details
|
|
398
|
+
openbroker twap --coin ETH --side buy --size 2 --duration 60 --dry
|
|
396
399
|
```
|
|
397
400
|
|
|
398
401
|
| Flag | Description | Default |
|
|
@@ -400,14 +403,38 @@ openbroker twap --coin BTC --side sell --size 0.5 --duration 1800 --intervals 6
|
|
|
400
403
|
| `--coin` | Asset to trade | **required** |
|
|
401
404
|
| `--side` | `buy` or `sell` | **required** |
|
|
402
405
|
| `--size` | Total order size in base asset | **required** |
|
|
403
|
-
| `--duration` |
|
|
404
|
-
| `--
|
|
405
|
-
| `--
|
|
406
|
-
| `--
|
|
407
|
-
| `--dry` | Show
|
|
406
|
+
| `--duration` | Duration in minutes (5–1440) | **required** |
|
|
407
|
+
| `--randomize` | Randomize execution timing | `true` |
|
|
408
|
+
| `--reduce-only` | Reduce-only order | `false` |
|
|
409
|
+
| `--leverage` | Set leverage before placing | — |
|
|
410
|
+
| `--dry` | Show order details without placing | — |
|
|
408
411
|
| `--verbose` | Show debug output | — |
|
|
409
412
|
|
|
410
|
-
|
|
413
|
+
#### `twap-cancel` — Cancel TWAP Order
|
|
414
|
+
|
|
415
|
+
Cancel a running native TWAP order by its TWAP ID.
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
openbroker twap-cancel --coin ETH --twap-id 77738308
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
| Flag | Description | Default |
|
|
422
|
+
|------|-------------|---------|
|
|
423
|
+
| `--coin` | Asset symbol | **required** |
|
|
424
|
+
| `--twap-id` | TWAP order ID to cancel | **required** |
|
|
425
|
+
|
|
426
|
+
#### `twap-status` — TWAP Order Status
|
|
427
|
+
|
|
428
|
+
View TWAP order history and currently running TWAP orders.
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
openbroker twap-status # All TWAP history
|
|
432
|
+
openbroker twap-status --active # Only running TWAPs
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
| Flag | Description | Default |
|
|
436
|
+
|------|-------------|---------|
|
|
437
|
+
| `--active` | Show only active/running TWAP orders | — |
|
|
411
438
|
|
|
412
439
|
#### `scale` — Scale In/Out
|
|
413
440
|
|
|
@@ -724,7 +751,9 @@ When loaded, the plugin registers these agent tools:
|
|
|
724
751
|
| Trading | `ob_trigger` | Trigger order (TP/SL) |
|
|
725
752
|
| Trading | `ob_tpsl` | Set TP/SL on existing position |
|
|
726
753
|
| Trading | `ob_cancel` | Cancel orders |
|
|
727
|
-
| Advanced | `ob_twap` | TWAP
|
|
754
|
+
| Advanced | `ob_twap` | Native TWAP order (exchange-managed) |
|
|
755
|
+
| Advanced | `ob_twap_cancel` | Cancel a running TWAP order |
|
|
756
|
+
| Advanced | `ob_twap_status` | View TWAP order history/status |
|
|
728
757
|
| Advanced | `ob_bracket` | Entry + TP + SL |
|
|
729
758
|
| Advanced | `ob_chase` | Chase price with ALO orders |
|
|
730
759
|
| Monitoring | `ob_watcher_status` | Background watcher state |
|
package/SKILL.md
CHANGED
|
@@ -4,8 +4,8 @@ description: Hyperliquid trading plugin with background position monitoring and
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
|
|
6
6
|
homepage: https://www.npmjs.com/package/openbroker
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "1.0.
|
|
8
|
-
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.71", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
|
|
8
|
+
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_twap_cancel ob_twap_status ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Open Broker - Hyperliquid Trading CLI
|
|
@@ -73,7 +73,7 @@ If an `ob_*` plugin tool returns unexpected errors, empty results, or crashes, *
|
|
|
73
73
|
**When to use CLI fallback:**
|
|
74
74
|
- Plugin tool returns `null`, empty data, or throws an error
|
|
75
75
|
- You need data the plugin tool doesn't expose (e.g., `--verbose` debug output)
|
|
76
|
-
- Long-running operations (strategies
|
|
76
|
+
- Long-running operations (strategies) — the CLI handles timeouts and progress better
|
|
77
77
|
|
|
78
78
|
Add `--dry` to any trading CLI command to preview without executing. Add `--json` to info commands for structured output.
|
|
79
79
|
|
|
@@ -288,13 +288,19 @@ openbroker cancel --oid 123456 # Cancel specific order
|
|
|
288
288
|
|
|
289
289
|
## Advanced Execution
|
|
290
290
|
|
|
291
|
-
### TWAP (
|
|
291
|
+
### TWAP (Native Exchange Order)
|
|
292
292
|
```bash
|
|
293
|
-
#
|
|
294
|
-
openbroker twap --coin ETH --side buy --size 1 --duration
|
|
293
|
+
# Buy 1 ETH over 30 minutes (exchange handles slicing)
|
|
294
|
+
openbroker twap --coin ETH --side buy --size 1 --duration 30
|
|
295
295
|
|
|
296
|
-
#
|
|
297
|
-
openbroker twap --coin BTC --side sell --size 0.5 --duration
|
|
296
|
+
# Sell 0.5 BTC over 2 hours without randomized timing
|
|
297
|
+
openbroker twap --coin BTC --side sell --size 0.5 --duration 120 --randomize false
|
|
298
|
+
|
|
299
|
+
# Cancel a running TWAP
|
|
300
|
+
openbroker twap-cancel --coin ETH --twap-id 77738308
|
|
301
|
+
|
|
302
|
+
# Check TWAP status
|
|
303
|
+
openbroker twap-status --active
|
|
298
304
|
```
|
|
299
305
|
|
|
300
306
|
### Scale In/Out (Grid Orders)
|
package/bin/cli.ts
CHANGED
|
@@ -41,7 +41,9 @@ const commands: Record<string, { script: string; description: string }> = {
|
|
|
41
41
|
'trigger': { script: 'operations/trigger-order.ts', description: 'Trigger order (TP/SL)' },
|
|
42
42
|
'tpsl': { script: 'operations/set-tpsl.ts', description: 'Set TP/SL on position' },
|
|
43
43
|
'cancel': { script: 'operations/cancel.ts', description: 'Cancel orders' },
|
|
44
|
-
'twap': { script: 'operations/twap.ts', description: 'TWAP
|
|
44
|
+
'twap': { script: 'operations/twap.ts', description: 'Native TWAP order' },
|
|
45
|
+
'twap-cancel': { script: 'operations/twap-cancel.ts', description: 'Cancel a TWAP order' },
|
|
46
|
+
'twap-status': { script: 'operations/twap-status.ts', description: 'View TWAP order status' },
|
|
45
47
|
'scale': { script: 'operations/scale.ts', description: 'Scale in/out orders' },
|
|
46
48
|
'bracket': { script: 'operations/bracket.ts', description: 'Bracket order (entry + TP + SL)' },
|
|
47
49
|
'chase': { script: 'operations/chase.ts', description: 'Chase order with ALO' },
|
|
@@ -87,7 +89,9 @@ Trading Commands:
|
|
|
87
89
|
cancel Cancel orders
|
|
88
90
|
|
|
89
91
|
Advanced Execution:
|
|
90
|
-
twap
|
|
92
|
+
twap Native TWAP order (exchange-managed)
|
|
93
|
+
twap-cancel Cancel a running TWAP order
|
|
94
|
+
twap-status View TWAP order history/status
|
|
91
95
|
scale Scale in/out with multiple orders
|
|
92
96
|
bracket Entry with TP and SL
|
|
93
97
|
chase Chase price with ALO orders
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.71",
|
|
4
4
|
"description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"set-tpsl": "tsx scripts/operations/set-tpsl.ts",
|
|
36
36
|
"cancel": "tsx scripts/operations/cancel.ts",
|
|
37
37
|
"twap": "tsx scripts/operations/twap.ts",
|
|
38
|
+
"twap-cancel": "tsx scripts/operations/twap-cancel.ts",
|
|
39
|
+
"twap-status": "tsx scripts/operations/twap-status.ts",
|
|
38
40
|
"scale": "tsx scripts/operations/scale.ts",
|
|
39
41
|
"bracket": "tsx scripts/operations/bracket.ts",
|
|
40
42
|
"chase": "tsx scripts/operations/chase.ts",
|
package/scripts/core/client.ts
CHANGED
|
@@ -1638,6 +1638,90 @@ export class HyperliquidClient {
|
|
|
1638
1638
|
throw error;
|
|
1639
1639
|
}
|
|
1640
1640
|
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Place a native Hyperliquid TWAP order.
|
|
1644
|
+
* The exchange handles slicing and timing server-side.
|
|
1645
|
+
* @param coin Asset symbol (e.g. "ETH")
|
|
1646
|
+
* @param isBuy true for long, false for short
|
|
1647
|
+
* @param size Total size in base currency
|
|
1648
|
+
* @param durationMinutes Duration in minutes (5–1440)
|
|
1649
|
+
* @param randomize Enable random order timing
|
|
1650
|
+
* @param reduceOnly Reduce-only flag
|
|
1651
|
+
* @param leverage Optional leverage to set before placing the TWAP
|
|
1652
|
+
*/
|
|
1653
|
+
async twapOrder(
|
|
1654
|
+
coin: string,
|
|
1655
|
+
isBuy: boolean,
|
|
1656
|
+
size: number,
|
|
1657
|
+
durationMinutes: number,
|
|
1658
|
+
randomize: boolean = true,
|
|
1659
|
+
reduceOnly: boolean = false,
|
|
1660
|
+
leverage?: number
|
|
1661
|
+
) {
|
|
1662
|
+
await this.getMetaAndAssetCtxs();
|
|
1663
|
+
|
|
1664
|
+
if (leverage) {
|
|
1665
|
+
await this.setLeverage(coin, leverage);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
const assetIndex = this.getAssetIndex(coin);
|
|
1669
|
+
const roundedSize = roundSize(size, this.getSzDecimals(coin));
|
|
1670
|
+
|
|
1671
|
+
this.log(`TWAP order: ${coin} (asset ${assetIndex}) ${isBuy ? 'BUY' : 'SELL'} ${roundedSize} over ${durationMinutes}m, randomize=${randomize}, reduceOnly=${reduceOnly}`);
|
|
1672
|
+
|
|
1673
|
+
try {
|
|
1674
|
+
const response = await this.exchange.twapOrder({
|
|
1675
|
+
twap: {
|
|
1676
|
+
a: assetIndex,
|
|
1677
|
+
b: isBuy,
|
|
1678
|
+
s: String(roundedSize),
|
|
1679
|
+
r: reduceOnly,
|
|
1680
|
+
m: durationMinutes,
|
|
1681
|
+
t: randomize,
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1684
|
+
this.log('TWAP order response:', JSON.stringify(response, null, 2));
|
|
1685
|
+
return response;
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
this.log('TWAP order error:', error);
|
|
1688
|
+
throw error;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Cancel a running TWAP order.
|
|
1694
|
+
* @param coin Asset symbol (e.g. "ETH")
|
|
1695
|
+
* @param twapId The TWAP order ID to cancel
|
|
1696
|
+
*/
|
|
1697
|
+
async twapCancel(coin: string, twapId: number) {
|
|
1698
|
+
await this.getMetaAndAssetCtxs();
|
|
1699
|
+
|
|
1700
|
+
const assetIndex = this.getAssetIndex(coin);
|
|
1701
|
+
|
|
1702
|
+
this.log(`TWAP cancel: ${coin} (asset ${assetIndex}) twapId=${twapId}`);
|
|
1703
|
+
|
|
1704
|
+
try {
|
|
1705
|
+
const response = await this.exchange.twapCancel({
|
|
1706
|
+
a: assetIndex,
|
|
1707
|
+
t: twapId,
|
|
1708
|
+
});
|
|
1709
|
+
this.log('TWAP cancel response:', JSON.stringify(response, null, 2));
|
|
1710
|
+
return response;
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
this.log('TWAP cancel error:', error);
|
|
1713
|
+
throw error;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Get TWAP order history for the current user.
|
|
1719
|
+
*/
|
|
1720
|
+
async twapHistory() {
|
|
1721
|
+
const response = await this.info.twapHistory({ user: this.address as `0x${string}` });
|
|
1722
|
+
this.log('TWAP history:', JSON.stringify(response, null, 2));
|
|
1723
|
+
return response;
|
|
1724
|
+
}
|
|
1641
1725
|
}
|
|
1642
1726
|
|
|
1643
1727
|
// Singleton instance
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Cancel a running TWAP order
|
|
3
|
+
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
import { parseArgs } from '../core/utils.js';
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.log(`
|
|
9
|
+
Open Broker - Cancel TWAP Order
|
|
10
|
+
================================
|
|
11
|
+
|
|
12
|
+
Cancel a running native Hyperliquid TWAP order.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx tsx scripts/operations/twap-cancel.ts --coin <COIN> --twap-id <ID>
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--coin Asset symbol (e.g., ETH, BTC)
|
|
19
|
+
--twap-id TWAP order ID to cancel
|
|
20
|
+
--verbose Show debug output
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
npx tsx scripts/operations/twap-cancel.ts --coin ETH --twap-id 77738308
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
const args = parseArgs(process.argv.slice(2));
|
|
29
|
+
|
|
30
|
+
const coin = args.coin as string;
|
|
31
|
+
const twapId = args['twap-id'] ? parseInt(args['twap-id'] as string) : NaN;
|
|
32
|
+
|
|
33
|
+
if (!coin || isNaN(twapId)) {
|
|
34
|
+
printUsage();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const client = getClient();
|
|
39
|
+
|
|
40
|
+
if (args.verbose) {
|
|
41
|
+
client.verbose = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('Open Broker - Cancel TWAP Order');
|
|
45
|
+
console.log('===============================\n');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
console.log(`Cancelling TWAP ${twapId} for ${coin}...`);
|
|
49
|
+
|
|
50
|
+
const response = await client.twapCancel(coin, twapId);
|
|
51
|
+
|
|
52
|
+
const status = response.response.data.status;
|
|
53
|
+
if (typeof status === 'string' && status === 'success') {
|
|
54
|
+
console.log(`\nTWAP order ${twapId} cancelled successfully.`);
|
|
55
|
+
} else if (typeof status === 'object' && 'error' in status) {
|
|
56
|
+
console.error(`\nFailed to cancel TWAP: ${status.error}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// View TWAP order history and status
|
|
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 - TWAP Status
|
|
10
|
+
==========================
|
|
11
|
+
|
|
12
|
+
View your TWAP order history and currently running TWAP orders.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx tsx scripts/operations/twap-status.ts [--active]
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--active Show only active (running) TWAP orders
|
|
19
|
+
--verbose Show debug output
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
npx tsx scripts/operations/twap-status.ts # All TWAP history
|
|
23
|
+
npx tsx scripts/operations/twap-status.ts --active # Only running TWAPs
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
const args = parseArgs(process.argv.slice(2));
|
|
29
|
+
|
|
30
|
+
if (args.help) {
|
|
31
|
+
printUsage();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const activeOnly = args.active as boolean;
|
|
36
|
+
const client = getClient();
|
|
37
|
+
|
|
38
|
+
if (args.verbose) {
|
|
39
|
+
client.verbose = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('Open Broker - TWAP Status');
|
|
43
|
+
console.log('=========================\n');
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const history = await client.twapHistory();
|
|
47
|
+
|
|
48
|
+
if (history.length === 0) {
|
|
49
|
+
console.log('No TWAP orders found.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const filtered = activeOnly
|
|
54
|
+
? history.filter(h => h.status.status === 'activated')
|
|
55
|
+
: history;
|
|
56
|
+
|
|
57
|
+
if (filtered.length === 0) {
|
|
58
|
+
console.log(activeOnly ? 'No active TWAP orders.' : 'No TWAP orders found.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(`Found ${filtered.length} TWAP order${filtered.length > 1 ? 's' : ''}${activeOnly ? ' (active)' : ''}:\n`);
|
|
63
|
+
|
|
64
|
+
for (const entry of filtered) {
|
|
65
|
+
const { state, status, twapId } = entry;
|
|
66
|
+
const isBuy = state.side === 'B';
|
|
67
|
+
const executedSz = parseFloat(state.executedSz);
|
|
68
|
+
const totalSz = parseFloat(state.sz);
|
|
69
|
+
const executedNtl = parseFloat(state.executedNtl);
|
|
70
|
+
const avgPrice = executedSz > 0 ? executedNtl / executedSz : 0;
|
|
71
|
+
const pctDone = totalSz > 0 ? (executedSz / totalSz) * 100 : 0;
|
|
72
|
+
|
|
73
|
+
const statusLabel = status.status === 'activated' ? 'RUNNING'
|
|
74
|
+
: status.status === 'finished' ? 'FINISHED'
|
|
75
|
+
: status.status === 'terminated' ? 'CANCELLED'
|
|
76
|
+
: status.status === 'error' ? `ERROR: ${'description' in status ? status.description : ''}`
|
|
77
|
+
: status.status;
|
|
78
|
+
|
|
79
|
+
console.log(` ${twapId !== undefined ? `TWAP #${twapId}` : 'TWAP'} — ${state.coin} ${isBuy ? 'BUY' : 'SELL'}`);
|
|
80
|
+
console.log(` Status: ${statusLabel}`);
|
|
81
|
+
console.log(` Size: ${executedSz} / ${totalSz} (${pctDone.toFixed(1)}%)`);
|
|
82
|
+
if (avgPrice > 0) {
|
|
83
|
+
console.log(` Avg Price: ${formatUsd(avgPrice)}`);
|
|
84
|
+
console.log(` Notional: ${formatUsd(executedNtl)}`);
|
|
85
|
+
}
|
|
86
|
+
console.log(` Duration: ${state.minutes}m, Randomize: ${state.randomize ? 'yes' : 'no'}`);
|
|
87
|
+
console.log(` Started: ${new Date(state.timestamp).toLocaleString()}`);
|
|
88
|
+
console.log('');
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
|
@@ -1,67 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env npx tsx
|
|
2
|
-
// TWAP (Time-Weighted Average Price) execution
|
|
2
|
+
// TWAP (Time-Weighted Average Price) execution using Hyperliquid's native TWAP orders
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs
|
|
5
|
+
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
9
|
-
Open Broker - TWAP Order
|
|
10
|
-
|
|
9
|
+
Open Broker - TWAP Order (Native)
|
|
10
|
+
==================================
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Place a native Hyperliquid TWAP order. The exchange handles order slicing
|
|
13
|
+
and execution timing server-side.
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
|
-
npx tsx scripts/operations/twap.ts --coin <COIN> --side <buy|sell> --size <SIZE> --duration <
|
|
16
|
+
npx tsx scripts/operations/twap.ts --coin <COIN> --side <buy|sell> --size <SIZE> --duration <MINUTES>
|
|
17
17
|
|
|
18
18
|
Options:
|
|
19
19
|
--coin Asset to trade (e.g., ETH, BTC)
|
|
20
20
|
--side Order side: buy or sell
|
|
21
21
|
--size Total order size in base asset
|
|
22
|
-
--duration Total execution time in
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
26
|
-
--
|
|
27
|
-
--
|
|
22
|
+
--duration Total execution time in minutes (5–1440, i.e. 5 min to 24 hours)
|
|
23
|
+
--randomize Enable random order timing (default: true)
|
|
24
|
+
--reduce-only Reduce-only order (default: false)
|
|
25
|
+
--leverage Set leverage (e.g., 10 for 10x)
|
|
26
|
+
--dry Dry run - show order plan without executing
|
|
27
|
+
--verbose Show debug output
|
|
28
28
|
|
|
29
29
|
Examples:
|
|
30
|
-
# Execute 1 ETH buy over
|
|
31
|
-
npx tsx scripts/operations/twap.ts --coin ETH --side buy --size 1 --duration
|
|
30
|
+
# Execute 1 ETH buy over 30 minutes
|
|
31
|
+
npx tsx scripts/operations/twap.ts --coin ETH --side buy --size 1 --duration 30
|
|
32
32
|
|
|
33
|
-
# Execute 0.5 BTC sell over
|
|
34
|
-
npx tsx scripts/operations/twap.ts --coin BTC --side sell --size 0.5 --duration
|
|
33
|
+
# Execute 0.5 BTC sell over 2 hours without randomized timing
|
|
34
|
+
npx tsx scripts/operations/twap.ts --coin BTC --side sell --size 0.5 --duration 120 --randomize false
|
|
35
35
|
|
|
36
36
|
# Preview execution plan
|
|
37
|
-
npx tsx scripts/operations/twap.ts --coin ETH --side buy --size 2 --duration
|
|
37
|
+
npx tsx scripts/operations/twap.ts --coin ETH --side buy --size 2 --duration 60 --dry
|
|
38
38
|
`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
interface TwapResult {
|
|
42
|
-
slice: number;
|
|
43
|
-
timestamp: Date;
|
|
44
|
-
size: number;
|
|
45
|
-
filled: number;
|
|
46
|
-
avgPrice: number;
|
|
47
|
-
status: 'filled' | 'partial' | 'failed';
|
|
48
|
-
error?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
41
|
async function main() {
|
|
52
42
|
const args = parseArgs(process.argv.slice(2));
|
|
53
43
|
|
|
54
44
|
const coin = args.coin as string;
|
|
55
45
|
const side = args.side as string;
|
|
56
46
|
const totalSize = parseFloat(args.size as string);
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const slippage = args.slippage ? parseInt(args.slippage as string) : undefined;
|
|
47
|
+
const durationMinutes = parseInt(args.duration as string);
|
|
48
|
+
const randomize = args.randomize === 'false' || args.randomize === false ? false : true;
|
|
49
|
+
const reduceOnly = args['reduce-only'] as boolean || false;
|
|
61
50
|
const leverage = args.leverage ? parseInt(args.leverage as string) : undefined;
|
|
62
51
|
const dryRun = args.dry as boolean;
|
|
63
52
|
|
|
64
|
-
if (!coin || !side || isNaN(totalSize) || isNaN(
|
|
53
|
+
if (!coin || !side || isNaN(totalSize) || isNaN(durationMinutes)) {
|
|
65
54
|
printUsage();
|
|
66
55
|
process.exit(1);
|
|
67
56
|
}
|
|
@@ -71,8 +60,13 @@ async function main() {
|
|
|
71
60
|
process.exit(1);
|
|
72
61
|
}
|
|
73
62
|
|
|
74
|
-
if (totalSize <= 0
|
|
75
|
-
console.error('Error: size
|
|
63
|
+
if (totalSize <= 0) {
|
|
64
|
+
console.error('Error: --size must be positive');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (durationMinutes < 5 || durationMinutes > 1440) {
|
|
69
|
+
console.error('Error: --duration must be between 5 and 1440 minutes (5 min to 24 hours)');
|
|
76
70
|
process.exit(1);
|
|
77
71
|
}
|
|
78
72
|
|
|
@@ -83,11 +77,11 @@ async function main() {
|
|
|
83
77
|
client.verbose = true;
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
console.log('Open Broker - TWAP
|
|
87
|
-
console.log('
|
|
80
|
+
console.log('Open Broker - Native TWAP Order');
|
|
81
|
+
console.log('===============================\n');
|
|
88
82
|
|
|
89
83
|
try {
|
|
90
|
-
// Get current price for
|
|
84
|
+
// Get current price for display
|
|
91
85
|
const mids = await client.getAllMids();
|
|
92
86
|
const midPrice = parseFloat(mids[coin]);
|
|
93
87
|
if (!midPrice) {
|
|
@@ -95,120 +89,53 @@ async function main() {
|
|
|
95
89
|
process.exit(1);
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
const sliceSize = totalSize / intervals;
|
|
99
|
-
const baseInterval = (duration * 1000) / intervals; // ms between slices
|
|
100
92
|
const notional = midPrice * totalSize;
|
|
101
93
|
|
|
102
|
-
console.log('
|
|
103
|
-
console.log('
|
|
94
|
+
console.log('Order Details');
|
|
95
|
+
console.log('-------------');
|
|
104
96
|
console.log(`Coin: ${coin}`);
|
|
105
97
|
console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
|
|
106
98
|
console.log(`Total Size: ${totalSize}`);
|
|
107
99
|
console.log(`Current Price: ${formatUsd(midPrice)}`);
|
|
108
100
|
console.log(`Est. Notional: ${formatUsd(notional)}`);
|
|
109
|
-
console.log(`Duration: ${formatDuration(
|
|
110
|
-
console.log(`
|
|
111
|
-
console.log(`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
console.log(`Randomization: ±${randomize}%`);
|
|
101
|
+
console.log(`Duration: ${formatDuration(durationMinutes * 60)}`);
|
|
102
|
+
console.log(`Randomize: ${randomize ? 'yes' : 'no'}`);
|
|
103
|
+
console.log(`Reduce Only: ${reduceOnly ? 'yes' : 'no'}`);
|
|
104
|
+
if (leverage) {
|
|
105
|
+
console.log(`Leverage: ${leverage}x`);
|
|
115
106
|
}
|
|
116
107
|
|
|
117
108
|
if (dryRun) {
|
|
118
|
-
console.log('\
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < intervals; i++) {
|
|
121
|
-
const jitter = randomize > 0 ? (Math.random() - 0.5) * 2 * (randomize / 100) : 0;
|
|
122
|
-
const interval = baseInterval * (1 + jitter);
|
|
123
|
-
console.log(` Slice ${i + 1}/${intervals}: ${sliceSize.toFixed(6)} @ T+${formatDuration(time / 1000)}`);
|
|
124
|
-
time += interval;
|
|
125
|
-
}
|
|
126
|
-
console.log(`\n Total duration: ~${formatDuration(time / 1000)}`);
|
|
109
|
+
console.log('\nDry run - no order placed.');
|
|
110
|
+
console.log('The exchange will handle order slicing and timing automatically.');
|
|
127
111
|
return;
|
|
128
112
|
}
|
|
129
113
|
|
|
130
|
-
console.log('\
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
154
|
-
const statuses = response.response.data.statuses;
|
|
155
|
-
for (const status of statuses) {
|
|
156
|
-
if (status.filled) {
|
|
157
|
-
result.filled = parseFloat(status.filled.totalSz);
|
|
158
|
-
result.avgPrice = parseFloat(status.filled.avgPx);
|
|
159
|
-
result.status = result.filled >= sliceSize * 0.99 ? 'filled' : 'partial';
|
|
160
|
-
totalFilled += result.filled;
|
|
161
|
-
totalCost += result.filled * result.avgPrice;
|
|
162
|
-
console.log(` ✅ Filled ${result.filled} @ ${formatUsd(result.avgPrice)}`);
|
|
163
|
-
} else if (status.error) {
|
|
164
|
-
result.status = 'failed';
|
|
165
|
-
result.error = status.error;
|
|
166
|
-
console.log(` ❌ Error: ${status.error}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
result.status = 'failed';
|
|
171
|
-
result.error = typeof response.response === 'string' ? response.response : 'Unknown error';
|
|
172
|
-
console.log(` ❌ Failed: ${result.error}`);
|
|
173
|
-
}
|
|
174
|
-
} catch (err) {
|
|
175
|
-
result.status = 'failed';
|
|
176
|
-
result.error = err instanceof Error ? err.message : String(err);
|
|
177
|
-
console.log(` ❌ Error: ${result.error}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
results.push(result);
|
|
181
|
-
|
|
182
|
-
// Wait for next interval (unless last slice)
|
|
183
|
-
if (i < intervals - 1) {
|
|
184
|
-
const jitter = randomize > 0 ? (Math.random() - 0.5) * 2 * (randomize / 100) : 0;
|
|
185
|
-
const waitTime = Math.max(1000, baseInterval * (1 + jitter));
|
|
186
|
-
console.log(` Waiting ${formatDuration(waitTime / 1000)} until next slice...\n`);
|
|
187
|
-
await sleep(waitTime);
|
|
188
|
-
}
|
|
114
|
+
console.log('\nPlacing native TWAP order...\n');
|
|
115
|
+
|
|
116
|
+
const response = await client.twapOrder(
|
|
117
|
+
coin,
|
|
118
|
+
isBuy,
|
|
119
|
+
totalSize,
|
|
120
|
+
durationMinutes,
|
|
121
|
+
randomize,
|
|
122
|
+
reduceOnly,
|
|
123
|
+
leverage,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const status = response.response.data.status;
|
|
127
|
+
if ('running' in status) {
|
|
128
|
+
console.log(`TWAP order placed successfully!`);
|
|
129
|
+
console.log(`TWAP ID: ${status.running.twapId}`);
|
|
130
|
+
console.log(`\nThe exchange is now executing your TWAP order over ${formatDuration(durationMinutes * 60)}.`);
|
|
131
|
+
console.log(`To cancel: openbroker twap-cancel --coin ${coin} --twap-id ${status.running.twapId}`);
|
|
132
|
+
console.log(`To check status: openbroker twap-status`);
|
|
133
|
+
} else if ('error' in status) {
|
|
134
|
+
console.error(`TWAP order failed: ${status.error}`);
|
|
135
|
+
process.exit(1);
|
|
189
136
|
}
|
|
190
|
-
|
|
191
|
-
// Summary
|
|
192
|
-
const endTime = Date.now();
|
|
193
|
-
const actualDuration = (endTime - startTime) / 1000;
|
|
194
|
-
const vwap = totalCost / totalFilled;
|
|
195
|
-
const currentPrice = parseFloat((await client.getAllMids())[coin]);
|
|
196
|
-
const slippageVsMid = isBuy
|
|
197
|
-
? (vwap - midPrice) / midPrice
|
|
198
|
-
: (midPrice - vwap) / midPrice;
|
|
199
|
-
|
|
200
|
-
console.log('\n========== TWAP Summary ==========');
|
|
201
|
-
console.log(`Total Filled: ${totalFilled.toFixed(6)} / ${totalSize} (${((totalFilled / totalSize) * 100).toFixed(1)}%)`);
|
|
202
|
-
console.log(`VWAP: ${formatUsd(vwap)}`);
|
|
203
|
-
console.log(`Start Price: ${formatUsd(midPrice)}`);
|
|
204
|
-
console.log(`End Price: ${formatUsd(currentPrice)}`);
|
|
205
|
-
console.log(`Slippage vs Mid: ${(slippageVsMid * 10000).toFixed(1)} bps`);
|
|
206
|
-
console.log(`Total Cost: ${formatUsd(totalCost)}`);
|
|
207
|
-
console.log(`Actual Duration: ${formatDuration(actualDuration)}`);
|
|
208
|
-
console.log(`Successful: ${results.filter(r => r.status === 'filled').length}/${intervals} slices`);
|
|
209
|
-
|
|
210
137
|
} catch (error) {
|
|
211
|
-
console.error('Error:', error);
|
|
138
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
212
139
|
process.exit(1);
|
|
213
140
|
}
|
|
214
141
|
}
|
package/scripts/plugin/tools.ts
CHANGED
|
@@ -1231,35 +1231,137 @@ export function createTools(watcherOrCtx: PositionWatcher | null | ToolsContext)
|
|
|
1231
1231
|
|
|
1232
1232
|
{
|
|
1233
1233
|
name: 'ob_twap',
|
|
1234
|
-
description: '
|
|
1234
|
+
description: 'Place a native Hyperliquid TWAP order. The exchange handles order slicing and timing server-side. Returns immediately with a TWAP ID.',
|
|
1235
1235
|
parameters: {
|
|
1236
1236
|
type: 'object',
|
|
1237
1237
|
properties: {
|
|
1238
|
-
coin: { type: 'string', description: 'Asset symbol' },
|
|
1238
|
+
coin: { type: 'string', description: 'Asset symbol (e.g., ETH, BTC)' },
|
|
1239
1239
|
side: { type: 'string', enum: ['buy', 'sell'], description: 'Order direction' },
|
|
1240
|
-
size: { type: 'number', description: 'Total order size' },
|
|
1241
|
-
duration: { type: 'number', description: 'Duration in
|
|
1242
|
-
|
|
1243
|
-
|
|
1240
|
+
size: { type: 'number', description: 'Total order size in base asset' },
|
|
1241
|
+
duration: { type: 'number', description: 'Duration in minutes (5–1440)' },
|
|
1242
|
+
randomize: { type: 'boolean', description: 'Randomize timing (default: true)' },
|
|
1243
|
+
reduce_only: { type: 'boolean', description: 'Reduce-only order (default: false)' },
|
|
1244
1244
|
leverage: { type: 'number', description: 'Set leverage (e.g., 10 for 10x)' },
|
|
1245
|
-
dry: { type: 'boolean', description: 'Preview without executing' },
|
|
1246
1245
|
},
|
|
1247
1246
|
required: ['coin', 'side', 'size', 'duration'],
|
|
1248
1247
|
},
|
|
1249
1248
|
async execute(_id, params) {
|
|
1250
|
-
const {
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1249
|
+
const { getClient } = await import('../core/client.js');
|
|
1250
|
+
const coin = normalizeCoin(params.coin as string);
|
|
1251
|
+
const isBuy = params.side === 'buy';
|
|
1252
|
+
const size = params.size as number;
|
|
1253
|
+
const durationMinutes = params.duration as number;
|
|
1254
|
+
const randomize = params.randomize !== false;
|
|
1255
|
+
const reduceOnly = params.reduce_only === true;
|
|
1256
|
+
const leverage = params.leverage as number | undefined;
|
|
1257
|
+
|
|
1258
|
+
if (durationMinutes < 5 || durationMinutes > 1440) {
|
|
1259
|
+
return error('Duration must be between 5 and 1440 minutes');
|
|
1256
1260
|
}
|
|
1257
1261
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1262
|
+
try {
|
|
1263
|
+
const client = getClient();
|
|
1264
|
+
const mids = await client.getAllMids();
|
|
1265
|
+
const midPrice = parseFloat(mids[coin]);
|
|
1266
|
+
|
|
1267
|
+
const response = await client.twapOrder(coin, isBuy, size, durationMinutes, randomize, reduceOnly, leverage);
|
|
1268
|
+
const status = response.response.data.status;
|
|
1269
|
+
|
|
1270
|
+
if ('running' in status) {
|
|
1271
|
+
return json({
|
|
1272
|
+
twapId: status.running.twapId,
|
|
1273
|
+
coin,
|
|
1274
|
+
side: isBuy ? 'buy' : 'sell',
|
|
1275
|
+
size,
|
|
1276
|
+
durationMinutes,
|
|
1277
|
+
randomize,
|
|
1278
|
+
reduceOnly,
|
|
1279
|
+
estimatedNotional: midPrice ? midPrice * size : undefined,
|
|
1280
|
+
midPrice: midPrice || undefined,
|
|
1281
|
+
});
|
|
1282
|
+
} else if ('error' in status) {
|
|
1283
|
+
return error(status.error);
|
|
1284
|
+
}
|
|
1285
|
+
return error('Unexpected response');
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
return error(err instanceof Error ? err.message : String(err));
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
},
|
|
1291
|
+
|
|
1292
|
+
{
|
|
1293
|
+
name: 'ob_twap_cancel',
|
|
1294
|
+
description: 'Cancel a running native Hyperliquid TWAP order by its TWAP ID.',
|
|
1295
|
+
parameters: {
|
|
1296
|
+
type: 'object',
|
|
1297
|
+
properties: {
|
|
1298
|
+
coin: { type: 'string', description: 'Asset symbol (e.g., ETH)' },
|
|
1299
|
+
twap_id: { type: 'number', description: 'TWAP order ID to cancel' },
|
|
1300
|
+
},
|
|
1301
|
+
required: ['coin', 'twap_id'],
|
|
1302
|
+
},
|
|
1303
|
+
async execute(_id, params) {
|
|
1304
|
+
const { getClient } = await import('../core/client.js');
|
|
1305
|
+
const coin = normalizeCoin(params.coin as string);
|
|
1306
|
+
const twapId = params.twap_id as number;
|
|
1307
|
+
|
|
1308
|
+
try {
|
|
1309
|
+
const client = getClient();
|
|
1310
|
+
const response = await client.twapCancel(coin, twapId);
|
|
1311
|
+
const status = response.response.data.status;
|
|
1312
|
+
|
|
1313
|
+
if (typeof status === 'string' && status === 'success') {
|
|
1314
|
+
return json({ cancelled: true, coin, twapId });
|
|
1315
|
+
} else if (typeof status === 'object' && 'error' in status) {
|
|
1316
|
+
return error(status.error);
|
|
1317
|
+
}
|
|
1318
|
+
return error('Unexpected response');
|
|
1319
|
+
} catch (err) {
|
|
1320
|
+
return error(err instanceof Error ? err.message : String(err));
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
|
|
1325
|
+
{
|
|
1326
|
+
name: 'ob_twap_status',
|
|
1327
|
+
description: 'View TWAP order history and status. Shows active and past TWAP orders.',
|
|
1328
|
+
parameters: {
|
|
1329
|
+
type: 'object',
|
|
1330
|
+
properties: {
|
|
1331
|
+
active: { type: 'boolean', description: 'Only show active/running TWAP orders' },
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
async execute(_id, params) {
|
|
1335
|
+
const { getClient } = await import('../core/client.js');
|
|
1336
|
+
|
|
1337
|
+
try {
|
|
1338
|
+
const client = getClient();
|
|
1339
|
+
const history = await client.twapHistory();
|
|
1340
|
+
|
|
1341
|
+
const filtered = params.active
|
|
1342
|
+
? history.filter((h: { status: { status: string } }) => h.status.status === 'activated')
|
|
1343
|
+
: history;
|
|
1344
|
+
|
|
1345
|
+
return json(filtered.map((entry: {
|
|
1346
|
+
twapId?: number;
|
|
1347
|
+
state: { coin: string; side: string; sz: string; executedSz: string; executedNtl: string; minutes: number; randomize: boolean; reduceOnly: boolean; timestamp: number };
|
|
1348
|
+
status: { status: string; description?: string };
|
|
1349
|
+
}) => ({
|
|
1350
|
+
twapId: entry.twapId,
|
|
1351
|
+
coin: entry.state.coin,
|
|
1352
|
+
side: entry.state.side === 'B' ? 'buy' : 'sell',
|
|
1353
|
+
totalSize: entry.state.sz,
|
|
1354
|
+
executedSize: entry.state.executedSz,
|
|
1355
|
+
executedNotional: entry.state.executedNtl,
|
|
1356
|
+
durationMinutes: entry.state.minutes,
|
|
1357
|
+
randomize: entry.state.randomize,
|
|
1358
|
+
reduceOnly: entry.state.reduceOnly,
|
|
1359
|
+
status: entry.status.status,
|
|
1360
|
+
startedAt: new Date(entry.state.timestamp).toISOString(),
|
|
1361
|
+
})));
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
return error(err instanceof Error ? err.message : String(err));
|
|
1364
|
+
}
|
|
1263
1365
|
},
|
|
1264
1366
|
},
|
|
1265
1367
|
|