@pear-protocol/exchanges-sdk 0.0.6 → 0.0.7

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 CHANGED
@@ -8,6 +8,7 @@ Unified SDK for connecting to cryptocurrency derivative exchanges. Provides real
8
8
  |----------|---------------|-------------|
9
9
  | Binance | `binanceusdm` | USDM Futures |
10
10
  | Bybit | `bybit` | Linear Perpetuals |
11
+ | Hyperliquid | `hyperliquid` | Perpetuals |
11
12
 
12
13
  ## Installation
13
14
 
@@ -67,6 +68,7 @@ Credentials are fetched automatically from the backend when you call `connect`.
67
68
  ```typescript
68
69
  await exchanges.account.connect(tradeAccountId, 'binanceusdm');
69
70
  await exchanges.account.connect(tradeAccountId, 'bybit');
71
+ await exchanges.account.connect(tradeAccountId, 'hyperliquid');
70
72
  ```
71
73
 
72
74
  Check connection status:
@@ -91,8 +93,11 @@ const tracker = exchanges.account.trackBalance((balance) => {
91
93
  // Per-asset breakdown
92
94
  for (const asset of balance.assets) {
93
95
  asset.asset; // "USDT", "BTC", etc.
96
+ asset.free; // available amount
97
+ asset.used; // amount in use
98
+ asset.total; // total amount
99
+ asset.walletBalance; // wallet balance
94
100
  asset.usdValue; // USD equivalent
95
- asset.isCollateral; // whether the asset is used as collateral
96
101
  }
97
102
  });
98
103
 
@@ -134,9 +139,6 @@ const tracker = exchanges.account.trackAsset('ETH', (info) => {
134
139
  info.coin; // "ETH"
135
140
  info.leverage; // "20"
136
141
  info.marginType; // "cross" | "isolated"
137
- info.availableToTrade; // [min, max] or null
138
- info.maxTradeSzs; // [min, max] or null
139
- info.collateralToken; // "USDT" or null
140
142
  });
141
143
 
142
144
  const current = tracker.get();
@@ -1,10 +1,11 @@
1
+ import PearSDK from '@pear-protocol/core-sdk';
1
2
  import { Connector } from '@pear-protocol/types';
2
3
  import { Credentials } from '../credentials.js';
3
4
  import { BalanceCallback, Tracker, AccountBalance, PositionsCallback, AccountPosition, TrackedAssetCallback, TrackedAssetInfo, ExchangeState } from './types.js';
4
- import '@pear-protocol/core-sdk';
5
5
 
