openbroker 1.0.52 → 1.0.53

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 CHANGED
@@ -2,10 +2,15 @@
2
2
 
3
3
  All notable changes to Open Broker will be documented in this file.
4
4
 
5
+ ## [1.0.53] - 2026-03-09
6
+
7
+ ### Fixed
8
+ - **HIP-3 Isolated Margin: Use Max Leverage**: The 3x leverage cap was too conservative for isolated margin — at 3x a $83 CL position needs $28 margin, which gets rejected even on funded accounts. Now uses the asset's `maxLeverage` (e.g., 20x for CL, 25x for GOLD) to minimize margin requirement. At 20x isolated, the same position only needs ~$4 margin.
9
+
5
10
  ## [1.0.52] - 2026-03-09
6
11
 
7
12
  ### Fixed
8
- - **HIP-3 Trading: Isolated Margin**: HIP-3 perps require isolated margin mode (per Hyperliquid docs), but orders were sent without setting it — causing "Insufficient margin to place order" rejections. Now automatically sets isolated margin (3x or asset max, whichever is lower) on first order for each HIP-3 asset. Affects all trading commands: `buy`, `sell`, `market`, `limit`, `trigger`, `tpsl`, `bracket`, `chase`, `twap`, `scale`.
13
+ - **HIP-3 Trading: Isolated Margin**: HIP-3 perps require isolated margin mode (per Hyperliquid docs), but orders were sent without setting it — causing "Insufficient margin to place order" rejections. Now automatically sets isolated margin on first order for each HIP-3 asset. Affects all trading commands: `buy`, `sell`, `market`, `limit`, `trigger`, `tpsl`, `bracket`, `chase`, `twap`, `scale`.
9
14
 
10
15
  ## [1.0.51] - 2026-03-09
