openbroker 1.0.47 → 1.0.48
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 +4 -3
- package/SKILL.md +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/core/client.ts +17 -23
- package/scripts/core/utils.ts +12 -0
- package/scripts/info/candles.ts +3 -3
- package/scripts/info/fills.ts +2 -2
- package/scripts/info/funding-history.ts +3 -3
- package/scripts/info/funding-scan.ts +2 -1
- package/scripts/info/funding.ts +4 -3
- package/scripts/info/markets.ts +4 -3
- package/scripts/info/orders.ts +2 -2
- package/scripts/info/trades.ts +3 -3
- package/scripts/plugin/tools.ts +21 -18
- package/scripts/strategies/funding-arb.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Open Broker will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [1.0.
|
|
5
|
+
## [1.0.48] - 2026-03-09
|
|
6
6
|
|
|
7
7
|
### Fixed
|
|
8
8
|
- **HIP-3 Perp Trading**: All trading commands now work with HIP-3 assets using `dex:COIN` syntax (e.g., `--coin xyz:CL`)
|
|
9
9
|
- `getMetaAndAssetCtxs()` loads HIP-3 dex assets into asset/szDecimals maps (asset index = `10000 * dexIdx + assetIdx`)
|
|
10
10
|
- `getAllMids()` fetches and merges mid prices from all HIP-3 dexes
|
|
11
|
-
- `getCandleSnapshot()`, `getFundingHistory()`, `getRecentTrades()`, `getL2Book()` pass correct `dex` param for HIP-3 coins
|
|
12
11
|
- Market, limit, trigger, bracket, TWAP, scale, chase orders all work with HIP-3 assets
|
|
13
|
-
- **HIP-3 Info Commands**: `
|
|
12
|
+
- **HIP-3 Info Commands**: `funding`, `funding-history`, `candles`, `trades` now return data for HIP-3 assets (previously returned "No data" / null)
|
|
13
|
+
- **API Name Format**: Hyperliquid API returns HIP-3 names already prefixed (e.g., `xyz:CL`); fixed double-prefixing bug that caused all HIP-3 lookups to fail
|
|
14
|
+
- **Case Normalization**: Added `normalizeCoin()` helper — keeps dex prefix lowercase, uppercases asset (`xyz:cl` → `xyz:CL`). Fixes `toUpperCase()` mangling HIP-3 tickers to `XYZ:CL`
|
|
14
15
|
- **Better Error Messages**: When a bare coin name (e.g., `CL`) matches HIP-3 assets, the error now suggests the prefixed ticker (e.g., `xyz:CL`)
|
|
15
16
|
|
|
16
17
|
### Added
|
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring. Exe
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
|
|
6
6
|
homepage: https://www.npmjs.com/package/openbroker
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "1.0.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.48", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
|
|
8
8
|
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_bracket ob_chase ob_watcher_status Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/core/client.ts
CHANGED
|
@@ -157,12 +157,15 @@ export class HyperliquidClient {
|
|
|
157
157
|
this.log(`Loading HIP-3 dex: ${dex.name} with ${universe.length} markets`);
|
|
158
158
|
|
|
159
159
|
universe.forEach((asset, assetIdx) => {
|
|
160
|
-
|
|
160
|
+
// API returns names already prefixed (e.g., "xyz:CL"), use as-is
|
|
161
|
+
const coinName = asset.name;
|
|
162
|
+
// Extract local name by stripping dex prefix if present
|
|
163
|
+
const localName = coinName.startsWith(dex.name + ':') ? coinName.slice(dex.name.length + 1) : coinName;
|
|
161
164
|
const globalIndex = 10000 * dexIdx + assetIdx;
|
|
162
165
|
|
|
163
|
-
this.assetMap.set(
|
|
164
|
-
this.szDecimalsMap.set(
|
|
165
|
-
this.coinDexMap.set(
|
|
166
|
+
this.assetMap.set(coinName, globalIndex);
|
|
167
|
+
this.szDecimalsMap.set(coinName, asset.szDecimals);
|
|
168
|
+
this.coinDexMap.set(coinName, { dexName: dex.name, dexIdx, localName });
|
|
166
169
|
});
|
|
167
170
|
}
|
|
168
171
|
} catch (e) {
|
|
@@ -197,9 +200,9 @@ export class HyperliquidClient {
|
|
|
197
200
|
});
|
|
198
201
|
const dexMids = await dexResponse.json() as Record<string, string>;
|
|
199
202
|
|
|
200
|
-
// Merge
|
|
203
|
+
// Merge directly — API already returns prefixed keys (e.g., "xyz:CL")
|
|
201
204
|
for (const [coin, mid] of Object.entries(dexMids)) {
|
|
202
|
-
response[
|
|
205
|
+
response[coin] = mid;
|
|
203
206
|
}
|
|
204
207
|
} catch (e) {
|
|
205
208
|
this.log(`Failed to fetch mids for HIP-3 dex ${dex.name}:`, e);
|
|
@@ -506,8 +509,8 @@ export class HyperliquidClient {
|
|
|
506
509
|
spreadBps: number;
|
|
507
510
|
}> {
|
|
508
511
|
this.log('Fetching l2Book for:', coin);
|
|
509
|
-
|
|
510
|
-
const response = await this.info.l2Book({ coin
|
|
512
|
+
// API accepts prefixed names directly (e.g., "xyz:CL")
|
|
513
|
+
const response = await this.info.l2Book({ coin });
|
|
511
514
|
|
|
512
515
|
const bids = response.levels[0] as Array<{ px: string; sz: string; n: number }>;
|
|
513
516
|
const asks = response.levels[1] as Array<{ px: string; sz: string; n: number }>;
|
|
@@ -954,12 +957,9 @@ export class HyperliquidClient {
|
|
|
954
957
|
? 'https://api.hyperliquid.xyz'
|
|
955
958
|
: 'https://api.hyperliquid-testnet.xyz';
|
|
956
959
|
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
const req: Record<string, unknown> = { coin: localName, interval, startTime };
|
|
960
|
+
// API accepts prefixed names directly (e.g., "xyz:CL")
|
|
961
|
+
const req: Record<string, unknown> = { coin, interval, startTime };
|
|
961
962
|
if (endTime !== undefined) req.endTime = endTime;
|
|
962
|
-
if (dex) req.dex = dex;
|
|
963
963
|
|
|
964
964
|
const response = await fetch(baseUrl + '/info', {
|
|
965
965
|
method: 'POST',
|
|
@@ -989,12 +989,9 @@ export class HyperliquidClient {
|
|
|
989
989
|
? 'https://api.hyperliquid.xyz'
|
|
990
990
|
: 'https://api.hyperliquid-testnet.xyz';
|
|
991
991
|
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
const body: Record<string, unknown> = { type: 'fundingHistory', coin: localName, startTime };
|
|
992
|
+
// API accepts prefixed names directly (e.g., "xyz:CL")
|
|
993
|
+
const body: Record<string, unknown> = { type: 'fundingHistory', coin, startTime };
|
|
996
994
|
if (endTime !== undefined) body.endTime = endTime;
|
|
997
|
-
if (dex) body.dex = dex;
|
|
998
995
|
|
|
999
996
|
const response = await fetch(baseUrl + '/info', {
|
|
1000
997
|
method: 'POST',
|
|
@@ -1023,11 +1020,8 @@ export class HyperliquidClient {
|
|
|
1023
1020
|
? 'https://api.hyperliquid.xyz'
|
|
1024
1021
|
: 'https://api.hyperliquid-testnet.xyz';
|
|
1025
1022
|
|
|
1026
|
-
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
const body: Record<string, unknown> = { type: 'recentTrades', coin: localName };
|
|
1030
|
-
if (dex) body.dex = dex;
|
|
1023
|
+
// API accepts prefixed names directly (e.g., "xyz:CL")
|
|
1024
|
+
const body: Record<string, unknown> = { type: 'recentTrades', coin };
|
|
1031
1025
|
|
|
1032
1026
|
const response = await fetch(baseUrl + '/info', {
|
|
1033
1027
|
method: 'POST',
|
package/scripts/core/utils.ts
CHANGED
|
@@ -119,6 +119,18 @@ export function parseArgs(args: string[]): Record<string, string | boolean> {
|
|
|
119
119
|
return result;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Normalize a coin name: uppercase the asset, keep dex prefix lowercase.
|
|
124
|
+
* "eth" → "ETH", "xyz:cl" → "xyz:CL", "xyz:CL" → "xyz:CL"
|
|
125
|
+
*/
|
|
126
|
+
export function normalizeCoin(coin: string): string {
|
|
127
|
+
const colonIdx = coin.indexOf(':');
|
|
128
|
+
if (colonIdx >= 0) {
|
|
129
|
+
return coin.slice(0, colonIdx).toLowerCase() + ':' + coin.slice(colonIdx + 1).toUpperCase();
|
|
130
|
+
}
|
|
131
|
+
return coin.toUpperCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
122
134
|
/**
|
|
123
135
|
* Sleep for specified milliseconds
|
|
124
136
|
*/
|
package/scripts/info/candles.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// View OHLCV candle data from Hyperliquid
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
const VALID_INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '3d', '1w', '1M'];
|
|
8
8
|
|
|
@@ -61,7 +61,7 @@ async function main() {
|
|
|
61
61
|
const bars = parseInt(args.bars as string) || 24;
|
|
62
62
|
const client = getClient();
|
|
63
63
|
|
|
64
|
-
console.log(`Open Broker - ${coin
|
|
64
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Candles (${interval})`);
|
|
65
65
|
console.log('='.repeat(40) + '\n');
|
|
66
66
|
|
|
67
67
|
try {
|
|
@@ -70,7 +70,7 @@ async function main() {
|
|
|
70
70
|
|
|
71
71
|
const now = Date.now();
|
|
72
72
|
const startTime = now - (bars * (INTERVAL_MS[interval] || 3_600_000));
|
|
73
|
-
const candles = await client.getCandleSnapshot(coin
|
|
73
|
+
const candles = await client.getCandleSnapshot(normalizeCoin(coin), interval, startTime);
|
|
74
74
|
|
|
75
75
|
if (candles.length === 0) {
|
|
76
76
|
console.log('No candle data found');
|
package/scripts/info/fills.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// View trade fill history from Hyperliquid
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -41,7 +41,7 @@ async function main() {
|
|
|
41
41
|
let fills = await client.getUserFills();
|
|
42
42
|
|
|
43
43
|
if (filterCoin) {
|
|
44
|
-
fills = fills.filter(f => f.coin === filterCoin
|
|
44
|
+
fills = fills.filter(f => f.coin === normalizeCoin(filterCoin));
|
|
45
45
|
}
|
|
46
46
|
if (filterSide) {
|
|
47
47
|
const sideCode = filterSide.toLowerCase() === 'buy' ? 'B' : 'A';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// View historical funding rates for an asset on Hyperliquid
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatPercent, annualizeFundingRate, parseArgs } from '../core/utils.js';
|
|
5
|
+
import { formatPercent, annualizeFundingRate, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -37,7 +37,7 @@ async function main() {
|
|
|
37
37
|
const hours = parseInt(args.hours as string) || 24;
|
|
38
38
|
const client = getClient();
|
|
39
39
|
|
|
40
|
-
console.log(`Open Broker - ${coin
|
|
40
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Funding History (${hours}h)`);
|
|
41
41
|
console.log('='.repeat(40) + '\n');
|
|
42
42
|
|
|
43
43
|
try {
|
|
@@ -46,7 +46,7 @@ async function main() {
|
|
|
46
46
|
|
|
47
47
|
const now = Date.now();
|
|
48
48
|
const startTime = now - (hours * 3_600_000);
|
|
49
|
-
const history = await client.getFundingHistory(coin
|
|
49
|
+
const history = await client.getFundingHistory(normalizeCoin(coin), startTime);
|
|
50
50
|
|
|
51
51
|
if (history.length === 0) {
|
|
52
52
|
console.log('No funding history found');
|
|
@@ -67,7 +67,8 @@ async function scanFunding(client: ReturnType<typeof getClient>, options: {
|
|
|
67
67
|
if (Math.abs(annualizedPct) < options.threshold) continue;
|
|
68
68
|
if (openInterest < 100) continue;
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
// API returns HIP-3 names already prefixed (e.g., "xyz:CL")
|
|
71
|
+
const coin = asset.name;
|
|
71
72
|
|
|
72
73
|
results.push({
|
|
73
74
|
coin,
|
package/scripts/info/funding.ts
CHANGED
|
@@ -68,8 +68,9 @@ async function main() {
|
|
|
68
68
|
const ctx = dexData.assetCtxs[i];
|
|
69
69
|
if (!ctx) continue;
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// API returns names already prefixed (e.g., "xyz:CL")
|
|
72
|
+
const coinName = asset.name;
|
|
73
|
+
if (filterCoin && coinName !== filterCoin) continue;
|
|
73
74
|
|
|
74
75
|
const hourlyRate = parseFloat(ctx.funding);
|
|
75
76
|
const annualizedRate = annualizeFundingRate(hourlyRate);
|
|
@@ -79,7 +80,7 @@ async function main() {
|
|
|
79
80
|
if (!showAll && openInterest < 1000) continue;
|
|
80
81
|
|
|
81
82
|
fundingData.push({
|
|
82
|
-
coin:
|
|
83
|
+
coin: coinName,
|
|
83
84
|
hourlyRate,
|
|
84
85
|
annualizedRate,
|
|
85
86
|
premium: 0,
|
package/scripts/info/markets.ts
CHANGED
|
@@ -71,8 +71,9 @@ async function main() {
|
|
|
71
71
|
const ctx = dexData.assetCtxs[i];
|
|
72
72
|
if (!ctx) continue;
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// API returns names already prefixed (e.g., "xyz:CL")
|
|
75
|
+
const coinName = asset.name;
|
|
76
|
+
if (filterCoin && coinName !== filterCoin) continue;
|
|
76
77
|
|
|
77
78
|
const markPx = parseFloat(ctx.markPx);
|
|
78
79
|
const oraclePx = parseFloat(ctx.oraclePx);
|
|
@@ -82,7 +83,7 @@ async function main() {
|
|
|
82
83
|
const change24h = prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0;
|
|
83
84
|
|
|
84
85
|
markets.push({
|
|
85
|
-
coin:
|
|
86
|
+
coin: coinName,
|
|
86
87
|
markPx,
|
|
87
88
|
oraclePx,
|
|
88
89
|
prevDayPx,
|
package/scripts/info/orders.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// View historical orders from Hyperliquid
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -41,7 +41,7 @@ async function main() {
|
|
|
41
41
|
let orders = await client.getHistoricalOrders();
|
|
42
42
|
|
|
43
43
|
if (filterCoin) {
|
|
44
|
-
orders = orders.filter(o => o.order.coin === filterCoin
|
|
44
|
+
orders = orders.filter(o => o.order.coin === normalizeCoin(filterCoin));
|
|
45
45
|
}
|
|
46
46
|
if (filterStatus) {
|
|
47
47
|
const s = filterStatus.toLowerCase();
|
package/scripts/info/trades.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// View recent trades (tape) for an asset on Hyperliquid
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
import { formatUsd, parseArgs, normalizeCoin } from '../core/utils.js';
|
|
6
6
|
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -37,14 +37,14 @@ async function main() {
|
|
|
37
37
|
const top = parseInt(args.top as string) || 30;
|
|
38
38
|
const client = getClient();
|
|
39
39
|
|
|
40
|
-
console.log(`Open Broker - ${coin
|
|
40
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Recent Trades`);
|
|
41
41
|
console.log('='.repeat(40) + '\n');
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
44
|
// Load metadata (needed for HIP-3 coin resolution)
|
|
45
45
|
await client.getMetaAndAssetCtxs();
|
|
46
46
|
|
|
47
|
-
let trades = await client.getRecentTrades(coin
|
|
47
|
+
let trades = await client.getRecentTrades(normalizeCoin(coin));
|
|
48
48
|
|
|
49
49
|
// Most recent first
|
|
50
50
|
trades.sort((a, b) => b.time - a.time);
|
package/scripts/plugin/tools.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { PluginTool } from './types.js';
|
|
5
5
|
import type { PositionWatcher } from './watcher.js';
|
|
6
|
+
import { normalizeCoin } from '../core/utils.js';
|
|
6
7
|
|
|
7
8
|
/** Helper to wrap a result as OpenClaw tool response */
|
|
8
9
|
function json(payload: unknown) {
|
|
@@ -102,7 +103,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
102
103
|
}));
|
|
103
104
|
|
|
104
105
|
if (params.coin) {
|
|
105
|
-
const coin = (params.coin as string)
|
|
106
|
+
const coin = normalizeCoin(params.coin as string);
|
|
106
107
|
positions = positions.filter(p => p.coin === coin);
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -139,7 +140,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
139
140
|
});
|
|
140
141
|
|
|
141
142
|
if (params.coin) {
|
|
142
|
-
const coin = (params.coin as string)
|
|
143
|
+
const coin = normalizeCoin(params.coin as string);
|
|
143
144
|
results = results.filter(r => r.coin === coin);
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -181,7 +182,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
181
182
|
}));
|
|
182
183
|
|
|
183
184
|
if (params.coin) {
|
|
184
|
-
const coin = (params.coin as string)
|
|
185
|
+
const coin = normalizeCoin(params.coin as string);
|
|
185
186
|
markets = markets.filter(m => m.coin === coin);
|
|
186
187
|
}
|
|
187
188
|
|
|
@@ -238,7 +239,8 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
238
239
|
const asset = dexData.meta.universe[i];
|
|
239
240
|
if (asset.name.toUpperCase().includes(query)) {
|
|
240
241
|
results.push({
|
|
241
|
-
|
|
242
|
+
// API returns names already prefixed (e.g., "xyz:CL")
|
|
243
|
+
coin: asset.name,
|
|
242
244
|
type: 'hip3',
|
|
243
245
|
dex: dexData.dexName,
|
|
244
246
|
markPx: dexData.assetCtxs[i]?.markPx,
|
|
@@ -315,7 +317,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
315
317
|
let fills = await client.getUserFills();
|
|
316
318
|
|
|
317
319
|
if (params.coin) {
|
|
318
|
-
const coin = (params.coin as string)
|
|
320
|
+
const coin = normalizeCoin(params.coin as string);
|
|
319
321
|
fills = fills.filter(f => f.coin === coin);
|
|
320
322
|
}
|
|
321
323
|
if (params.side) {
|
|
@@ -366,7 +368,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
366
368
|
let orders = await client.getHistoricalOrders();
|
|
367
369
|
|
|
368
370
|
if (params.coin) {
|
|
369
|
-
const coin = (params.coin as string)
|
|
371
|
+
const coin = normalizeCoin(params.coin as string);
|
|
370
372
|
orders = orders.filter(o => o.order.coin === coin);
|
|
371
373
|
}
|
|
372
374
|
if (params.status) {
|
|
@@ -485,7 +487,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
485
487
|
const { getClient } = await import('../core/client.js');
|
|
486
488
|
const client = getClient();
|
|
487
489
|
|
|
488
|
-
const coin = (params.coin as string)
|
|
490
|
+
const coin = normalizeCoin(params.coin as string);
|
|
489
491
|
const interval = (params.interval as string) || '1h';
|
|
490
492
|
const bars = (params.bars as number) || 24;
|
|
491
493
|
|
|
@@ -533,7 +535,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
533
535
|
const { annualizeFundingRate } = await import('../core/utils.js');
|
|
534
536
|
const client = getClient();
|
|
535
537
|
|
|
536
|
-
const coin = (params.coin as string)
|
|
538
|
+
const coin = normalizeCoin(params.coin as string);
|
|
537
539
|
const hours = (params.hours as number) || 24;
|
|
538
540
|
const startTime = Date.now() - (hours * 3_600_000);
|
|
539
541
|
|
|
@@ -574,7 +576,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
574
576
|
const { getClient } = await import('../core/client.js');
|
|
575
577
|
const client = getClient();
|
|
576
578
|
|
|
577
|
-
const coin = (params.coin as string)
|
|
579
|
+
const coin = normalizeCoin(params.coin as string);
|
|
578
580
|
// Load metadata for HIP-3 coin resolution
|
|
579
581
|
await client.getMetaAndAssetCtxs();
|
|
580
582
|
let trades = await client.getRecentTrades(coin);
|
|
@@ -673,7 +675,8 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
673
675
|
if (openInterest < 100) continue;
|
|
674
676
|
|
|
675
677
|
results.push({
|
|
676
|
-
|
|
678
|
+
// API returns HIP-3 names already prefixed (e.g., "xyz:CL")
|
|
679
|
+
coin: asset.name,
|
|
677
680
|
dex: dexData.dexName ?? 'main',
|
|
678
681
|
annualizedPct: annualizedPct.toFixed(1),
|
|
679
682
|
direction: hourlyRate > 0 ? 'longs pay shorts' : 'shorts pay longs',
|
|
@@ -742,7 +745,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
742
745
|
|
|
743
746
|
if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
|
|
744
747
|
|
|
745
|
-
const coin = (params.coin as string)
|
|
748
|
+
const coin = normalizeCoin(params.coin as string);
|
|
746
749
|
const size = params.size as number;
|
|
747
750
|
const slippageBps = (params.slippage as number) ?? client.builderInfo.f;
|
|
748
751
|
|
|
@@ -791,7 +794,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
791
794
|
|
|
792
795
|
if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
|
|
793
796
|
|
|
794
|
-
const coin = (params.coin as string)
|
|
797
|
+
const coin = normalizeCoin(params.coin as string);
|
|
795
798
|
const size = params.size as number;
|
|
796
799
|
const slippageBps = (params.slippage as number) ?? client.builderInfo.f;
|
|
797
800
|
|
|
@@ -843,7 +846,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
843
846
|
|
|
844
847
|
if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
|
|
845
848
|
|
|
846
|
-
const coin = (params.coin as string)
|
|
849
|
+
const coin = normalizeCoin(params.coin as string);
|
|
847
850
|
const isBuy = params.side === 'buy';
|
|
848
851
|
const size = params.size as number;
|
|
849
852
|
const price = params.price as number;
|
|
@@ -900,7 +903,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
900
903
|
|
|
901
904
|
if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
|
|
902
905
|
|
|
903
|
-
const coin = (params.coin as string)
|
|
906
|
+
const coin = normalizeCoin(params.coin as string);
|
|
904
907
|
const isBuy = params.side === 'buy';
|
|
905
908
|
const size = params.size as number;
|
|
906
909
|
const triggerPrice = params.trigger as number;
|
|
@@ -954,7 +957,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
954
957
|
if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
|
|
955
958
|
if (!params.tp && !params.sl) return error('At least one of tp or sl is required.');
|
|
956
959
|
|
|
957
|
-
const coin = (params.coin as string)
|
|
960
|
+
const coin = normalizeCoin(params.coin as string);
|
|
958
961
|
|
|
959
962
|
// Get current position
|
|
960
963
|
const state = await client.getUserState();
|
|
@@ -1028,12 +1031,12 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
1028
1031
|
if (params.oid) {
|
|
1029
1032
|
const coin = params.coin as string | undefined;
|
|
1030
1033
|
if (!coin) return error('--coin is required when cancelling by order ID');
|
|
1031
|
-
const result = await client.cancel(coin
|
|
1032
|
-
return json({ action: 'cancel', coin: coin
|
|
1034
|
+
const result = await client.cancel(normalizeCoin(coin), params.oid as number);
|
|
1035
|
+
return json({ action: 'cancel', coin: normalizeCoin(coin), oid: params.oid, result });
|
|
1033
1036
|
}
|
|
1034
1037
|
|
|
1035
1038
|
if (params.all || params.coin) {
|
|
1036
|
-
const coin = params.coin ? (params.coin as string)
|
|
1039
|
+
const coin = params.coin ? normalizeCoin(params.coin as string) : undefined;
|
|
1037
1040
|
const results = await client.cancelAll(coin);
|
|
1038
1041
|
return json({ action: 'cancelAll', coin: coin ?? 'all', results });
|
|
1039
1042
|
}
|
|
@@ -105,13 +105,13 @@ async function main() {
|
|
|
105
105
|
if (client.isHip3(coin)) {
|
|
106
106
|
const allPerps = await client.getAllPerpMetas();
|
|
107
107
|
const dexName = client.getCoinDex(coin);
|
|
108
|
-
const localName = client.getCoinLocalName(coin);
|
|
109
108
|
const dexData = allPerps.find(d => d.dexName === dexName);
|
|
110
109
|
if (!dexData) {
|
|
111
110
|
console.error(`Error: No market data for HIP-3 dex ${dexName}`);
|
|
112
111
|
process.exit(1);
|
|
113
112
|
}
|
|
114
|
-
|
|
113
|
+
// API returns universe names already prefixed (e.g., "xyz:CL")
|
|
114
|
+
const assetIdx = dexData.meta.universe.findIndex(a => a.name === coin);
|
|
115
115
|
if (assetIdx === -1 || !dexData.assetCtxs[assetIdx]) {
|
|
116
116
|
console.error(`Error: No market data for ${coin}`);
|
|
117
117
|
process.exit(1);
|
|
@@ -245,9 +245,9 @@ async function main() {
|
|
|
245
245
|
if (client.isHip3(coin)) {
|
|
246
246
|
const freshPerps = await client.getAllPerpMetas();
|
|
247
247
|
const dexName = client.getCoinDex(coin);
|
|
248
|
-
const localName = client.getCoinLocalName(coin);
|
|
249
248
|
const dexData = freshPerps.find(d => d.dexName === dexName);
|
|
250
|
-
|
|
249
|
+
// API returns universe names already prefixed (e.g., "xyz:CL")
|
|
250
|
+
const idx = dexData?.meta.universe.findIndex(a => a.name === coin) ?? -1;
|
|
251
251
|
newHourlyFunding = idx >= 0 && dexData?.assetCtxs[idx] ? parseFloat(dexData.assetCtxs[idx].funding) : 0;
|
|
252
252
|
} else {
|
|
253
253
|
const newMeta = await client.getMetaAndAssetCtxs();
|