6
6
  declare class AccountManager {
7
7
  private credentials;
8
+ private sdk;
8
9
  private demo;
9
10
  private activeAccount;
10
11
  private pendingExchange;
@@ -12,7 +13,7 @@ declare class AccountManager {
12
13
  private balanceListeners;
13
14
  private positionListeners;
14
15
  private assetListeners;
15
- constructor(credentials: Credentials, demo?: boolean);
16
+ constructor(credentials: Credentials, sdk: PearSDK, demo?: boolean);
16
17
  connect(tradeAccountId: string, exchange: Connector): Promise<void>;
17
18
  trackBalance(cb: BalanceCallback): Tracker<AccountBalance | null>;
18
19
  trackPositions(cb: PositionsCallback): Tracker<AccountPosition[]>;
@@ -28,6 +29,7 @@ declare class AccountManager {
28
29
  private disconnectWs;
29
30
  private maybeDisconnectWs;
30
31
  private ensureWsStarted;
32
+ private resolveCredentials;
31
33
  private startWs;
32
34
  }
33
35
 
@@ -2,6 +2,7 @@ import { ExchangeAccount } from './exchange-account';
2
2
 
3
3
  class AccountManager {
4
4
  credentials;
5
+ sdk;
5
6
  demo;
6
7
  activeAccount = null;
7
8
  pendingExchange = null;
@@ -9,12 +10,13 @@ class AccountManager {
9
10
  balanceListeners = /* @__PURE__ */ new Map();
10
11
  positionListeners = /* @__PURE__ */ new Map();
11
12
  assetListeners = /* @__PURE__ */ new Map();
12
- constructor(credentials, demo = false) {
13
+ constructor(credentials, sdk, demo = false) {
13
14
  this.credentials = credentials;
15
+ this.sdk = sdk;
14
16
  this.demo = demo;
15
17
  }
16
18
  async connect(tradeAccountId, exchange) {
17
- const creds = await this.credentials.getOrFetch(tradeAccountId);
19
+ const creds = await this.resolveCredentials(tradeAccountId, exchange);
18
20
  this.disconnectWs();
19
21
  this.pendingTradeAccountId = tradeAccountId;
20
22
  this.pendingExchange = exchange;
@@ -119,7 +121,22 @@ class AccountManager {
119
121
  if (this.activeAccount || !this.pendingExchange || !this.pendingTradeAccountId) return;
120
122
  const exchange = this.pendingExchange;
121
123
  const tradeAccountId = this.pendingTradeAccountId;
122
- this.credentials.getOrFetch(tradeAccountId).then((creds) => this.startWs(exchange, creds)).catch((err) => console.warn("[AccountManager] Failed to start WS:", err));
124
+ this.resolveCredentials(tradeAccountId, exchange).then((creds) => this.startWs(exchange, creds)).catch((err) => console.warn("[AccountManager] Failed to start WS:", err));
125
+ }
126
+ async resolveCredentials(tradeAccountId, exchange) {
127
+ const creds = await this.credentials.getOrFetch(tradeAccountId);
128
+ if (exchange === "hyperliquid") {
129
+ const { accounts } = await this.sdk.info.accounts.list();
130
+ const account = accounts.find((a) => a.id === tradeAccountId);
131
+ if (!account) {
132
+ throw new Error(`Trade account ${tradeAccountId} not found`);
133
+ }
134
+ if (!account.exchangeIdentifier) {
135
+ throw new Error(`Trade account ${tradeAccountId} has no Hyperliquid address`);
136
+ }
137
+ return { ...creds, address: account.exchangeIdentifier };
138
+ }
139
+ return creds;
123
140
  }
124
141
  async startWs(exchange, creds) {
125
142
  const account = new ExchangeAccount({ exchange, demo: this.demo });
@@ -2,7 +2,7 @@ import { AccountBalance, AccountPosition } from '../../types.js';
2
2
  import { BinanceRawAccountStatus, BinanceRawPositionRisk } from './types.js';
3
3
  import '@pear-protocol/types';
4
4
 
5
- declare function mapBalance(raw: BinanceRawAccountStatus, collateralRatios: Map<string, string>): AccountBalance;
5
+ declare function mapBalance(raw: BinanceRawAccountStatus): AccountBalance;
6
6
  declare function mapPositions(raw: BinanceRawPositionRisk[]): Map<string, AccountPosition>;
7
7
  declare function mergePositions(incoming: Map<string, AccountPosition>, existing: Map<string, AccountPosition>): Map<string, AccountPosition>;
8
8
 
@@ -1,7 +1,7 @@
1
1
  import '../../types';
2
2
  import { positionKey } from '../../utils';
3
3
 
4
- function mapBalance(raw, collateralRatios) {
4
+ function mapBalance(raw) {
5
5
  const free = {};
6
6
  const used = {};
7
7
  const total = {};
@@ -11,8 +11,6 @@ function mapBalance(raw, collateralRatios) {
11
11
  const ab = a.availableBalance ?? "0";
12
12
  const mb = a.marginBalance ?? "0";
13
13
  const im = a.initialMargin ?? "0";
14
- const mm = a.maintMargin ?? "0";
15
- const up = a.unrealizedProfit ?? "0";
16
14
  if (parseFloat(wb) === 0 && parseFloat(ab) === 0 && parseFloat(mb) === 0) continue;
17
15
  free[a.asset] = ab;
18
16
  used[a.asset] = im;
@@ -23,13 +21,7 @@ function mapBalance(raw, collateralRatios) {
23
21
  used: im,
24
22
  total: mb,
25
23
  walletBalance: wb,
26
- unrealizedPnl: up,
27
- usdValue: mb,
28
- initialMargin: im,
29
- maintenanceMargin: mm,
30
- isCollateral: a.marginAvailable ?? false,
31
- collateralRatio: collateralRatios.get(a.asset) ?? "1.0000",
32
- borrowedAmount: "0"
24
+ usdValue: mb
33
25
  });
34
26
  }
35
27
  const totalMarginBalance = raw.totalMarginBalance ?? "0";
@@ -38,9 +30,7 @@ function mapBalance(raw, collateralRatios) {
38
30
  const maintMargin = parseFloat(totalMaintMargin);
39
31
  const marginRatio = marginBalance > 0 ? (maintMargin / marginBalance).toFixed(4) : "0.0000";
40
32
  return {
41
- exchange: "binance",
42
33
  accountType: raw.multiAssetsMargin ? "multi-asset" : "single-asset",
43
- marginMode: "cross",
44
34
  free,
45
35
  used,
46
36
  total,
@@ -13,8 +13,6 @@ declare class BinanceOrchestrator extends BaseOrchestrator {
13
13
  private creds;
14
14
  private trackedAssetInfos;
15
15
  private currentPositions;
16
- private collateralRatios;
17
- private multiAssetsMargin;
18
16
  private readonly listenKeyRefreshMs;
19
17
  private readonly accountPollMs;
20
18
  constructor(config: BinanceOrchestratorConfig);
@@ -2,7 +2,7 @@ import { positionKey } from '../../utils';
2
2
  import { BaseOrchestrator } from '../base';
3
3
  import { DEFAULT_LISTEN_KEY_REFRESH_MS, DEFAULT_ACCOUNT_POLL_MS } from './const';
4
4
  import { mapBalance, mapPositions, mergePositions } from './mapper';
5
- import { fetchAccountStatus, fetchAllPositionRisk, fetchAssetIndex, createListenKey, keepAliveListenKey, fetchPositionRisk, setLeverage } from './rest';
5
+ import { fetchAccountStatus, fetchAllPositionRisk, createListenKey, keepAliveListenKey, fetchPositionRisk, setLeverage } from './rest';
6
6
  import { BinanceWs } from './ws';
7
7
 
8
8
  class BinanceOrchestrator extends BaseOrchestrator {
@@ -12,8 +12,6 @@ class BinanceOrchestrator extends BaseOrchestrator {
12
12
  creds = null;
13
13
  trackedAssetInfos = /* @__PURE__ */ new Map();
14
14
  currentPositions = /* @__PURE__ */ new Map();
15
- collateralRatios = /* @__PURE__ */ new Map();
16
- multiAssetsMargin = false;
17
15
  listenKeyRefreshMs;
18
16
  accountPollMs;
19
17
  constructor(config) {
@@ -33,18 +31,6 @@ class BinanceOrchestrator extends BaseOrchestrator {
33
31
  fetchAllPositionRisk(this.creds, this.demo)
34
32
  ]);
35
33
  if (gen !== this.startGeneration) return;
36
- this.multiAssetsMargin = snapshot.multiAssetsMargin ?? false;
37
- if (this.multiAssetsMargin) {
38
- const indexData = await fetchAssetIndex(this.demo);
39
- if (gen !== this.startGeneration) return;
40
- this.collateralRatios.clear();
41
- for (const item of indexData) {
42
- const asset = item.symbol.replace(/USD$/, "");
43
- if (item.bidRate && parseFloat(item.bidRate) > 0) {
44
- this.collateralRatios.set(asset, item.bidRate);
45
- }
46
- }
47
- }
48
34
  this.handleAccountStatus(snapshot);
49
35
  this.handlePositionRisk(positionRisk, false);
50
36
  const listenKey = await createListenKey(this.creds.api_key, this.demo);
@@ -87,8 +73,6 @@ class BinanceOrchestrator extends BaseOrchestrator {
87
73
  this.creds = null;
88
74
  this.trackedAssetInfos.clear();
89
75
  this.currentPositions.clear();
90
- this.collateralRatios.clear();
91
- this.multiAssetsMargin = false;
92
76
  }
93
77
  get isConnected() {
94
78
  return this.ws?.isConnected ?? false;
@@ -102,10 +86,7 @@ class BinanceOrchestrator extends BaseOrchestrator {
102
86
  const info = {
103
87
  coin,
104
88
  leverage,
105
- marginType,
106
- availableToTrade: null,
107
- maxTradeSzs: null,
108
- collateralToken: null
89
+ marginType
109
90
  };
110
91
  this.trackedAssetInfos.set(coin, info);
111
92
  const trackedAssets = /* @__PURE__ */ new Map();
@@ -166,7 +147,7 @@ class BinanceOrchestrator extends BaseOrchestrator {
166
147
  }
167
148
  }
168
149
  handleAccountStatus(result) {
169
- const balance = mapBalance(result, this.collateralRatios);
150
+ const balance = mapBalance(result);
170
151
  this.onStateUpdate({ balance });
171
152
  }
172
153
  handlePositionRisk(risk, merge) {
@@ -2,7 +2,7 @@ import { AccountBalance, AccountPosition } from '../../types.js';
2
2
  import { BybitRawWallet, BybitRawCoin, BybitRawPosition } from './types.js';
3
3
  import '@pear-protocol/types';
4
4
 
5
- declare function buildBalance(raw: BybitRawWallet, coins: Map<string, BybitRawCoin>, marginMode: string, collateralRatios: Map<string, string>): AccountBalance;
5
+ declare function buildBalance(raw: BybitRawWallet, coins: Map<string, BybitRawCoin>, marginMode: string): AccountBalance;
6
6
  declare function mergeRawCoins(existing: Map<string, BybitRawCoin>, incoming: BybitRawCoin[]): Map<string, BybitRawCoin>;
7
7
  declare function mapSinglePosition(p: BybitRawPosition, size: string): AccountPosition;
8
8
  declare function mapPositions(raw: BybitRawPosition[]): Map<string, AccountPosition>;
@@ -1,7 +1,7 @@
1
1
  import '../../types';
2
2
  import { positionKey } from '../../utils';
3
3
 
4
- function buildBalance(raw, coins, marginMode, collateralRatios) {
4
+ function buildBalance(raw, coins, marginMode) {
5
5
  const free = {};
6
6
  const used = {};
7
7
  const total = {};
@@ -11,7 +11,6 @@ function buildBalance(raw, coins, marginMode, collateralRatios) {
11
11
  const equity = c.equity ?? "0";
12
12
  const positionInitialMargin = c.totalPositionIM === "" ? "0" : c.totalPositionIM ?? "0";
13
13
  const orderInitialMargin = c.totalOrderIM === "" ? "0" : c.totalOrderIM ?? "0";
14
- const positionMaintenanceMargin = c.totalPositionMM === "" ? "0" : c.totalPositionMM ?? "0";
15
14
  const locked = c.locked ?? "0";
16
15
  const equityNum = parseFloat(equity);
17
16
  const positionInitialMarginNum = parseFloat(positionInitialMargin);
@@ -36,19 +35,11 @@ function buildBalance(raw, coins, marginMode, collateralRatios) {
36
35
  used: coinUsed,
37
36
  total: equity,
38
37
  walletBalance: c.walletBalance ?? "0",
39
- unrealizedPnl: c.unrealisedPnl ?? "0",
40
- usdValue: c.usdValue ?? "0",
41
- initialMargin: isPortfolio ? "0" : (positionInitialMarginNum + orderInitialMarginNum).toString(),
42
- maintenanceMargin: positionMaintenanceMargin,
43
- isCollateral: (c.marginCollateral ?? false) && (c.collateralSwitch ?? false),
44
- collateralRatio: collateralRatios.get(c.coin) ?? "1.0000",
45
- borrowedAmount: c.borrowAmount ?? "0"
38
+ usdValue: c.usdValue ?? "0"
46
39
  });
47
40
  }
48
41
  return {
49
- exchange: "bybit",
50
42
  accountType: "UNIFIED",
51
- marginMode,
52
43
  free,
53
44
  used,
54
45
  total,
@@ -8,7 +8,6 @@ declare class BybitOrchestrator extends BaseOrchestrator {
8
8
  private lastWallet;
9
9
  private trackedAssetInfos;
10
10
  private marginMode;
11
- private collateralRatios;
12
11
  start(credentials: SupportedCredentials): Promise<void>;
13
12
  stop(): void;
14
13
  get isConnected(): boolean;
@@ -1,6 +1,6 @@
1
1
  import { BaseOrchestrator } from '../base';
2
2
  import { mergeRawCoins, buildBalance, mapPositions, mapPositionUpdates } from './mapper';
3
- import { fetchWalletBalance, fetchPositions, fetchAccountInfo, fetchCollateralRatios, fetchPositionLeverage, setLeverage } from './rest';
3
+ import { fetchWalletBalance, fetchPositions, fetchAccountInfo, fetchPositionLeverage, setLeverage } from './rest';
4
4
  import { BybitWs } from './ws';
5
5
 
6
6
  class BybitOrchestrator extends BaseOrchestrator {
@@ -9,7 +9,6 @@ class BybitOrchestrator extends BaseOrchestrator {
9
9
  lastWallet = null;
10
10
  trackedAssetInfos = /* @__PURE__ */ new Map();
11
11
  marginMode = "cross";
12
- collateralRatios = /* @__PURE__ */ new Map();
13
12
  async start(credentials) {
14
13
  const gen = ++this.startGeneration;
15
14
  this.manualClose = false;
@@ -17,11 +16,10 @@ class BybitOrchestrator extends BaseOrchestrator {
17
16
  const creds = credentials;
18
17
  this.stopAll();
19
18
  try {
20
- const [wallet, positions, accountInfo, ratios] = await Promise.all([
19
+ const [wallet, positions, accountInfo] = await Promise.all([
21
20
  fetchWalletBalance(creds, this.demo),
22
21
  fetchPositions(creds, this.demo),
23
- fetchAccountInfo(creds, this.demo),
24
- fetchCollateralRatios(creds, this.demo)
22
+ fetchAccountInfo(creds, this.demo)
25
23
  ]);
26
24
  if (gen !== this.startGeneration) return;
27
25
  const MARGIN_MODE_MAP = {
@@ -31,12 +29,11 @@ class BybitOrchestrator extends BaseOrchestrator {
31
29
  };
32
30
  const rawMode = accountInfo?.marginMode ?? "REGULAR_MARGIN";
33
31
  this.marginMode = MARGIN_MODE_MAP[rawMode] ?? "cross";
34
- this.collateralRatios = ratios;
35
32
  if (wallet) {
36
33
  this.lastWallet = wallet;
37
34
  this.rawCoins = mergeRawCoins(/* @__PURE__ */ new Map(), wallet.coin ?? []);
38
35
  this.onStateUpdate({
39
- balance: buildBalance(wallet, this.rawCoins, this.marginMode, this.collateralRatios)
36
+ balance: buildBalance(wallet, this.rawCoins, this.marginMode)
40
37
  });
41
38
  }
42
39
  this.onStateUpdate({ positions: mapPositions(positions) });
@@ -67,7 +64,6 @@ class BybitOrchestrator extends BaseOrchestrator {
67
64
  this.rawCoins.clear();
68
65
  this.lastWallet = null;
69
66
  this.trackedAssetInfos.clear();
70
- this.collateralRatios.clear();
71
67
  this.marginMode = "cross";
72
68
  this.credentials = null;
73
69
  }
@@ -83,10 +79,7 @@ class BybitOrchestrator extends BaseOrchestrator {
83
79
  const info = {
84
80
  coin,
85
81
  leverage,
86
- marginType,
87
- availableToTrade: null,
88
- maxTradeSzs: null,
89
- collateralToken: null
82
+ marginType
90
83
  };
91
84
  this.trackedAssetInfos.set(coin, info);
92
85
  const trackedAssets = /* @__PURE__ */ new Map();
@@ -151,7 +144,7 @@ class BybitOrchestrator extends BaseOrchestrator {
151
144
  } : raw;
152
145
  this.lastWallet = merged;
153
146
  this.onStateUpdate({
154
- balance: buildBalance(merged, this.rawCoins, this.marginMode, this.collateralRatios)
147
+ balance: buildBalance(merged, this.rawCoins, this.marginMode)
155
148
  });
156
149
  }
157
150
  }
@@ -0,0 +1,3 @@
1
+ declare const LOCKED_TRACKED_COINS: readonly ["BTC"];
2
+
3
+ export { LOCKED_TRACKED_COINS };
@@ -0,0 +1,3 @@
1
+ const LOCKED_TRACKED_COINS = ["BTC"];
2
+
3
+ export { LOCKED_TRACKED_COINS };
@@ -0,0 +1,9 @@
1
+ import { AccountBalance, AccountPosition } from '../../types.js';
2
+ import { AggregatedClearinghouseSummary, HyperliquidCoinBalance, AssetPosition } from './types.js';
3
+ import '@pear-protocol/types';
4
+ import '@nktkas/hyperliquid/api/subscription';
5
+
6
+ declare function mapBalance(summary: AggregatedClearinghouseSummary, spotCoinBalances: Map<string, HyperliquidCoinBalance>, accountType: string, availableToTradeByCollateral: Map<string, number>): AccountBalance;
7
+ declare function mapPositions(raw: AssetPosition[]): Map<string, AccountPosition>;
8
+
9
+ export { mapBalance, mapPositions };
@@ -0,0 +1,68 @@
1
+ import { positionKey } from '../../utils';
2
+ import { COLLATERAL_TOKEN_MAP } from './types';
3
+
4
+ const COLLATERAL_COINS = new Set(Object.values(COLLATERAL_TOKEN_MAP));
5
+ function mapBalance(summary, spotCoinBalances, accountType, availableToTradeByCollateral) {
6
+ const free = {};
7
+ const used = {};
8
+ const total = {};
9
+ const assets = [];
10
+ for (const [, coin] of spotCoinBalances) {
11
+ if (!COLLATERAL_COINS.has(coin.coin)) continue;
12
+ const availableOverride = availableToTradeByCollateral.get(coin.coin);
13
+ const coinFree = availableOverride !== void 0 ? availableOverride.toString() : (coin.total - coin.hold).toString();
14
+ const coinUsed = coin.hold.toString();
15
+ const coinTotal = coin.total.toString();
16
+ free[coin.coin] = coinFree;
17
+ used[coin.coin] = coinUsed;
18
+ total[coin.coin] = coinTotal;
19
+ assets.push({
20
+ asset: coin.coin,
21
+ free: coinFree,
22
+ used: coinUsed,
23
+ total: coinTotal,
24
+ walletBalance: coinTotal,
25
+ usdValue: coinTotal
26
+ });
27
+ }
28
+ const walletBalance = summary.accountValue - summary.totalUnrealizedPnl;
29
+ const marginRatio = summary.accountValue > 0 ? (summary.crossMaintenanceMarginUsed / summary.accountValue).toFixed(4) : "0.0000";
30
+ return {
31
+ accountType,
32
+ free,
33
+ used,
34
+ total,
35
+ availableToTrade: summary.withdrawable.toString(),
36
+ totalEquity: summary.accountValue.toString(),
37
+ walletBalance: walletBalance.toString(),
38
+ unrealizedPnl: summary.totalUnrealizedPnl.toString(),
39
+ initialMarginUsed: summary.totalMarginUsed.toString(),
40
+ maintenanceMargin: summary.crossMaintenanceMarginUsed.toString(),
41
+ marginRatio,
42
+ assets,
43
+ timestamp: Date.now()
44
+ };
45
+ }
46
+ function mapPositions(raw) {
47
+ const positions = /* @__PURE__ */ new Map();
48
+ for (const ap of raw) {
49
+ const pos = ap.position;
50
+ const szi = Number.parseFloat(pos.szi || "0");
51
+ if (szi === 0) continue;
52
+ const coin = pos.coin || "";
53
+ const side = szi > 0 ? "long" : "short";
54
+ positions.set(positionKey(coin, side), {
55
+ symbol: coin,
56
+ side,
57
+ size: szi.toString(),
58
+ entryPrice: pos.entryPx || "0",
59
+ unrealizedPnl: pos.unrealizedPnl || "0",
60
+ leverage: pos.leverage.value.toString(),
61
+ marginType: pos.leverage.type,
62
+ liquidationPrice: pos.liquidationPx || null
63
+ });
64
+ }
65
+ return positions;
66
+ }
67
+
68
+ export { mapBalance, mapPositions };
@@ -0,0 +1,38 @@
1
+ import { InstrumentId } from '@pear-protocol/types';
2
+ import { SupportedCredentials } from '../../types.js';
3
+ import { BaseOrchestrator } from '../base.js';
4
+
5
+ declare class HyperliquidOrchestrator extends BaseOrchestrator {
6
+ private static httpTransport;
7
+ private static wsTransport;
8
+ private infoClient;
9
+ private subsClient;
10
+ private subscriptions;
11
+ private address;
12
+ private activeAssetSubscriptions;
13
+ private trackedAssetData;
14
+ private spotCoinBalances;
15
+ private accountType;
16
+ private symbolToCollateralToken;
17
+ private availableToTradeByCollateral;
18
+ private lastSummary;
19
+ start(credentials: SupportedCredentials): Promise<void>;
20
+ stop(): void;
21
+ get isConnected(): boolean;
22
+ onAssetTracked(coin: string): void;
23
+ onAssetUntracked(coin: string): void;
24
+ setLeverage(id: InstrumentId, leverage: string): Promise<void>;
25
+ protected canUntrackAsset(coin: string): boolean;
26
+ private fetchPerpMetas;
27
+ private subscribeActiveAssetData;
28
+ private stopAll;
29
+ private handleClearinghouseState;
30
+ private handleActiveAssetData;
31
+ private buildTrackedAssetInfo;
32
+ private buildTrackedAssetsMap;
33
+ private buildBalance;
34
+ private handleWebData3;
35
+ private handleSpotState;
36
+ }
37
+
38
+ export { HyperliquidOrchestrator };
@@ -0,0 +1,239 @@
1
+ import { HttpTransport, WebSocketTransport, InfoClient, SubscriptionClient } from '@nktkas/hyperliquid';
2
+ import { BaseOrchestrator } from '../base';
3
+ import { LOCKED_TRACKED_COINS } from './const';
4
+ import { mapPositions, mapBalance } from './mapper';
5
+ import { setLeverage } from './rest';
6
+ import { COLLATERAL_TOKEN_MAP } from './types';
7
+
8
+ class HyperliquidOrchestrator extends BaseOrchestrator {
9
+ static httpTransport = new HttpTransport();
10
+ static wsTransport = new WebSocketTransport({
11
+ reconnect: {
12
+ maxRetries: Number.MAX_SAFE_INTEGER
13
+ }
14
+ });
15
+ infoClient = null;
16
+ subsClient = null;
17
+ subscriptions = [];
18
+ address = null;
19
+ activeAssetSubscriptions = /* @__PURE__ */ new Map();
20
+ trackedAssetData = /* @__PURE__ */ new Map();
21
+ spotCoinBalances = /* @__PURE__ */ new Map();
22
+ accountType = "disabled";
23
+ symbolToCollateralToken = /* @__PURE__ */ new Map();
24
+ availableToTradeByCollateral = /* @__PURE__ */ new Map();
25
+ lastSummary = {
26
+ accountValue: 0,
27
+ totalMarginUsed: 0,
28
+ totalRawUsd: 0,
29
+ crossMaintenanceMarginUsed: 0,
30
+ withdrawable: 0,
31
+ totalUnrealizedPnl: 0
32
+ };
33
+ async start(credentials) {
34
+ const gen = ++this.startGeneration;
35
+ this.manualClose = false;
36
+ this.credentials = credentials;
37
+ const creds = credentials;
38
+ this.address = creds.address;
39
+ await this.stopAll();
40
+ this.trackedAssetData.clear();
41
+ this.spotCoinBalances.clear();
42
+ this.symbolToCollateralToken.clear();
43
+ this.availableToTradeByCollateral.clear();
44
+ try {
45
+ this.infoClient = new InfoClient({ transport: HyperliquidOrchestrator.httpTransport });
46
+ await this.fetchPerpMetas();
47
+ if (gen !== this.startGeneration) return;
48
+ this.subsClient = new SubscriptionClient({ transport: HyperliquidOrchestrator.wsTransport });
49
+ const subs = [];
50
+ subs.push(
51
+ this.subsClient.allDexsClearinghouseState(
52
+ { user: this.address },
53
+ (data) => this.handleClearinghouseState(data)
54
+ )
55
+ );
56
+ subs.push(this.subsClient.spotState({ user: this.address }, (data) => this.handleSpotState(data)));
57
+ subs.push(this.subsClient.webData3({ user: this.address }, (data) => this.handleWebData3(data)));
58
+ this.subscriptions = await Promise.all(subs);
59
+ if (gen !== this.startGeneration) return;
60
+ for (const sub of this.subscriptions) {
61
+ sub.failureSignal.addEventListener("abort", () => {
62
+ if (!this.manualClose) {
63
+ this.onError(new Error("Hyperliquid subscription failed after reconnect"));
64
+ }
65
+ });
66
+ }
67
+ for (const coin of LOCKED_TRACKED_COINS) {
68
+ this.trackAsset(coin);
69
+ }
70
+ for (const coin of this.trackedCoins) {
71
+ if (!LOCKED_TRACKED_COINS.includes(coin)) {
72
+ await this.subscribeActiveAssetData(coin);
73
+ }
74
+ }
75
+ this.reconnectAttempts = 0;
76
+ } catch (err) {
77
+ if (gen !== this.startGeneration) return;
78
+ await this.stopAll();
79
+ this.onError(err instanceof Error ? err : new Error(String(err)));
80
+ this.scheduleReconnect();
81
+ }
82
+ }
83
+ stop() {
84
+ this.manualClose = true;
85
+ this.stopAll();
86
+ this.credentials = null;
87
+ this.address = null;
88
+ }
89
+ get isConnected() {
90
+ return this.subsClient !== null && !this.manualClose;
91
+ }
92
+ onAssetTracked(coin) {
93
+ this.subscribeActiveAssetData(coin).catch((err) => {
94
+ this.onError(err instanceof Error ? err : new Error(String(err)));
95
+ });
96
+ }
97
+ onAssetUntracked(coin) {
98
+ const sub = this.activeAssetSubscriptions.get(coin);
99
+ if (sub) {
100
+ sub.unsubscribe().catch(() => {
101
+ });
102
+ this.activeAssetSubscriptions.delete(coin);
103
+ }
104
+ this.trackedAssetData.delete(coin);
105
+ const trackedAssets = this.buildTrackedAssetsMap();
106
+ this.onStateUpdate({ trackedAssets });
107
+ }
108
+ async setLeverage(id, leverage) {
109
+ const creds = this.credentials;
110
+ if (!creds) throw new Error("Not connected");
111
+ const asset = Number.parseInt(id, 10);
112
+ if (Number.isNaN(asset)) throw new Error(`Invalid Hyperliquid asset index: ${id}`);
113
+ const tracked = this.trackedAssetData.get(id);
114
+ const isCross = tracked?.leverage?.type === "cross";
115
+ await setLeverage(creds, asset, Number.parseFloat(leverage), isCross);
116
+ }
117
+ canUntrackAsset(coin) {
118
+ return !LOCKED_TRACKED_COINS.includes(coin);
119
+ }
120
+ async fetchPerpMetas() {
121
+ if (!this.infoClient) return;
122
+ const allMetas = await this.infoClient.allPerpMetas();
123
+ allMetas.forEach((dexMeta) => {
124
+ const collateralToken = COLLATERAL_TOKEN_MAP[dexMeta.collateralToken];
125
+ dexMeta.universe.forEach((asset) => {
126
+ if (!asset.name) return;
127
+ if (collateralToken && !this.symbolToCollateralToken.has(asset.name)) {
128
+ this.symbolToCollateralToken.set(asset.name, collateralToken);
129
+ }
130
+ });
131
+ });
132
+ }
133
+ async subscribeActiveAssetData(coin) {
134
+ if (!this.subsClient || !this.address) return;
135
+ if (this.activeAssetSubscriptions.has(coin)) return;
136
+ const sub = await this.subsClient.activeAssetData(
137
+ { coin, user: this.address },
138
+ (data) => this.handleActiveAssetData(data)
139
+ );
140
+ sub.failureSignal.addEventListener("abort", () => {
141
+ if (!this.manualClose) {
142
+ this.activeAssetSubscriptions.delete(coin);
143
+ }
144
+ });
145
+ this.activeAssetSubscriptions.set(coin, sub);
146
+ }
147
+ async stopAll() {
148
+ this.clearReconnectTimer();
149
+ const assetUnsubs = Array.from(this.activeAssetSubscriptions.values()).map((s) => s.unsubscribe().catch(() => {
150
+ }));
151
+ this.activeAssetSubscriptions.clear();
152
+ const unsubs = this.subscriptions.map((s) => s.unsubscribe().catch(() => {
153
+ }));
154
+ this.subscriptions = [];
155
+ await Promise.all([...unsubs, ...assetUnsubs]);
156
+ this.subsClient = null;
157
+ this.infoClient = null;
158
+ }
159
+ handleClearinghouseState(data) {
160
+ const states = (data.clearinghouseStates || []).map(([, s]) => s).filter(Boolean);
161
+ const sumStr = (values) => values.reduce((acc, v) => acc + (Number.parseFloat(v || "0") || 0), 0);
162
+ const allAssetPositions = states.flatMap((s) => s.assetPositions || []);
163
+ const totalUnrealizedPnl = allAssetPositions.reduce(
164
+ (acc, ap) => acc + (Number.parseFloat(ap.position.unrealizedPnl || "0") || 0),
165
+ 0
166
+ );
167
+ this.lastSummary = {
168
+ accountValue: sumStr(states.map((s) => s.crossMarginSummary.accountValue)),
169
+ totalMarginUsed: sumStr(states.map((s) => s.crossMarginSummary.totalMarginUsed)),
170
+ totalRawUsd: sumStr(states.map((s) => s.crossMarginSummary.totalRawUsd)),
171
+ crossMaintenanceMarginUsed: sumStr(states.map((s) => s.crossMaintenanceMarginUsed)),
172
+ withdrawable: sumStr(states.map((s) => s.withdrawable)),
173
+ totalUnrealizedPnl
174
+ };
175
+ const balance = this.buildBalance();
176
+ const positions = mapPositions(allAssetPositions);
177
+ const leverageSettings = /* @__PURE__ */ new Map();
178
+ for (const ap of allAssetPositions) {
179
+ const coin = ap.position.coin;
180
+ if (coin && ap.position.leverage?.value) {
181
+ leverageSettings.set(coin, ap.position.leverage.value.toString());
182
+ }
183
+ }
184
+ this.onStateUpdate({ balance, positions, leverageSettings });
185
+ }
186
+ handleActiveAssetData(data) {
187
+ if (!data.coin) return;
188
+ this.trackedAssetData.set(data.coin, data);
189
+ const collateral = this.symbolToCollateralToken.get(data.coin);
190
+ if (collateral && data.availableToTrade) {
191
+ const long = Number.parseFloat(data.availableToTrade[0] || "0");
192
+ const short = Number.parseFloat(data.availableToTrade[1] || "0");
193
+ const minAvailable = Math.min(long, short);
194
+ this.availableToTradeByCollateral.set(collateral, minAvailable);
195
+ const balance = this.buildBalance();
196
+ this.onStateUpdate({ balance });
197
+ }
198
+ const trackedAssets = /* @__PURE__ */ new Map();
199
+ trackedAssets.set(data.coin, this.buildTrackedAssetInfo(data));
200
+ this.onStateUpdate({ trackedAssets });
201
+ }
202
+ buildTrackedAssetInfo(data) {
203
+ return {
204
+ coin: data.coin,
205
+ leverage: (data.leverage?.value ?? 1).toString(),
206
+ marginType: data.leverage?.type === "isolated" ? "isolated" : "cross"
207
+ };
208
+ }
209
+ buildTrackedAssetsMap() {
210
+ const result = /* @__PURE__ */ new Map();
211
+ for (const [coin, data] of this.trackedAssetData) {
212
+ result.set(coin, this.buildTrackedAssetInfo(data));
213
+ }
214
+ return result;
215
+ }
216
+ buildBalance() {
217
+ return mapBalance(this.lastSummary, this.spotCoinBalances, this.accountType, this.availableToTradeByCollateral);
218
+ }
219
+ handleWebData3(data) {
220
+ this.accountType = data.userState.abstraction || "disabled";
221
+ }
222
+ handleSpotState(data) {
223
+ const balances = data?.spotState?.balances;
224
+ if (!balances) return;
225
+ for (const b of balances) {
226
+ const total = Number.parseFloat(b.total || "0");
227
+ const hold = Number.parseFloat(b.hold || "0");
228
+ if (total !== 0 || hold !== 0) {
229
+ this.spotCoinBalances.set(b.coin, { coin: b.coin, total, hold });
230
+ } else {
231
+ this.spotCoinBalances.delete(b.coin);
232
+ }
233
+ }
234
+ const balance = this.buildBalance();
235
+ this.onStateUpdate({ balance });
236
+ }
237
+ }
238
+
239
+ export { HyperliquidOrchestrator };
@@ -0,0 +1,6 @@
1
+ import { HyperliquidAccountCredentials } from '../../types.js';
2
+ import '@pear-protocol/types';
3
+
4
+ declare function setLeverage(creds: HyperliquidAccountCredentials, asset: number, leverage: number, isCross: boolean): Promise<void>;
5
+
6
+ export { setLeverage };
@@ -0,0 +1,11 @@
1
+ import { HttpTransport, ExchangeClient } from '@nktkas/hyperliquid';
2
+ import { privateKeyToAccount } from 'viem/accounts';
3
+
4
+ async function setLeverage(creds, asset, leverage, isCross) {
5
+ const transport = new HttpTransport();
6
+ const wallet = privateKeyToAccount(creds.signer_key);
7
+ const exchangeClient = new ExchangeClient({ transport, wallet });
8
+ await exchangeClient.updateLeverage({ asset, leverage, isCross });
9
+ }
10
+
11
+ export { setLeverage };
@@ -0,0 +1,20 @@
1
+ import { AllDexsClearinghouseStateEvent } from '@nktkas/hyperliquid/api/subscription';
2
+
3
+ type ClearinghouseState = AllDexsClearinghouseStateEvent['clearinghouseStates'][number][1];
4
+ type AssetPosition = ClearinghouseState['assetPositions'][number];
5
+ interface HyperliquidCoinBalance {
6
+ coin: string;
7
+ total: number;
8
+ hold: number;
9
+ }
10
+ interface AggregatedClearinghouseSummary {
11
+ accountValue: number;
12
+ totalMarginUsed: number;
13
+ totalRawUsd: number;
14
+ crossMaintenanceMarginUsed: number;
15
+ withdrawable: number;
16
+ totalUnrealizedPnl: number;
17
+ }
18
+ declare const COLLATERAL_TOKEN_MAP: Record<number, string>;
19
+
20
+ export { type AggregatedClearinghouseSummary, type AssetPosition, COLLATERAL_TOKEN_MAP, type ClearinghouseState, type HyperliquidCoinBalance };
@@ -0,0 +1,8 @@
1
+ const COLLATERAL_TOKEN_MAP = {
2
+ 0: "USDC",
3
+ 235: "USDE",
4
+ 268: "USDT0",
5
+ 360: "USDH"
6
+ };
7
+
8
+ export { COLLATERAL_TOKEN_MAP };
@@ -1,6 +1,7 @@
1
1
  import './base';
2
2
  import { BinanceOrchestrator } from './binance/orchestrator';
3
3
  import { BybitOrchestrator } from './bybit/orchestrator';
4
+ import { HyperliquidOrchestrator } from './hyperliquid/orchestrator';
4
5
 
5
6
  function createOrchestrator(exchange, config) {
6
7
  switch (exchange) {
@@ -8,6 +9,8 @@ function createOrchestrator(exchange, config) {
8
9
  return new BinanceOrchestrator(config);
9
10
  case "bybit":
10
11
  return new BybitOrchestrator(config);
12
+ case "hyperliquid":
13
+ return new HyperliquidOrchestrator(config);
11
14
  default:
12
15
  throw new Error(`Unsupported exchange: ${exchange}`);
13
16
  }
@@ -1,8 +1,8 @@
1
1
  export { AccountManager } from './account-manager.js';
2
2
  export { ExchangeAccount } from './exchange-account.js';
3
3
  export { createOrchestrator } from './exchanges/index.js';
4
- export { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker } from './types.js';
4
+ export { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, HyperliquidAccountCredentials, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker } from './types.js';
5
5
  export { Orchestrator, OrchestratorConfig } from './exchanges/base.js';
6
+ import '@pear-protocol/core-sdk';
6
7
  import '@pear-protocol/types';
7
8
  import '../credentials.js';
8
- import '@pear-protocol/core-sdk';
@@ -1,4 +1,4 @@
1
- import { Connector, BinanceUSDMCredentialsDTO, BybitCredentialsDTO } from '@pear-protocol/types';
1
+ import { Connector, BinanceUSDMCredentialsDTO, BybitCredentialsDTO, HyperliquidCredentialsDTO } from '@pear-protocol/types';
2
2
 
3
3
  type MarginMode = 'cross' | 'isolated';
4
4
  interface AssetBalance {
@@ -7,18 +7,10 @@ interface AssetBalance {
7
7
  used: string;
8
8
  total: string;
9
9
  walletBalance: string;
10
- unrealizedPnl: string;
11
10
  usdValue: string;
12
- initialMargin: string;
13
- maintenanceMargin: string;
14
- isCollateral: boolean;
15
- collateralRatio: string;
16
- borrowedAmount: string;
17
11
  }
18
12
  interface AccountBalance {
19
- exchange: string;
20
13
  accountType: string;
21
- marginMode: string;
22
14
  free: Record<string, string>;
23
15
  used: Record<string, string>;
24
16
  total: Record<string, string>;
@@ -46,9 +38,6 @@ interface TrackedAssetInfo {
46
38
  coin: string;
47
39
  leverage: string;
48
40
  marginType: MarginMode;
49
- availableToTrade: [string, string] | null;
50
- maxTradeSzs: [string, string] | null;
51
- collateralToken: string | null;
52
41
  }
53
42
  interface ExchangeState {
54
43
  balance: AccountBalance | null;
@@ -70,7 +59,10 @@ interface AccountConfig {
70
59
  demo?: boolean;
71
60
  onAuthError?: () => void;
72
61
  }
73
- type SupportedCredentials = BinanceUSDMCredentialsDTO | BybitCredentialsDTO;
62
+ type HyperliquidAccountCredentials = HyperliquidCredentialsDTO & {
63
+ address: string;
64
+ };
65
+ type SupportedCredentials = BinanceUSDMCredentialsDTO | BybitCredentialsDTO | HyperliquidAccountCredentials;
74
66
  interface StateUpdate {
75
67
  balance?: AccountBalance;
76
68
  positions?: Map<string, AccountPosition>;
@@ -78,4 +70,4 @@ interface StateUpdate {
78
70
  trackedAssets?: Map<string, TrackedAssetInfo>;
79
71
  }
80
72
 
81
- export type { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker };
73
+ export type { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, HyperliquidAccountCredentials, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import PearSDK from '@pear-protocol/core-sdk';
2
2
  import { AccountManager } from './account/account-manager.js';
3
3
  export { ExchangeAccount } from './account/exchange-account.js';
4
4
  export { createOrchestrator } from './account/exchanges/index.js';
5
- export { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker } from './account/types.js';
5
+ export { AccountBalance, AccountConfig, AccountPosition, AssetBalance, BalanceCallback, ExchangeState, HyperliquidAccountCredentials, MarginMode, PositionsCallback, StateUpdate, SupportedCredentials, TrackedAssetCallback, TrackedAssetInfo, Tracker } from './account/types.js';
6
6
  import { Credentials } from './credentials.js';
7
7
  export { Orchestrator, OrchestratorConfig } from './account/exchanges/base.js';
8
8
  import '@pear-protocol/types';
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ class ExchangesSDK {
10
10
  constructor(config) {
11
11
  this.sdk = config.sdk;
12
12
  this.credentials = new Credentials(config.sdk);
13
- this.account = new AccountManager(this.credentials, config.demo);
13
+ this.account = new AccountManager(this.credentials, config.sdk, config.demo);
14
14
  }
15
15
  destroy() {
16
16
  this.account.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/exchanges-sdk",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Pear Protocol Exchanges SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -25,8 +25,10 @@
25
25
  "typecheck": "tsc --noEmit"
26
26
  },
27
27
  "dependencies": {
28
+ "@nktkas/hyperliquid": "0.30.2",
28
29
  "@pear-protocol/core-sdk": "0.0.8",
29
- "@pear-protocol/types": "0.0.7"
30
+ "@pear-protocol/types": "0.0.7",
31
+ "viem": "^2.43.5"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@backend/typescript-config": "workspace:*",