11
16
 
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.52", "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)"}]}}
7
+ metadata: {"author": "monemetrics", "version": "1.0.53", "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
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openbroker",
3
3
  "name": "OpenBroker — Hyperliquid Trading",
4
- "version": "1.0.52",
4
+ "version": "1.0.53",
5
5
  "description": "Trade on Hyperliquid DEX with background position monitoring",
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1065,12 +1065,41 @@ export class HyperliquidClient {
1065
1065
  return data;
1066
1066
  }
1067
1067
 
1068
- async getUserState(user?: string): Promise<ClearinghouseState> {
1069
- this.log('Fetching clearinghouseState for:', user ?? this.address);
1070
- const response = await this.info.clearinghouseState({ user: user ?? this.address });
1068
+ async getUserState(user?: string, dex?: string): Promise<ClearinghouseState> {
1069
+ this.log('Fetching clearinghouseState for:', user ?? this.address, dex ? `dex: ${dex}` : '');
1070
+ const params: { user: string; dex?: string } = { user: user ?? this.address };
1071
+ if (dex !== undefined) params.dex = dex;
1072
+ const response = await this.info.clearinghouseState(params as any);
1071
1073
  return response as ClearinghouseState;
1072
1074
  }
1073
1075
 
1076
+ /**
1077
+ * Get user state across all dexes (main + HIP-3).
1078
+ * Returns the main state with HIP-3 positions merged into assetPositions.
1079
+ */
1080
+ async getUserStateAll(user?: string): Promise<ClearinghouseState> {
1081
+ await this.getMetaAndAssetCtxs(); // Ensure HIP-3 dex list is loaded
1082
+
1083
+ const mainState = await this.getUserState(user);
1084
+ const dexs = await this.getPerpDexs();
1085
+
1086
+ for (let i = 1; i < dexs.length; i++) {
1087
+ const dex = dexs[i];
1088
+ if (!dex) continue;
1089
+
1090
+ try {
1091
+ const dexState = await this.getUserState(user, dex.name);
1092
+ if (dexState.assetPositions?.length > 0) {
1093
+ mainState.assetPositions.push(...dexState.assetPositions);
1094
+ }
1095
+ } catch (err) {
1096
+ this.log(`Failed to fetch state for dex ${dex.name}:`, err instanceof Error ? err.message : String(err));
1097
+ }
1098
+ }
1099
+
1100
+ return mainState;
1101
+ }
1102
+
1074
1103
  async getOpenOrders(user?: string): Promise<OpenOrder[]> {
1075
1104
  this.log('Fetching openOrders for:', user ?? this.address);
1076
1105
  const response = await this.info.openOrders({ user: user ?? this.address });
@@ -1080,37 +1109,51 @@ export class HyperliquidClient {
1080
1109
  // ============ Trading ============
1081
1110
 
1082
1111
  /**
1083
- * HIP-3 perps require isolated margin mode. Automatically sets isolated margin
1084
- * with default leverage (3x) on first order for each HIP-3 asset this session.
1112
+ * HIP-3 perps have independent margin per dex. Before ordering:
1113
+ * 1. Set isolated margin mode (required for HIP-3)
1114
+ * 2. Transfer USDC from main perp to the HIP-3 dex (each dex has its own balance)
1085
1115
  */
1086
- private async ensureHip3Isolated(coin: string): Promise<void> {
1116
+ private async ensureHip3Ready(coin: string, notional: number): Promise<void> {
1087
1117
  if (!this.isHip3(coin)) return;
1088
- if (this.hip3IsolatedSet.has(coin)) return;
1089
1118
 
1090
1119
  const dexInfo = this.coinDexMap.get(coin);
1091
- const maxLev = this.getHip3MaxLeverage(coin);
1092
- const leverage = Math.min(3, maxLev || 3);
1120
+ if (!dexInfo?.dexName) return;
1121
+
1122
+ // Set isolated margin on first order per asset
1123
+ if (!this.hip3IsolatedSet.has(coin)) {
1124
+ const maxLev = this.hip3MaxLeverageMap.get(coin) ?? 10;
1125
+ this.log(`HIP-3 asset ${coin} (dex: ${dexInfo.dexName}) — setting isolated margin at ${maxLev}x`);
1126
+ try {
1127
+ await this.updateLeverage(coin, maxLev, false); // false = isolated
1128
+ this.hip3IsolatedSet.add(coin);
1129
+ } catch (err) {
1130
+ this.log(`Failed to set isolated margin for ${coin}:`, err instanceof Error ? err.message : String(err));
1131
+ this.hip3IsolatedSet.add(coin);
1132
+ }
1133
+ }
1093
1134
 
1094
- this.log(`HIP-3 asset ${coin} (dex: ${dexInfo?.dexName}) setting isolated margin at ${leverage}x`);
1135
+ // Transfer USDC to the HIP-3 dex to cover margin
1136
+ const maxLev = this.hip3MaxLeverageMap.get(coin) ?? 10;
1137
+ const requiredMargin = notional / maxLev;
1138
+ // Add 20% buffer for fees and slippage
1139
+ const transferAmount = Math.ceil(requiredMargin * 1.2 * 100) / 100;
1140
+
1141
+ this.log(`HIP-3 margin transfer: ${transferAmount} USDC from main → ${dexInfo.dexName} (notional: ${notional}, leverage: ${maxLev}x)`);
1095
1142
  try {
1096
- await this.updateLeverage(coin, leverage, false); // false = isolated
1097
- this.hip3IsolatedSet.add(coin);
1143
+ await this.exchange.sendAsset({
1144
+ destination: this.address as `0x${string}`,
1145
+ sourceDex: '', // main perp dex
1146
+ destinationDex: dexInfo.dexName,
1147
+ token: 'USDC:0x6d1e7cde53ba9467b783cb7c530ce054',
1148
+ amount: String(transferAmount),
1149
+ });
1150
+ this.log(`Transferred ${transferAmount} USDC to ${dexInfo.dexName} dex`);
1098
1151
  } catch (err) {
1099
- // Log but don't block — might already be set
1100
- this.log(`Failed to set isolated margin for ${coin}:`, err instanceof Error ? err.message : String(err));
1101
- this.hip3IsolatedSet.add(coin); // Don't retry every order
1152
+ // Log but don't block — dex may already have sufficient balance
1153
+ this.log(`Margin transfer to ${dexInfo.dexName} failed:`, err instanceof Error ? err.message : String(err));
1102
1154
  }
1103
1155
  }
1104
1156
 
1105
- /**
1106
- * Get maxLeverage for a HIP-3 asset from cached metadata.
1107
- */
1108
- private getHip3MaxLeverage(coin: string): number | null {
1109
- // Already loaded via getMetaAndAssetCtxs → loadHip3Assets
1110
- // Check if we cached it during loadHip3Assets
1111
- return this.hip3MaxLeverageMap.get(coin) ?? null;
1112
- }
1113
-
1114
1157
  async order(
1115
1158
  coin: string,
1116
1159
  isBuy: boolean,
@@ -1123,8 +1166,8 @@ export class HyperliquidClient {
1123
1166
  this.requireTrading();
1124
1167
  await this.getMetaAndAssetCtxs();
1125
1168
 
1126
- // HIP-3 perps require isolated margin mode
1127
- await this.ensureHip3Isolated(coin);
1169
+ // HIP-3 perps: set isolated margin + transfer USDC to dex
1170
+ await this.ensureHip3Ready(coin, size * price);
1128
1171
 
1129
1172
  const assetIndex = this.getAssetIndex(coin);
1130
1173
  const szDecimals = this.getSzDecimals(coin);
@@ -1244,8 +1287,8 @@ export class HyperliquidClient {
1244
1287
  this.requireTrading();
1245
1288
  await this.getMetaAndAssetCtxs();
1246
1289
 
1247
- // HIP-3 perps require isolated margin mode
1248
- await this.ensureHip3Isolated(coin);
1290
+ // HIP-3 perps: set isolated margin + transfer USDC to dex
1291
+ await this.ensureHip3Ready(coin, size * limitPrice);
1249
1292
 
1250
1293
  const assetIndex = this.getAssetIndex(coin);
1251
1294
  const szDecimals = this.getSzDecimals(coin);
@@ -30,7 +30,7 @@ async function main() {
30
30
  console.log('');
31
31
 
32
32
  try {
33
- const state = await client.getUserState();
33
+ const state = await client.getUserStateAll();
34
34
 
35
35
  const margin = state.crossMarginSummary;
36
36
  const accountValue = parseFloat(margin.accountValue);
@@ -14,7 +14,7 @@ async function main() {
14
14
 
15
15
  try {
16
16
  const [state, mids, fundingHistory] = await Promise.all([
17
- client.getUserState(),
17
+ client.getUserStateAll(),
18
18
  client.getAllMids(),
19
19
  client.getUserFunding(),
20
20
  ]);
@@ -34,7 +34,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
34
34
  async execute(_id, params) {
35
35
  const { getClient } = await import('../core/client.js');
36
36
  const client = getClient();
37
- const state = await client.getUserState();
37
+ const state = await client.getUserStateAll();
38
38
 
39
39
  const result: Record<string, unknown> = {
40
40
  address: client.address,
@@ -85,7 +85,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
85
85
  async execute(_id, params) {
86
86
  const { getClient } = await import('../core/client.js');
87
87
  const client = getClient();
88
- const state = await client.getUserState();
88
+ const state = await client.getUserStateAll();
89
89
 
90
90
  let positions = state.assetPositions
91
91
  .filter(ap => parseFloat(ap.position.szi) !== 0)
@@ -969,7 +969,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
969
969
  const coin = normalizeCoin(params.coin as string);
970
970
 
971
971
  // Get current position
972
- const state = await client.getUserState();
972
+ const state = await client.getUserStateAll();
973
973
  const position = state.assetPositions.find(
974
974
  ap => ap.position.coin === coin && parseFloat(ap.position.szi) !== 0,
975
975
  );
@@ -117,7 +117,7 @@ export class PositionWatcher implements PluginService {
117
117
  try {
118
118
  const { getClient } = await import('../core/client.js');
119
119
  const client = getClient();
120
- const state = await client.getUserState(this.accountAddress);
120
+ const state = await client.getUserStateAll(this.accountAddress);
121
121
 
122
122
  const snapshot = this.buildSnapshot(state);
123
123