openbroker 1.0.55 → 1.0.56
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 +10 -0
- package/SKILL.md +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/core/client.ts +120 -17
- package/scripts/info/account.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Open Broker will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.56] - 2026-03-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Unified Account Support**: Detects and handles Hyperliquid's account abstraction modes (standard, unified, portfolio margin, dex abstraction)
|
|
9
|
+
- `getAccountMode()` / `isUnifiedAccount()` — queries `userAbstraction` API to detect mode
|
|
10
|
+
- **Unified accounts**: equity comes from `spotClearinghouseState` (single USDC balance shared across all dexes); skips `sendAsset` transfers for HIP-3 trading
|
|
11
|
+
- **Standard accounts**: keeps existing behavior (per-dex margin aggregation + USDC transfers)
|
|
12
|
+
- `account` command now displays the account abstraction mode
|
|
13
|
+
- Prepares for deprecation of DEX abstraction mode per Hyperliquid docs
|
|
14
|
+
|
|
5
15
|
## [1.0.55] - 2026-03-09
|
|
6
16
|
|
|
7
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.56", "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
|
@@ -35,6 +35,8 @@ export class HyperliquidClient {
|
|
|
35
35
|
private hip3IsolatedSet: Set<string> = new Set();
|
|
36
36
|
/** Cached maxLeverage for HIP-3 assets */
|
|
37
37
|
private hip3MaxLeverageMap: Map<string, number> = new Map();
|
|
38
|
+
/** Cached account abstraction mode: 'standard' | 'unified' | 'portfolio' | 'dexAbstraction' */
|
|
39
|
+
private accountMode: string | null = null;
|
|
38
40
|
public verbose: boolean = false;
|
|
39
41
|
|
|
40
42
|
constructor(config?: OpenBrokerConfig) {
|
|
@@ -631,6 +633,57 @@ export class HyperliquidClient {
|
|
|
631
633
|
|
|
632
634
|
// ============ Account Info ============
|
|
633
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Get the account's abstraction mode.
|
|
638
|
+
* Returns: 'standard' | 'unified' | 'portfolio' | 'dexAbstraction'
|
|
639
|
+
* Unified accounts have a single USDC balance shared across all dexes.
|
|
640
|
+
* Standard accounts have separate balances per dex (need sendAsset transfers).
|
|
641
|
+
*/
|
|
642
|
+
async getAccountMode(user?: string): Promise<string> {
|
|
643
|
+
if (this.accountMode) return this.accountMode;
|
|
644
|
+
|
|
645
|
+
const baseUrl = isMainnet()
|
|
646
|
+
? 'https://api.hyperliquid.xyz'
|
|
647
|
+
: 'https://api.hyperliquid-testnet.xyz';
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
const response = await fetch(baseUrl + '/info', {
|
|
651
|
+
method: 'POST',
|
|
652
|
+
headers: { 'Content-Type': 'application/json' },
|
|
653
|
+
body: JSON.stringify({
|
|
654
|
+
type: 'userAbstraction',
|
|
655
|
+
user: user ?? this.address,
|
|
656
|
+
}),
|
|
657
|
+
});
|
|
658
|
+
const data = await response.json();
|
|
659
|
+
this.log('userAbstraction response:', data);
|
|
660
|
+
|
|
661
|
+
// API returns: "default" | "disabled" | "dexAbstraction" | "unifiedAccount" | "portfolioMargin"
|
|
662
|
+
if (data === 'unifiedAccount') {
|
|
663
|
+
this.accountMode = 'unified';
|
|
664
|
+
} else if (data === 'portfolioMargin') {
|
|
665
|
+
this.accountMode = 'portfolio';
|
|
666
|
+
} else if (data === 'dexAbstraction') {
|
|
667
|
+
this.accountMode = 'dexAbstraction';
|
|
668
|
+
} else {
|
|
669
|
+
// "default" or "disabled" both mean standard mode
|
|
670
|
+
this.accountMode = 'standard';
|
|
671
|
+
}
|
|
672
|
+
} catch (err) {
|
|
673
|
+
this.log('Failed to fetch account abstraction mode:', err instanceof Error ? err.message : String(err));
|
|
674
|
+
this.accountMode = 'standard'; // Safe fallback
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
this.log('Account mode:', this.accountMode);
|
|
678
|
+
return this.accountMode;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Whether the account uses unified balances (unified or portfolio margin) */
|
|
682
|
+
async isUnifiedAccount(user?: string): Promise<boolean> {
|
|
683
|
+
const mode = await this.getAccountMode(user);
|
|
684
|
+
return mode === 'unified' || mode === 'portfolio';
|
|
685
|
+
}
|
|
686
|
+
|
|
634
687
|
/**
|
|
635
688
|
* Check if an address has sub-accounts (is a master account)
|
|
636
689
|
* Sub-accounts cannot approve builder fees - only master accounts can
|
|
@@ -1075,14 +1128,17 @@ export class HyperliquidClient {
|
|
|
1075
1128
|
|
|
1076
1129
|
/**
|
|
1077
1130
|
* Get user state across all dexes (main + HIP-3).
|
|
1078
|
-
*
|
|
1131
|
+
* For unified accounts: equity comes from spotClearinghouseState (single USDC balance).
|
|
1132
|
+
* For standard accounts: aggregates margin summaries from each dex.
|
|
1079
1133
|
*/
|
|
1080
1134
|
async getUserStateAll(user?: string): Promise<ClearinghouseState> {
|
|
1081
1135
|
await this.getMetaAndAssetCtxs(); // Ensure HIP-3 dex list is loaded
|
|
1082
1136
|
|
|
1137
|
+
const unified = await this.isUnifiedAccount(user);
|
|
1083
1138
|
const mainState = await this.getUserState(user);
|
|
1084
1139
|
const dexs = await this.getPerpDexs();
|
|
1085
1140
|
|
|
1141
|
+
// Collect positions from all HIP-3 dexes
|
|
1086
1142
|
for (let i = 1; i < dexs.length; i++) {
|
|
1087
1143
|
const dex = dexs[i];
|
|
1088
1144
|
if (!dex) continue;
|
|
@@ -1093,24 +1149,63 @@ export class HyperliquidClient {
|
|
|
1093
1149
|
mainState.assetPositions.push(...dexState.assetPositions);
|
|
1094
1150
|
}
|
|
1095
1151
|
|
|
1096
|
-
//
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1152
|
+
// For standard accounts, aggregate margin from each dex
|
|
1153
|
+
if (!unified) {
|
|
1154
|
+
const dexMargin = dexState.marginSummary;
|
|
1155
|
+
if (dexMargin) {
|
|
1156
|
+
const addToSummary = (summary: { accountValue: string; totalNtlPos: string; totalRawUsd: string; totalMarginUsed: string; withdrawable: string }) => {
|
|
1157
|
+
summary.accountValue = String(parseFloat(summary.accountValue) + parseFloat(dexMargin.accountValue));
|
|
1158
|
+
summary.totalNtlPos = String(parseFloat(summary.totalNtlPos) + parseFloat(dexMargin.totalNtlPos));
|
|
1159
|
+
summary.totalRawUsd = String(parseFloat(summary.totalRawUsd) + parseFloat(dexMargin.totalRawUsd));
|
|
1160
|
+
summary.totalMarginUsed = String(parseFloat(summary.totalMarginUsed) + parseFloat(dexMargin.totalMarginUsed));
|
|
1161
|
+
summary.withdrawable = String(parseFloat(summary.withdrawable) + parseFloat(dexMargin.withdrawable));
|
|
1162
|
+
};
|
|
1163
|
+
addToSummary(mainState.marginSummary);
|
|
1164
|
+
addToSummary(mainState.crossMarginSummary);
|
|
1165
|
+
}
|
|
1108
1166
|
}
|
|
1109
1167
|
} catch (err) {
|
|
1110
1168
|
this.log(`Failed to fetch state for dex ${dex.name}:`, err instanceof Error ? err.message : String(err));
|
|
1111
1169
|
}
|
|
1112
1170
|
}
|
|
1113
1171
|
|
|
1172
|
+
// For unified accounts: equity is the USDC balance from spot clearinghouse
|
|
1173
|
+
if (unified) {
|
|
1174
|
+
try {
|
|
1175
|
+
const spotState = await this.getSpotBalances(user);
|
|
1176
|
+
const usdcBalance = spotState.balances.find(b => b.coin === 'USDC');
|
|
1177
|
+
if (usdcBalance) {
|
|
1178
|
+
const totalUsdc = usdcBalance.total;
|
|
1179
|
+
const holdUsdc = usdcBalance.hold;
|
|
1180
|
+
const withdrawable = String(parseFloat(totalUsdc) - parseFloat(holdUsdc));
|
|
1181
|
+
|
|
1182
|
+
// Compute total margin used and notional from all positions
|
|
1183
|
+
let totalMarginUsed = 0;
|
|
1184
|
+
let totalNtlPos = 0;
|
|
1185
|
+
for (const ap of mainState.assetPositions) {
|
|
1186
|
+
const pos = ap.position;
|
|
1187
|
+
if (parseFloat(pos.szi) === 0) continue;
|
|
1188
|
+
totalMarginUsed += parseFloat(pos.marginUsed);
|
|
1189
|
+
totalNtlPos += Math.abs(parseFloat(pos.positionValue));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const summary = {
|
|
1193
|
+
accountValue: totalUsdc,
|
|
1194
|
+
totalNtlPos: String(totalNtlPos),
|
|
1195
|
+
totalRawUsd: totalUsdc,
|
|
1196
|
+
totalMarginUsed: String(totalMarginUsed),
|
|
1197
|
+
withdrawable,
|
|
1198
|
+
};
|
|
1199
|
+
mainState.marginSummary = summary;
|
|
1200
|
+
mainState.crossMarginSummary = { ...summary };
|
|
1201
|
+
|
|
1202
|
+
this.log(`Unified account: USDC balance $${parseFloat(totalUsdc).toFixed(2)}, margin used $${totalMarginUsed.toFixed(2)}`);
|
|
1203
|
+
}
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
this.log('Failed to fetch spot balances for unified account:', err instanceof Error ? err.message : String(err));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1114
1209
|
return mainState;
|
|
1115
1210
|
}
|
|
1116
1211
|
|
|
@@ -1123,9 +1218,10 @@ export class HyperliquidClient {
|
|
|
1123
1218
|
// ============ Trading ============
|
|
1124
1219
|
|
|
1125
1220
|
/**
|
|
1126
|
-
* HIP-3 perps
|
|
1221
|
+
* HIP-3 perps: prepare for trading.
|
|
1127
1222
|
* 1. Set isolated margin mode (required for HIP-3)
|
|
1128
|
-
* 2.
|
|
1223
|
+
* 2. For standard accounts only: transfer USDC from main perp to HIP-3 dex
|
|
1224
|
+
* (unified accounts share USDC across all dexes automatically)
|
|
1129
1225
|
*/
|
|
1130
1226
|
private async ensureHip3Ready(coin: string, notional: number, leverage?: number): Promise<void> {
|
|
1131
1227
|
if (!this.isHip3(coin)) return;
|
|
@@ -1148,12 +1244,19 @@ export class HyperliquidClient {
|
|
|
1148
1244
|
}
|
|
1149
1245
|
}
|
|
1150
1246
|
|
|
1151
|
-
//
|
|
1247
|
+
// Unified accounts share USDC across all dexes — no transfer needed
|
|
1248
|
+
const unified = await this.isUnifiedAccount();
|
|
1249
|
+
if (unified) {
|
|
1250
|
+
this.log(`Unified account — skipping USDC transfer for ${coin} (shared balance)`);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Standard accounts: transfer USDC to the HIP-3 dex to cover margin
|
|
1152
1255
|
const requiredMargin = notional / effectiveLev;
|
|
1153
1256
|
// Add 20% buffer for fees and slippage
|
|
1154
1257
|
const transferAmount = Math.ceil(requiredMargin * 1.2 * 100) / 100;
|
|
1155
1258
|
|
|
1156
|
-
this.log(`HIP-3 margin transfer: ${transferAmount} USDC from main → ${dexInfo.dexName} (notional: ${notional}, leverage: ${
|
|
1259
|
+
this.log(`HIP-3 margin transfer: ${transferAmount} USDC from main → ${dexInfo.dexName} (notional: ${notional}, leverage: ${effectiveLev}x)`);
|
|
1157
1260
|
try {
|
|
1158
1261
|
await this.exchange.sendAsset({
|
|
1159
1262
|
destination: this.address as `0x${string}`,
|
package/scripts/info/account.ts
CHANGED
|
@@ -17,6 +17,15 @@ async function main() {
|
|
|
17
17
|
console.log(`Signing Wallet: ${client.walletAddress}`);
|
|
18
18
|
console.log(`Wallet Type: ${client.isApiWallet ? 'API Wallet' : 'Main Wallet'}`);
|
|
19
19
|
|
|
20
|
+
const accountMode = await client.getAccountMode();
|
|
21
|
+
const modeLabel: Record<string, string> = {
|
|
22
|
+
standard: 'Standard (separate balances per dex)',
|
|
23
|
+
unified: 'Unified Account (shared USDC across all dexes)',
|
|
24
|
+
portfolio: 'Portfolio Margin',
|
|
25
|
+
dexAbstraction: 'DEX Abstraction (deprecated)',
|
|
26
|
+
};
|
|
27
|
+
console.log(`Account Mode: ${modeLabel[accountMode] ?? accountMode}`);
|
|
28
|
+
|
|
20
29
|
// Check builder fee approval
|
|
21
30
|
const builderApproval = await client.getMaxBuilderFee();
|
|
22
31
|
console.log(`Builder Address: ${client.builderAddress}`);
|