@pear-protocol/exchanges-sdk 0.15.0 → 0.17.0

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
@@ -103,6 +103,38 @@ Hyperliquid status requires the builder address and the minimum approved fee in
103
103
 
104
104
  Historical Hyperliquid fill synchronization identifies fills by bare `tid` and requests them aggregated by time, so partial fills of one crossing order arrive as a single fill. Consumers that persisted composite `time:coin:tid` identifiers must account for the transition overlap. Hyperliquid serves only the 10,000 most recent fills; an account with a longer history imports from that boundary forward, and the snapshot-alignment synthetics cover the residual gap. Synchronization fails closed when the exchange reports a maker rebate: the current core external-fill contract accepts only non-negative fees, so the SDK will not silently turn a rebate into a cost or advance the synchronization watermark past it.
105
105
 
106
+ ## Public Tracker (address only)
107
+
108
+ Hyperliquid answers account state for any address with no key, so an account the caller does not own, such as a vault's agent, can be tracked without `connect()`. `createPublicTracker` fetches no credentials and needs no trade account:
109
+
110
+ ```typescript
111
+ const tracker = exchanges.createPublicTracker({
112
+ connector: 'hyperliquid',
113
+ address: '0x...',
114
+ onError: (error) => console.warn(error),
115
+ });
116
+
117
+ await tracker.start(); // rejects if the venue reads or the socket fail
118
+ const offPositions = tracker.trackPosition((positions) => {
119
+ // Main-dex and HIP-3 positions together, e.g. `BTC` and `xyz:TSLA`
120
+ });
121
+
122
+ const { balance, positions } = await tracker.snapshot(); // REST, every perp dex, no socket
123
+
124
+ offPositions();
125
+ await tracker.disconnect();
126
+ ```
127
+
128
+ It is the same `HyperliquidAccountTracker` the trade-account path builds, so live updates, reconnects, snapshots and cleanup behave identically. It only reads: nothing on it can trade.
129
+
130
+ A read that has not landed never looks like an empty account. `trackPosition` first fires once the first `allDexsClearinghouseState` frame arrives, and `isInitialized` stays `false` until then. When the socket drops, the tracker clears its live state (`isInitialized` turns `false`, `getPositions()` returns `[]`) and reports the outage to `onError` once, however many reconnect attempts fail; listeners are not sent an empty list. Once the socket reopens it resubscribes, and `isInitialized` turns `true` again with the first fresh frame. A subscription the venue refuses, and a frame that cannot be mapped, also reach `onError`.
131
+
132
+ To build one directly, `HyperliquidAccountTracker` now takes the address itself:
133
+
134
+ ```typescript
135
+ new HyperliquidAccountTracker({ demo: false, address: '0x...', onError });
136
+ ```
137
+
106
138
  ## Account Status
107
139
 
108
140
  Use `status(connection)` to check whether the account is ready for trading through PEAR.
@@ -10,6 +10,7 @@ class BaseWs {
10
10
  pingTimer = null;
11
11
  messageListeners = /* @__PURE__ */ new Map();
12
12
  openListeners = /* @__PURE__ */ new Map();
13
+ closeListeners = /* @__PURE__ */ new Map();
13
14
  nextListenerId = 0;
14
15
  async connect() {
15
16
  if (this.ws) return;
@@ -42,6 +43,13 @@ class BaseWs {
42
43
  });
43
44
  ws.addEventListener("close", () => {
44
45
  this.stopPing();
46
+ for (const listener of this.closeListeners.values()) {
47
+ try {
48
+ listener();
49
+ } catch (error) {
50
+ console.warn("Error in ws close listener", error);
51
+ }
52
+ }
45
53
  });
46
54
  ws.addEventListener("error", () => {
47
55
  });
@@ -57,6 +65,7 @@ class BaseWs {
57
65
  }, "onOpen");
58
66
  const onError = /* @__PURE__ */ __name(() => {
59
67
  ws.removeEventListener("open", onOpen);
68
+ this.disconnect();
60
69
  reject(new Error("WebSocket connection failed"));
61
70
  }, "onError");
62
71
  ws.addEventListener("open", onOpen, {
@@ -98,10 +107,19 @@ class BaseWs {
98
107
  removeOpenListener(id) {
99
108
  this.openListeners.delete(id);
100
109
  }
110
+ addCloseListener(listener) {
111
+ const id = String(++this.nextListenerId);
112
+ this.closeListeners.set(id, listener);
113
+ return id;
114
+ }
115
+ removeCloseListener(id) {
116
+ this.closeListeners.delete(id);
117
+ }
101
118
  destroy() {
102
119
  this.disconnect();
103
120
  this.messageListeners.clear();
104
121
  this.openListeners.clear();
122
+ this.closeListeners.clear();
105
123
  }
106
124
  startPing() {
107
125
  this.stopPing();
@@ -86,6 +86,19 @@ async function fetchUserVaultEquities(user, demo = false) {
86
86
  }, demo);
87
87
  }
88
88
  __name(fetchUserVaultEquities, "fetchUserVaultEquities");
89
+ async function fetchBorrowLendUserState(user, demo = false) {
90
+ return fetchInfo({
91
+ type: "borrowLendUserState",
92
+ user
93
+ }, demo);
94
+ }
95
+ __name(fetchBorrowLendUserState, "fetchBorrowLendUserState");
96
+ async function fetchAllBorrowLendReserveStates(demo = false) {
97
+ return fetchInfo({
98
+ type: "allBorrowLendReserveStates"
99
+ }, demo);
100
+ }
101
+ __name(fetchAllBorrowLendReserveStates, "fetchAllBorrowLendReserveStates");
89
102
  async function fetchActiveAssetData(user, coin, demo = false) {
90
103
  return fetchInfo({
91
104
  type: "activeAssetData",
@@ -95,4 +108,4 @@ async function fetchActiveAssetData(user, coin, demo = false) {
95
108
  }
96
109
  __name(fetchActiveAssetData, "fetchActiveAssetData");
97
110
 
98
- export { HyperliquidInfoRequestError, fetchActiveAssetData, fetchAllMids, fetchClearinghouseState, fetchDelegatorSummary, fetchInfo, fetchPerpDexs, fetchSpotClearinghouseState, fetchUserAbstraction, fetchUserRole, fetchUserVaultEquities };
111
+ export { HyperliquidInfoRequestError, fetchActiveAssetData, fetchAllBorrowLendReserveStates, fetchAllMids, fetchBorrowLendUserState, fetchClearinghouseState, fetchDelegatorSummary, fetchInfo, fetchPerpDexs, fetchSpotClearinghouseState, fetchUserAbstraction, fetchUserRole, fetchUserVaultEquities };
package/dist/index.d.ts CHANGED
@@ -54,17 +54,28 @@ type ConnectOptions = {
54
54
  integratorMinPerpsTakerFee?: number;
55
55
  };
56
56
  type ExchangeConnection = HttpTypes.ExchangeCredentialsResponse;
57
+ /**
58
+ * A read-only tracker for an address the caller need not own, such as a vault's agent. Only
59
+ * Hyperliquid publishes account state by address, so it is the only connector here.
60
+ */
61
+ type PublicTrackerConfig = {
62
+ connector: 'hyperliquid';
63
+ address: `0x${string}`;
64
+ onError?: (error: Error) => void;
65
+ };
57
66
 
58
67
  interface WsMessage {
59
68
  [key: string]: unknown;
60
69
  }
61
70
  type MessageListener = (data: string) => void;
62
71
  type OpenListener = () => void;
72
+ type CloseListener = () => void;
63
73
  declare abstract class BaseWs {
64
74
  protected ws: WebSocket | null;
65
75
  protected pingTimer: ReturnType<typeof setInterval> | null;
66
76
  private messageListeners;
67
77
  private openListeners;
78
+ private closeListeners;
68
79
  private nextListenerId;
69
80
  protected abstract getWsUrl(): string;
70
81
  protected abstract getPingIntervalMs(): number;
@@ -77,6 +88,8 @@ declare abstract class BaseWs {
77
88
  removeMessageListener(id: string): void;
78
89
  addOpenListener(listener: OpenListener): string;
79
90
  removeOpenListener(id: string): void;
91
+ addCloseListener(listener: CloseListener): string;
92
+ removeCloseListener(id: string): void;
80
93
  destroy(): void;
81
94
  private startPing;
82
95
  private stopPing;
@@ -240,11 +253,19 @@ type ExchangeWsByConnector = {
240
253
  lighter: LighterExchangeWs;
241
254
  lighter_rhc: LighterExchangeWs;
242
255
  };
243
- type CreateExchangeWsConfig = {
256
+ /**
257
+ * Hyperliquid's account channels are public, so its socket needs no credentials; every other venue
258
+ * still requires them. The tuple keeps the check whole, so a connector not known to be Hyperliquid
259
+ * (a `Connector` union) still requires them too.
260
+ */
261
+ type CreateExchangeWsConfig<E extends Connector = Connector> = {
244
262
  demo: boolean;
263
+ } & ([E] extends ['hyperliquid'] ? {
264
+ credentials?: SupportedCredentials;
265
+ } : {
245
266
  credentials: SupportedCredentials;
246
- };
247
- declare function createExchangeWs<E extends Connector>(exchange: E, config: CreateExchangeWsConfig): Promise<ExchangeWsByConnector[E]>;
267
+ });
268
+ declare function createExchangeWs<E extends Connector>(exchange: E, config: CreateExchangeWsConfig<E>): Promise<ExchangeWsByConnector[E]>;
248
269
 
249
270
  interface HyperliquidHelper {
250
271
  checkAccountActivation(userAddress: `0x${string}`): Promise<boolean>;
@@ -1447,6 +1468,7 @@ declare const AccountBalance: z.ZodDiscriminatedUnion<[z.ZodObject<{
1447
1468
  withdrawable: z.ZodString;
1448
1469
  ltv: z.ZodNullable<z.ZodString>;
1449
1470
  borrowCapUsed: z.ZodNullable<z.ZodString>;
1471
+ borrowed: z.ZodString;
1450
1472
  }, z.core.$strip>>;
1451
1473
  tradingEquity: z.ZodNullable<z.ZodString>;
1452
1474
  vaultEquity: z.ZodString;
@@ -1458,6 +1480,13 @@ declare const AccountBalance: z.ZodDiscriminatedUnion<[z.ZodObject<{
1458
1480
  portfolioMarginRatio: z.ZodNullable<z.ZodString>;
1459
1481
  portfolioAccountLeverage: z.ZodNullable<z.ZodString>;
1460
1482
  borrowCapUsed: z.ZodNullable<z.ZodString>;
1483
+ borrowedValue: z.ZodNullable<z.ZodString>;
1484
+ healthFactor: z.ZodNullable<z.ZodString>;
1485
+ borrowLendReserves: z.ZodArray<z.ZodObject<{
1486
+ asset: z.ZodString;
1487
+ borrowYearlyRate: z.ZodString;
1488
+ supplyYearlyRate: z.ZodString;
1489
+ }, z.core.$strip>>;
1461
1490
  perDexState: z.ZodArray<z.ZodObject<{
1462
1491
  dex: z.ZodString;
1463
1492
  collateralToken: z.ZodNullable<z.ZodNumber>;
@@ -1772,9 +1801,12 @@ interface AccountSnapshot {
1772
1801
  type AssetSnapshotMap = Record<string, TrackedAssetInfo | null>;
1773
1802
 
1774
1803
  type ErrorHandler = (error: Error) => void;
1804
+ /**
1805
+ * What every tracker takes. Credentials are not part of it: a tracker that reads private venue
1806
+ * channels declares them in its own config, while Hyperliquid reads public account state by address.
1807
+ */
1775
1808
  type AccountTrackerConfig = {
1776
1809
  demo: boolean;
1777
- credentials: SupportedCredentials;
1778
1810
  onError?: ErrorHandler;
1779
1811
  };
1780
1812
  interface AccountTracker {
@@ -1795,7 +1827,6 @@ interface AccountTracker {
1795
1827
  declare abstract class BaseAccountTracker implements AccountTracker {
1796
1828
  protected onError: ErrorHandler;
1797
1829
  protected demo: boolean;
1798
- protected credentials: SupportedCredentials;
1799
1830
  private balance;
1800
1831
  private balanceInitialized;
1801
1832
  private positions;
@@ -2174,8 +2205,9 @@ type HyperliquidUnifiedBalance = z.infer<typeof HyperliquidUnifiedBalance>;
2174
2205
  * accounts measured hold one — `0x0871deb3…` reports USDC at −957,802.15. An unsigned row cannot
2175
2206
  * state that at all, which is the defect that made a borrowed pool indistinguishable from an
2176
2207
  * untouched one.
2177
- * - **two more columns.** `ltv` and `borrowCapUsed` are per-coin risk weights the venue renders
2178
- * beside the quantities, and both are PM-only wire fields.
2208
+ * - **three more columns.** `ltv` and `borrowCapUsed` are per-coin risk weights the venue renders
2209
+ * beside the quantities, and both are PM-only wire fields. `borrowed`, the coin's outstanding
2210
+ * debt, is the third, and PM-only as well.
2179
2211
  *
2180
2212
  * The row is otherwise the shared shape: same keys, same meanings, so a consumer reading
2181
2213
  * `perCollateral` without narrowing still reads `asset` / `equity` / `availableMargin` /
@@ -2188,8 +2220,16 @@ declare const HyperliquidPortfolioMarginCollateral: z.ZodObject<{
2188
2220
  withdrawable: z.ZodString;
2189
2221
  ltv: z.ZodNullable<z.ZodString>;
2190
2222
  borrowCapUsed: z.ZodNullable<z.ZodString>;
2223
+ borrowed: z.ZodString;
2191
2224
  }, z.core.$strip>;
2192
2225
  type HyperliquidPortfolioMarginCollateral = z.infer<typeof HyperliquidPortfolioMarginCollateral>;
2226
+ /** One borrow/lend reserve's yearly rates, named by its coin's wire name. */
2227
+ declare const HyperliquidBorrowLendReserve: z.ZodObject<{
2228
+ asset: z.ZodString;
2229
+ borrowYearlyRate: z.ZodString;
2230
+ supplyYearlyRate: z.ZodString;
2231
+ }, z.core.$strip>;
2232
+ type HyperliquidBorrowLendReserve = z.infer<typeof HyperliquidBorrowLendReserve>;
2193
2233
  declare const HyperliquidPortfolioMarginBalance: z.ZodObject<{
2194
2234
  totalEquity: z.ZodNullable<z.ZodString>;
2195
2235
  withdrawableBalance: z.ZodNullable<z.ZodString>;
@@ -2204,6 +2244,7 @@ declare const HyperliquidPortfolioMarginBalance: z.ZodObject<{
2204
2244
  withdrawable: z.ZodString;
2205
2245
  ltv: z.ZodNullable<z.ZodString>;
2206
2246
  borrowCapUsed: z.ZodNullable<z.ZodString>;
2247
+ borrowed: z.ZodString;
2207
2248
  }, z.core.$strip>>;
2208
2249
  tradingEquity: z.ZodNullable<z.ZodString>;
2209
2250
  vaultEquity: z.ZodString;
@@ -2215,6 +2256,13 @@ declare const HyperliquidPortfolioMarginBalance: z.ZodObject<{
2215
2256
  portfolioMarginRatio: z.ZodNullable<z.ZodString>;
2216
2257
  portfolioAccountLeverage: z.ZodNullable<z.ZodString>;
2217
2258
  borrowCapUsed: z.ZodNullable<z.ZodString>;
2259
+ borrowedValue: z.ZodNullable<z.ZodString>;
2260
+ healthFactor: z.ZodNullable<z.ZodString>;
2261
+ borrowLendReserves: z.ZodArray<z.ZodObject<{
2262
+ asset: z.ZodString;
2263
+ borrowYearlyRate: z.ZodString;
2264
+ supplyYearlyRate: z.ZodString;
2265
+ }, z.core.$strip>>;
2218
2266
  perDexState: z.ZodArray<z.ZodObject<{
2219
2267
  dex: z.ZodString;
2220
2268
  collateralToken: z.ZodNullable<z.ZodNumber>;
@@ -2226,36 +2274,30 @@ declare const HyperliquidPortfolioMarginBalance: z.ZodObject<{
2226
2274
  }, z.core.$strip>;
2227
2275
  type HyperliquidPortfolioMarginBalance = z.infer<typeof HyperliquidPortfolioMarginBalance>;
2228
2276
 
2229
- /**
2230
- * Hyperliquid's two read paths: the live WebSocket tracker and the one-shot REST snapshot.
2231
- *
2232
- * Both fill the same `HyperliquidBalanceContext` and hand it to the same mapper, which is what
2233
- * makes "same account state, same balance" true across two different transports. The tracker takes
2234
- * every CHANGING value off a channel — `allDexsClearinghouseState`, `spotState`, `webData3`,
2235
- * `allMids`, `activeAssetData` — including vault equity, which rides `webData3`. Its only REST
2236
- * reads happen at `start()`: the static dex metadata, and the staking position, which the venue
2237
- * publishes on no channel at all.
2238
- *
2239
- * The snapshot has no channels, so it reads the same figures over REST — vault equity from
2240
- * `userVaultEquities`, which was measured equal to the `webData3` figure on ten live accounts.
2241
- */
2242
-
2243
2277
  type HyperliquidAccountTrackerConfig = AccountTrackerConfig & {
2244
- account: TradeAccount;
2245
- credentials: SupportedCredentials;
2278
+ /** The account whose state is read. Hyperliquid answers for any address, so no credentials are needed. */
2279
+ address: `0x${string}`;
2246
2280
  };
2247
2281
  declare class HyperliquidAccountTracker extends BaseAccountTracker {
2248
2282
  private readonly address;
2249
2283
  private ws;
2250
2284
  private messageListenerId;
2251
2285
  private openListenerId;
2286
+ private closeListenerId;
2252
2287
  private lifecycleVersion;
2288
+ /** Whether the socket is down and the outage already reported, so its retries report nothing more. */
2289
+ private socketDropped;
2253
2290
  private subscribedCoins;
2254
2291
  private lastSummary;
2255
2292
  private spotCoinBalances;
2256
2293
  /** Portfolio Margin's own risk scalars, which ride the same `spotState` frame the balances do. */
2257
2294
  private portfolioMarginRatio;
2258
2295
  private borrowRatioByToken;
2296
+ /** Portfolio Margin's REST-only figures, and the timer that keeps them fresh while in that mode. */
2297
+ private borrowLend;
2298
+ private borrowLendPollTimer;
2299
+ /** Advances on every stop, so a read fired in an earlier poll period is recognised and dropped. */
2300
+ private borrowLendPollGeneration;
2259
2301
  private hasSpotState;
2260
2302
  private hasClearinghouseState;
2261
2303
  private accountType;
@@ -2265,6 +2307,17 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
2265
2307
  private hasWebData3;
2266
2308
  private vaultEquity;
2267
2309
  private stakedHype;
2310
+ /** Whether a staking re-read is running, whether another was asked for meanwhile, and its retry. */
2311
+ private stakingReadRunning;
2312
+ private stakingReadRequested;
2313
+ private stakingRetryTimer;
2314
+ private stakingEventReadStarting;
2315
+ private stakingEventReadTimer;
2316
+ private stakingEventReadPending;
2317
+ /** Whether the latest staking summary reported at least one withdrawal still queued. */
2318
+ private hasQueuedWithdrawal;
2319
+ /** Advances on every stop, so a staking read fired before it is recognised and dropped. */
2320
+ private stakingReadGeneration;
2268
2321
  private dexToMeta;
2269
2322
  private trackedAssetData;
2270
2323
  constructor(config: HyperliquidAccountTrackerConfig);
@@ -2293,6 +2346,15 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
2293
2346
  /** Subscribes to the spot state variant required by the active account mode. */
2294
2347
  private sendSpotStateSubscription;
2295
2348
  private resubscribeAll;
2349
+ /**
2350
+ * Drops the live account state when the socket goes down, and reports the outage once.
2351
+ *
2352
+ * Without this, the last frame's positions would read as live for the whole outage, or for good
2353
+ * if the socket never returns. Nothing is broadcast, since an empty list would read as a flat
2354
+ * account; `isInitialized` turns false and the reopen resubscribes. partysocket fires `close` for
2355
+ * every failed reconnect attempt too, so only the first close of an outage reaches `onError`.
2356
+ */
2357
+ private onSocketClosed;
2296
2358
  private routeRaw;
2297
2359
  private handle;
2298
2360
  private loadPerpMetas;
@@ -2314,6 +2376,58 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
2314
2376
  * vault. Partial equity is worse than no equity, so nothing is published early.
2315
2377
  */
2316
2378
  private buildBalance;
2379
+ /**
2380
+ * Runs the borrow/lend poll exactly while the account is in Portfolio Margin.
2381
+ *
2382
+ * Safe to call on every mode read: a poll already in the state the mode asks for is left alone,
2383
+ * so a steady stream of `webData3` frames neither restarts the timer nor refires a read.
2384
+ */
2385
+ private syncBorrowLendPoll;
2386
+ /**
2387
+ * Stops the borrow/lend poll and drops its figures, so no later mode or session inherits them.
2388
+ *
2389
+ * Every stop opens a new generation — a session ending, a restart, or the account leaving
2390
+ * Portfolio Margin — so a read still in flight from the period just ended can never land.
2391
+ */
2392
+ private stopBorrowLendPoll;
2393
+ /**
2394
+ * Reads the health factor and reserve rates once, then republishes the balance with them.
2395
+ *
2396
+ * A failed read is reported and left for the next tick: the previous figures stay in place, since
2397
+ * one missed read says nothing about the account that the last good one did not.
2398
+ */
2399
+ private refreshBorrowLendState;
2400
+ /**
2401
+ * Re-reads the staking account, one read at a time: a request made while a read runs is served by
2402
+ * a single read after it, however many arrive.
2403
+ *
2404
+ * That follow-up matters. A read already in flight may have left before the withdrawal it should
2405
+ * catch completed, so its answer can still hold that HYPE.
2406
+ */
2407
+ private requestStakingRead;
2408
+ /**
2409
+ * Requests at most one staking read per interval from spot frames, preserving one trailing read
2410
+ * when more frames ask during the interval.
2411
+ */
2412
+ private requestStakingReadFromSpotState;
2413
+ /** Starts one spot-triggered read, then opens the interval selected by its pending count. */
2414
+ private startStakingReadInterval;
2415
+ /**
2416
+ * Stops the staking re-reads; the cached figure stays, as it was true when last read.
2417
+ *
2418
+ * Every stop opens a new generation (a session ending, or a restart), so a read still in flight
2419
+ * from before it can never land.
2420
+ */
2421
+ private stopStakingReads;
2422
+ /**
2423
+ * Reads the staking account once, then republishes the balance with it.
2424
+ *
2425
+ * A failed read is reported and retried after `STAKING_READ_RETRY_MS`, keeping the last figure in
2426
+ * place until one succeeds.
2427
+ */
2428
+ private refreshStakingAccount;
2429
+ /** Retries a failed staking read once `STAKING_READ_RETRY_MS` has passed, unless the session stopped. */
2430
+ private scheduleStakingRetry;
2317
2431
  /** The floor of `availableToTrade` over the tracked set; `null` when nothing is tracked. */
2318
2432
  private availableMargin;
2319
2433
  /** Clears both sides of the live balance join and the public account cache without emitting. */
@@ -2406,6 +2520,7 @@ declare class LighterAccountTracker extends BaseAccountTracker {
2406
2520
  private readonly accountIdentifier;
2407
2521
  private readonly apiKeyIndex;
2408
2522
  private readonly apiPrivateKey;
2523
+ private readonly creds;
2409
2524
  private ws;
2410
2525
  private messageListenerId;
2411
2526
  private openListenerId;
@@ -2770,6 +2885,11 @@ declare class ExchangesSDK {
2770
2885
  */
2771
2886
  status(connection: ExchangeConnection, options?: ConnectOptions): Promise<AccountStatus>;
2772
2887
  createTracker(connection: ExchangeConnection): AccountTracker;
2888
+ /**
2889
+ * A read-only tracker for any address, built without `connect()`: Hyperliquid answers account
2890
+ * state for an address with no credentials, so none are fetched and no trade account is needed.
2891
+ */
2892
+ createPublicTracker(config: PublicTrackerConfig): AccountTracker;
2773
2893
  snapshot(tradeAccountId: string): Promise<AccountSnapshot>;
2774
2894
  snapshotAssets(tradeAccountId: string, coins: string[]): Promise<AssetSnapshotMap>;
2775
2895
  createHelper<E extends ExchangeHelperExchange>(exchange: E): ExchangeHelperMap[E];
@@ -2778,4 +2898,4 @@ declare class ExchangesSDK {
2778
2898
  private fetchCredentials;
2779
2899
  }
2780
2900
 
2781
- export { ACCOUNT_MODE_LABELS, ASSET_DECIMALS, AccountBalance, AccountBalanceCore, type AccountHealthStatus, AccountMode, type AccountPosition, type AccountSnapshot, type AccountStatus, type AccountSyncer, type AccountSyncerConfig, type AccountTierCallback, type AccountTracker, type AccountTrackerConfig, type AccountWarningReason, type ApiKey, type ApproveIntegratorParams, type ApproveIntegratorResult, AssetId, type AssetSnapshotMap, type BalanceCallback, BaseAccountSyncer, BaseAccountTracker, BaseWs, BinanceAccountSyncer, BinanceAccountTracker, type BinanceAccountTrackerConfig, BinanceExchangeWs, BinanceMultiAssetBalance, BinanceSingleAssetBalance, BinanceUserDataWs, type BinanceUserDataWsConfig, BinanceWsApi, type BinanceWsApiConfig, BybitAccountSyncer, BybitAccountTracker, type BybitAccountTrackerConfig, BybitExchangeWs, BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance, BybitWs, type BybitWsConfig, type CctpDepositAddressParams, type CctpDepositAddressResult, type CctpDepositHistorySettlement, type CctpDepositParams, type CctpDepositRegistrationSettlement, type CctpDepositResult, type CctpDepositSettlement, type ChangeAccountTierParams, type ChangeAccountTierResult, type CheckApiWalletParams, CollateralBalance, type ConnectOptions, type ConnectWalletParams, type ConnectWalletResult, type CreateAccountTrackerConfig, type CreateExchangeWsConfig, DecimalString, type EthereumDepositParams, type EthereumDepositResult, type ExchangeConnection, type ExchangeHelperExchange, type ExchangeHelperMap, type ExchangeState, ExchangeWs, type ExchangeWsByConnector, ExchangesSDK, type ExchangesSDKConfig, FUNXYZ_DEPOSIT_MINIMUMS_USD, FUNXYZ_ETHEREUM_MAINNET_CHAIN_ID, FUNXYZ_EVM_CHAIN_NAMES, type FastWithdrawParams, type FastWithdrawResult, type FillSyncOutcome, type FundingSyncOutcome, type FunxyzAccountState, type FunxyzAsset, type FunxyzDepositAddressParams, type FunxyzDepositAddressResult, type FunxyzDepositCredit, type FunxyzDepositParams, type FunxyzDepositResult, type FunxyzDepositSettlement, type FunxyzDepositSource, type FunxyzMarket, HyperliquidAccountSyncer, HyperliquidAccountTracker, type HyperliquidAccountTrackerConfig, HyperliquidDexState, HyperliquidExchangeWs, type HyperliquidHelper, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance, HyperliquidWs, type IntegratorApprovalRequirements, L2_ASSET_DECIMALS, LIGHTER_NATIVE_USDC_ADDRESSES, LIGHTER_QUOTE_ASSETS, LIGHTER_RHC_CHAIN_ID, LIGHTER_RHC_USDG_ADDRESS, LIGHTER_RHC_USDG_DECIMALS, LIGHTER_USDC_DECIMALS, LighterAccountSyncer, type LighterAccountSyncerConfig, type LighterAccountTier, LighterAccountTracker, type LighterAccountTrackerConfig, type LighterApplyReferralCodeParams, type LighterAuthCredentials, LighterClassicBalance, type LighterConnector, type LighterDeposit, type LighterDepositAddressParams, type LighterDepositAddressResult, type LighterDepositCredit, type LighterDepositHistoryParams, type LighterDepositNetwork, type LighterDepositPage, type LighterDepositParams, type LighterDepositProgress, type LighterDepositResult, type LighterDepositStatus, type LighterDepositWalletAdapter, LighterExchangeWs, type LighterHelper, type LighterLookupResult, type LighterOnboardingStatus, type LighterReferral, type LighterReferralTotals, type LighterReferralTradeStats, LighterRhcAssetId, type LighterRhcDepositAddressParams, type LighterRhcDepositAddressResult, type LighterRhcDepositParams, type LighterRhcDepositResult, type LighterRhcDepositWallet, type LighterRhcHelper, type LighterSharedHelper, LighterUnifiedBalance, type LighterUserReferrals, type LighterUserReferralsParams, type LighterWithdraw, type LighterWithdrawHistoryParams, type LighterWithdrawPage, type LighterWithdrawStatus, type LighterWithdrawType, LighterWs, type ListExternalFillsParams, type ListExternalFundingParams, type ListExternalPositionsParams, MAX_API_KEY_INDEX, MIN_LIGHTER_CCTP_DEPOSIT_USDC, MIN_LIGHTER_FAST_WITHDRAW_COLLATERAL, MIN_LIGHTER_RHC_DEPOSIT_USDG, MIN_USER_API_KEY_INDEX, MarginMode, type MessageListener, OkxAccountSyncer, OkxAccountTracker, type OkxAccountTrackerConfig, OkxExchangeWs, OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance, OkxWs, type OkxWsConfig, type OpenListener, type PositionsCallback, RouteType, Side, SignedDecimalString, type StateUpdate, type SupportedCredentials, type SyncerThrottle, type TrackedAssetCallback, type TrackedAssetInfo, type WalletClientWithAccount, type WithdrawParams, type WithdrawResult, type WsMessage, createAccountSyncer, createAccountTracker, createExchangeWs, createHyperliquidHelper, createLighterHelper, createLighterRhcHelper, findAvailableApiKeyIndex, getAssetDecimals, getLighterNativeUsdcAddress, listFunxyzTransferableChains, readFunxyzChainSource, readFunxyzMinimumUsd, retrieveLighterAuthToken, usdcAbi };
2901
+ export { ACCOUNT_MODE_LABELS, ASSET_DECIMALS, AccountBalance, AccountBalanceCore, type AccountHealthStatus, AccountMode, type AccountPosition, type AccountSnapshot, type AccountStatus, type AccountSyncer, type AccountSyncerConfig, type AccountTierCallback, type AccountTracker, type AccountTrackerConfig, type AccountWarningReason, type ApiKey, type ApproveIntegratorParams, type ApproveIntegratorResult, AssetId, type AssetSnapshotMap, type BalanceCallback, BaseAccountSyncer, BaseAccountTracker, BaseWs, BinanceAccountSyncer, BinanceAccountTracker, type BinanceAccountTrackerConfig, BinanceExchangeWs, BinanceMultiAssetBalance, BinanceSingleAssetBalance, BinanceUserDataWs, type BinanceUserDataWsConfig, BinanceWsApi, type BinanceWsApiConfig, BybitAccountSyncer, BybitAccountTracker, type BybitAccountTrackerConfig, BybitExchangeWs, BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance, BybitWs, type BybitWsConfig, type CctpDepositAddressParams, type CctpDepositAddressResult, type CctpDepositHistorySettlement, type CctpDepositParams, type CctpDepositRegistrationSettlement, type CctpDepositResult, type CctpDepositSettlement, type ChangeAccountTierParams, type ChangeAccountTierResult, type CheckApiWalletParams, type CloseListener, CollateralBalance, type ConnectOptions, type ConnectWalletParams, type ConnectWalletResult, type CreateAccountTrackerConfig, type CreateExchangeWsConfig, DecimalString, type EthereumDepositParams, type EthereumDepositResult, type ExchangeConnection, type ExchangeHelperExchange, type ExchangeHelperMap, type ExchangeState, ExchangeWs, type ExchangeWsByConnector, ExchangesSDK, type ExchangesSDKConfig, FUNXYZ_DEPOSIT_MINIMUMS_USD, FUNXYZ_ETHEREUM_MAINNET_CHAIN_ID, FUNXYZ_EVM_CHAIN_NAMES, type FastWithdrawParams, type FastWithdrawResult, type FillSyncOutcome, type FundingSyncOutcome, type FunxyzAccountState, type FunxyzAsset, type FunxyzDepositAddressParams, type FunxyzDepositAddressResult, type FunxyzDepositCredit, type FunxyzDepositParams, type FunxyzDepositResult, type FunxyzDepositSettlement, type FunxyzDepositSource, type FunxyzMarket, HyperliquidAccountSyncer, HyperliquidAccountTracker, type HyperliquidAccountTrackerConfig, HyperliquidBorrowLendReserve, HyperliquidDexState, HyperliquidExchangeWs, type HyperliquidHelper, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance, HyperliquidWs, type IntegratorApprovalRequirements, L2_ASSET_DECIMALS, LIGHTER_NATIVE_USDC_ADDRESSES, LIGHTER_QUOTE_ASSETS, LIGHTER_RHC_CHAIN_ID, LIGHTER_RHC_USDG_ADDRESS, LIGHTER_RHC_USDG_DECIMALS, LIGHTER_USDC_DECIMALS, LighterAccountSyncer, type LighterAccountSyncerConfig, type LighterAccountTier, LighterAccountTracker, type LighterAccountTrackerConfig, type LighterApplyReferralCodeParams, type LighterAuthCredentials, LighterClassicBalance, type LighterConnector, type LighterDeposit, type LighterDepositAddressParams, type LighterDepositAddressResult, type LighterDepositCredit, type LighterDepositHistoryParams, type LighterDepositNetwork, type LighterDepositPage, type LighterDepositParams, type LighterDepositProgress, type LighterDepositResult, type LighterDepositStatus, type LighterDepositWalletAdapter, LighterExchangeWs, type LighterHelper, type LighterLookupResult, type LighterOnboardingStatus, type LighterReferral, type LighterReferralTotals, type LighterReferralTradeStats, LighterRhcAssetId, type LighterRhcDepositAddressParams, type LighterRhcDepositAddressResult, type LighterRhcDepositParams, type LighterRhcDepositResult, type LighterRhcDepositWallet, type LighterRhcHelper, type LighterSharedHelper, LighterUnifiedBalance, type LighterUserReferrals, type LighterUserReferralsParams, type LighterWithdraw, type LighterWithdrawHistoryParams, type LighterWithdrawPage, type LighterWithdrawStatus, type LighterWithdrawType, LighterWs, type ListExternalFillsParams, type ListExternalFundingParams, type ListExternalPositionsParams, MAX_API_KEY_INDEX, MIN_LIGHTER_CCTP_DEPOSIT_USDC, MIN_LIGHTER_FAST_WITHDRAW_COLLATERAL, MIN_LIGHTER_RHC_DEPOSIT_USDG, MIN_USER_API_KEY_INDEX, MarginMode, type MessageListener, OkxAccountSyncer, OkxAccountTracker, type OkxAccountTrackerConfig, OkxExchangeWs, OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance, OkxWs, type OkxWsConfig, type OpenListener, type PositionsCallback, type PublicTrackerConfig, RouteType, Side, SignedDecimalString, type StateUpdate, type SupportedCredentials, type SyncerThrottle, type TrackedAssetCallback, type TrackedAssetInfo, type WalletClientWithAccount, type WithdrawParams, type WithdrawResult, type WsMessage, createAccountSyncer, createAccountTracker, createExchangeWs, createHyperliquidHelper, createLighterHelper, createLighterRhcHelper, findAvailableApiKeyIndex, getAssetDecimals, getLighterNativeUsdcAddress, listFunxyzTransferableChains, readFunxyzChainSource, readFunxyzMinimumUsd, retrieveLighterAuthToken, usdcAbi };
package/dist/sdk.js CHANGED
@@ -2,7 +2,7 @@ import { configureProxy } from '@pear-protocol/utils';
2
2
  import { createExchangeHelper } from './helper/factory';
3
3
  import { checkAccountStatus } from './status';
4
4
  import { createAccountSyncer } from './syncer';
5
- import { createAccountTracker } from './tracker';
5
+ import { HyperliquidAccountTracker, createAccountTracker } from './tracker';
6
6
 
7
7
  var __defProp = Object.defineProperty;
8
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -42,6 +42,20 @@ class ExchangesSDK {
42
42
  createTracker(connection) {
43
43
  return this.buildTracker(connection);
44
44
  }
45
+ /**
46
+ * A read-only tracker for any address, built without `connect()`: Hyperliquid answers account
47
+ * state for an address with no credentials, so none are fetched and no trade account is needed.
48
+ */
49
+ createPublicTracker(config) {
50
+ switch (config.connector) {
51
+ case "hyperliquid":
52
+ return new HyperliquidAccountTracker({
53
+ demo: this.demo,
54
+ onError: config.onError,
55
+ address: config.address
56
+ });
57
+ }
58
+ }
45
59
  async snapshot(tradeAccountId) {
46
60
  const connection = await this.fetchCredentials(tradeAccountId);
47
61
  return this.buildTracker(connection).snapshot();
@@ -23,14 +23,14 @@ class BaseAccountSyncer {
23
23
  }
24
24
  // Phase A: Load current Pear state (positions, fills, instruments, watermark).
25
25
  async loadPearState() {
26
- const [{ positions: openPositions }, { fills: provisionalFills }, { instruments }, watermark] = await Promise.all([
26
+ const watermark = await this.sdk.core.sync.fills.since({
27
+ overlapMs: OVERLAP_MS
28
+ });
29
+ const [{ positions: openPositions }, { fills: provisionalFills }, { instruments }] = await Promise.all([
27
30
  this.sdk.core.positions.openLite(),
28
31
  this.sdk.core.fills.provisionals(),
29
32
  this.sdk.core.instruments.lite({
30
33
  connector: this.connector
31
- }),
32
- this.sdk.core.sync.fills.since({
33
- overlapMs: OVERLAP_MS
34
34
  })
35
35
  ]);
36
36
  const entryBySymbol = await this.loadEntryBySymbol(openPositions);
@@ -128,7 +128,9 @@ class BaseAccountSyncer {
128
128
  },
129
129
  ...firstSyncStartedAt === void 0 ? {} : {
130
130
  firstSyncStartedAt
131
- }
131
+ },
132
+ // Core defers the create when any execution write postdates this server-minted token.
133
+ executionsUpdatedAtEpochMs: watermark.executionsUpdatedAtEpochMs
132
134
  };
133
135
  if (firstSyncTrim) {
134
136
  console.log(`${this.connector}: first-sync trim \u2014 dropped ${firstSyncTrim.trimmedCount} pre-cutoff fills (cutoffTs=${firstSyncTrim.cutoffTs})`);
@@ -6,7 +6,6 @@ class BaseAccountTracker {
6
6
  }
7
7
  onError;
8
8
  demo;
9
- credentials;
10
9
  balance = null;
11
10
  balanceInitialized = false;
12
11
  positions = /* @__PURE__ */ new Map();
@@ -16,7 +15,6 @@ class BaseAccountTracker {
16
15
  assetEntries = /* @__PURE__ */ new Map();
17
16
  constructor(config) {
18
17
  this.demo = config.demo;
19
- this.credentials = config.credentials;
20
18
  this.onError = (error) => {
21
19
  console.warn("[AccountTracker] error:", error.message);
22
20
  config.onError?.(error);
@@ -61,7 +61,7 @@ class BinanceAccountTracker extends BaseAccountTracker {
61
61
  this.handlePositionRisk(positionRisk, false);
62
62
  const ws = await createExchangeWs("binance", {
63
63
  demo: this.demo,
64
- credentials: this.credentials
64
+ credentials: this.creds
65
65
  });
66
66
  this.userDataWs = ws.userData;
67
67
  this.wsApiWs = ws.wsApi;
@@ -82,7 +82,7 @@ class BybitAccountTracker extends BaseAccountTracker {
82
82
  this.handleInitialData(wallet ?? null, positions, toBybitAccountMode(accountInfo.marginMode));
83
83
  this.ws = await createExchangeWs("bybit", {
84
84
  demo: this.demo,
85
- credentials: this.credentials
85
+ credentials: this.creds
86
86
  });
87
87
  this.messageListenerId = this.ws.main.addMessageListener((raw) => this.routeRaw(raw));
88
88
  this.openListenerId = this.ws.main.addOpenListener(() => {
@@ -146,7 +146,22 @@ const HyperliquidPortfolioMarginCollateral = z.object({
146
146
  * `borrowed / borrow cap` for this coin, and `null` where the venue renders `--` — no borrow.
147
147
  * NOT clipped at one: `0x0871deb3…` reports 1.0081 on USDT0.
148
148
  */
149
- borrowCapUsed: DecimalString.nullable()
149
+ borrowCapUsed: DecimalString.nullable(),
150
+ /**
151
+ * The coin's outstanding borrow in its own units, and `0` on a coin never borrowed.
152
+ *
153
+ * Read from the wire's `borrowed`, never from a negative `equity`: the USDC row already contains
154
+ * the perps account value, so `equity` is `supplied − borrowed + accountValue` and its negative
155
+ * understates the debt by exactly that wallet.
156
+ */
157
+ borrowed: DecimalString
158
+ });
159
+ const HyperliquidBorrowLendReserve = z.object({
160
+ asset: z.string(),
161
+ /** What borrowing the coin costs per year, as a fraction — `0.05` is 5% APY. */
162
+ borrowYearlyRate: DecimalString,
163
+ /** What supplying the coin earns per year; Portfolio Margin supplies idle balances automatically. */
164
+ supplyYearlyRate: DecimalString
150
165
  });
151
166
  const HyperliquidPortfolioMarginBalance = AccountBalanceCore.extend({
152
167
  accountMode: z.literal("hyperliquid:portfolioMargin"),
@@ -194,7 +209,27 @@ const HyperliquidPortfolioMarginBalance = AccountBalanceCore.extend({
194
209
  * account borrows nothing. Not clipped at one, and never the USDC entry alone.
195
210
  */
196
211
  borrowCapUsed: DecimalString.nullable(),
212
+ /**
213
+ * The venue's Your Borrowed: every row's `borrowed` valued at its own mid, in USD, and `0` when
214
+ * the account borrows nothing. Null when a borrowed coin cannot be priced, since a debt counted
215
+ * as zero would hide exactly the part nobody could value.
216
+ */
217
+ borrowedValue: DecimalString.nullable(),
218
+ /**
219
+ * The venue's Health Factor — LTV-weighted collateral over the borrowed value — at four decimals.
220
+ * Below `1` the account can borrow no more, which is not by itself liquidation: that is
221
+ * `portfolioMarginRatio`. Null when the account borrows nothing, and until the first read lands.
222
+ *
223
+ * Read, never derived, and over REST because no channel carries it: the live tracker refreshes it
224
+ * on a timer, so it can trail the channel-fed figures beside it by up to one poll interval.
225
+ */
226
+ healthFactor: DecimalString.nullable(),
227
+ /**
228
+ * Every reserve's yearly rates, listed whatever the account holds, so what a borrow will cost
229
+ * shows before the first one happens. Empty until the first read lands.
230
+ */
231
+ borrowLendReserves: z.array(HyperliquidBorrowLendReserve),
197
232
  perDexState: z.array(HyperliquidDexState)
198
233
  });
199
234
 
200
- export { HyperliquidDexState, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance };
235
+ export { HyperliquidBorrowLendReserve, HyperliquidDexState, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance };
@@ -0,0 +1,32 @@
1
+ import { fetchBorrowLendUserState, fetchAllBorrowLendReserveStates } from '../../client/hyperliquid/rest';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ const EMPTY_BORROW_LEND_STATE = {
6
+ healthFactor: null,
7
+ ratesByToken: /* @__PURE__ */ new Map()
8
+ };
9
+ async function fetchBorrowLendState(user, demo) {
10
+ const [userState, reserves] = await Promise.all([
11
+ fetchBorrowLendUserState(user, demo),
12
+ fetchAllBorrowLendReserveStates(demo)
13
+ ]);
14
+ return {
15
+ healthFactor: userState.healthFactor ?? null,
16
+ ratesByToken: ratesByToken(reserves)
17
+ };
18
+ }
19
+ __name(fetchBorrowLendState, "fetchBorrowLendState");
20
+ function ratesByToken(reserves) {
21
+ const rates = /* @__PURE__ */ new Map();
22
+ for (const [token, reserve] of reserves) {
23
+ rates.set(token, {
24
+ borrowYearlyRate: reserve.borrowYearlyRate,
25
+ supplyYearlyRate: reserve.supplyYearlyRate
26
+ });
27
+ }
28
+ return rates;
29
+ }
30
+ __name(ratesByToken, "ratesByToken");
31
+
32
+ export { EMPTY_BORROW_LEND_STATE, fetchBorrowLendState };
@@ -232,6 +232,9 @@ function mapPortfolioMarginBalance(context) {
232
232
  portfolioMarginRatio: wireMarginRatio(context),
233
233
  portfolioAccountLeverage: portfolioAccountLeverage(context),
234
234
  borrowCapUsed: worstBorrowCapUsed(context),
235
+ borrowedValue: borrowedValueOf(context),
236
+ healthFactor: wireHealthFactor(context),
237
+ borrowLendReserves: borrowLendReserveRows(context),
235
238
  perDexState: context.summary.perDexState,
236
239
  timestamp: context.timestamp
237
240
  };
@@ -256,7 +259,8 @@ function portfolioMarginCollateralRows(context) {
256
259
  availableMargin: context.availableMarginByToken.get(balance.token) ?? null,
257
260
  withdrawable: signed(total.minus(toDecimal(balance.hold)).toFixed()),
258
261
  ltv: balance.ltv === null ? null : toDecimal(balance.ltv).toFixed(),
259
- borrowCapUsed: borrowCapUsedFor(context, balance.token)
262
+ borrowCapUsed: borrowCapUsedFor(context, balance.token),
263
+ borrowed: toDecimal(balance.borrowed).toFixed()
260
264
  });
261
265
  }
262
266
  rows.sort(byRowAsset);
@@ -283,6 +287,22 @@ function worstBorrowCapUsed(context) {
283
287
  return worst.isZero() ? null : worst.toFixed(RATIO_DECIMALS);
284
288
  }
285
289
  __name(worstBorrowCapUsed, "worstBorrowCapUsed");
290
+ function borrowedValueOf(context) {
291
+ let borrowed = ZERO;
292
+ for (const balance of context.spotBalances.values()) {
293
+ const debt = toDecimal(balance.borrowed);
294
+ if (debt.isZero()) {
295
+ continue;
296
+ }
297
+ const mid = context.prices.midForToken(balance.token);
298
+ if (mid === null) {
299
+ return null;
300
+ }
301
+ borrowed = borrowed.plus(debt.times(toDecimal(mid)));
302
+ }
303
+ return borrowed.toFixed();
304
+ }
305
+ __name(borrowedValueOf, "borrowedValueOf");
286
306
  function wireMarginRatio(context) {
287
307
  if (context.portfolioMarginRatio === null) {
288
308
  return null;
@@ -290,6 +310,38 @@ function wireMarginRatio(context) {
290
310
  return toDecimal(context.portfolioMarginRatio).toFixed(RATIO_DECIMALS);
291
311
  }
292
312
  __name(wireMarginRatio, "wireMarginRatio");
313
+ function wireHealthFactor(context) {
314
+ const healthFactor = context.borrowLend.healthFactor;
315
+ if (healthFactor === null) {
316
+ return null;
317
+ }
318
+ return toDecimal(healthFactor).toFixed(RATIO_DECIMALS);
319
+ }
320
+ __name(wireHealthFactor, "wireHealthFactor");
321
+ function borrowLendReserveRows(context) {
322
+ const rows = [];
323
+ for (const [token, rates] of context.borrowLend.ratesByToken) {
324
+ const asset = context.collateralTokenNames.get(token);
325
+ if (asset === void 0) {
326
+ continue;
327
+ }
328
+ rows.push({
329
+ asset,
330
+ borrowYearlyRate: toDecimal(rates.borrowYearlyRate).toFixed(),
331
+ supplyYearlyRate: toDecimal(rates.supplyYearlyRate).toFixed()
332
+ });
333
+ }
334
+ rows.sort(byReserveAsset);
335
+ return rows;
336
+ }
337
+ __name(borrowLendReserveRows, "borrowLendReserveRows");
338
+ function byReserveAsset(left, right) {
339
+ if (left.asset === right.asset) {
340
+ return 0;
341
+ }
342
+ return left.asset < right.asset ? -1 : 1;
343
+ }
344
+ __name(byReserveAsset, "byReserveAsset");
293
345
  function portfolioAccountLeverage(context) {
294
346
  const collateral = haircutCollateral(context);
295
347
  if (collateral === null) {
@@ -2,6 +2,7 @@ import { createExchangeWs } from '../../client';
2
2
  import { fetchPerpDexs, fetchInfo, fetchAllMids, fetchUserAbstraction, fetchSpotClearinghouseState, fetchDelegatorSummary, fetchUserVaultEquities, fetchClearinghouseState, fetchActiveAssetData } from '../../client/hyperliquid/rest';
3
3
  import { BaseAccountTracker } from '../base';
4
4
  import { resolveHyperliquidAccountType } from './account-mode';
5
+ import { EMPTY_BORROW_LEND_STATE, fetchBorrowLendState } from './borrow-lend';
5
6
  import { buildAvailableMarginByToken, buildCollateralTokenNames, minimumAvailableToTrade } from './collateral';
6
7
  import { LOCKED_TRACKED_COINS } from './const';
7
8
  import { toDecimal, ZERO } from './decimal';
@@ -15,6 +16,10 @@ const EMPTY_SPOT_META = {
15
16
  tokens: [],
16
17
  universe: []
17
18
  };
19
+ const BORROW_LEND_POLL_INTERVAL_MS = 3e4;
20
+ const STAKING_READ_RETRY_MS = 3e4;
21
+ const STAKING_QUEUED_READ_INTERVAL_MS = 6e4;
22
+ const STAKING_SPECULATIVE_READ_INTERVAL_MS = 3e5;
18
23
  class HyperliquidAccountTracker extends BaseAccountTracker {
19
24
  static {
20
25
  __name(this, "HyperliquidAccountTracker");
@@ -23,13 +28,21 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
23
28
  ws = null;
24
29
  messageListenerId = null;
25
30
  openListenerId = null;
31
+ closeListenerId = null;
26
32
  lifecycleVersion = 0;
33
+ /** Whether the socket is down and the outage already reported, so its retries report nothing more. */
34
+ socketDropped = false;
27
35
  subscribedCoins = /* @__PURE__ */ new Set();
28
36
  lastSummary = emptyClearinghouseSummary();
29
37
  spotCoinBalances = /* @__PURE__ */ new Map();
30
38
  /** Portfolio Margin's own risk scalars, which ride the same `spotState` frame the balances do. */
31
39
  portfolioMarginRatio = null;
32
40
  borrowRatioByToken = /* @__PURE__ */ new Map();
41
+ /** Portfolio Margin's REST-only figures, and the timer that keeps them fresh while in that mode. */
42
+ borrowLend = EMPTY_BORROW_LEND_STATE;
43
+ borrowLendPollTimer = null;
44
+ /** Advances on every stop, so a read fired in an earlier poll period is recognised and dropped. */
45
+ borrowLendPollGeneration = 0;
33
46
  hasSpotState = false;
34
47
  hasClearinghouseState = false;
35
48
  accountType = null;
@@ -39,11 +52,22 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
39
52
  hasWebData3 = false;
40
53
  vaultEquity = "0";
41
54
  stakedHype = "0";
55
+ /** Whether a staking re-read is running, whether another was asked for meanwhile, and its retry. */
56
+ stakingReadRunning = false;
57
+ stakingReadRequested = false;
58
+ stakingRetryTimer = null;
59
+ stakingEventReadStarting = false;
60
+ stakingEventReadTimer = null;
61
+ stakingEventReadPending = false;
62
+ /** Whether the latest staking summary reported at least one withdrawal still queued. */
63
+ hasQueuedWithdrawal = false;
64
+ /** Advances on every stop, so a staking read fired before it is recognised and dropped. */
65
+ stakingReadGeneration = 0;
42
66
  dexToMeta = /* @__PURE__ */ new Map();
43
67
  trackedAssetData = /* @__PURE__ */ new Map();
44
68
  constructor(config) {
45
69
  super(config);
46
- this.address = config.account.exchangeIdentifier;
70
+ this.address = config.address;
47
71
  }
48
72
  /**
49
73
  * Reads account mode, spot prices, collateral balances, per-DEX state, staking, vaults and
@@ -74,18 +98,21 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
74
98
  })));
75
99
  const assetData = await this.fetchActiveAssetEvents(this.coinsForAvailableMargin());
76
100
  const { summary, assetPositions } = aggregateClearinghouseStates(clearinghouseStates);
101
+ const accountType = resolveHyperliquidAccountType(abstraction);
102
+ const borrowLend = accountType === HyperliquidAccountType.PORTFOLIO_MARGIN ? await fetchBorrowLendState(user, this.demo) : EMPTY_BORROW_LEND_STATE;
77
103
  const balance = mapBalance({
78
- accountType: resolveHyperliquidAccountType(abstraction),
104
+ accountType,
79
105
  summary,
80
106
  spotBalances: mapSpotBalances(spotState),
81
107
  prices: SpotPrices.from(spotMeta, mids),
82
108
  vaultEquity: sumRestVaultEquity(vaults),
83
- stakedHype: stakingAccountHype(delegator),
109
+ stakedHype: _stakingAccountHype(delegator),
84
110
  availableMargin: minimumAvailableToTrade(assetData.values()),
85
111
  collateralTokenNames: buildCollateralTokenNames(spotMeta),
86
112
  availableMarginByToken: buildAvailableMarginByToken(summary.perDexState, assetData.values()),
87
113
  portfolioMarginRatio: spotState.portfolioMarginRatio ?? null,
88
114
  borrowRatioByToken: mapBorrowRatios(spotState),
115
+ borrowLend,
89
116
  timestamp: Date.now()
90
117
  });
91
118
  const positions = Array.from(mapPositions(assetPositions).values());
@@ -116,9 +143,12 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
116
143
  const lifecycleVersion = ++this.lifecycleVersion;
117
144
  const user = this.addrLower();
118
145
  this.destroyCurrentSocket();
146
+ this.stopBorrowLendPoll();
147
+ this.stopStakingReads();
119
148
  this.subscribedCoins.clear();
120
149
  this.resetLiveAccountState();
121
150
  this.dexToMeta.clear();
151
+ this.socketDropped = false;
122
152
  const [allMetas, perpDexs, spotMeta, abstraction, delegator] = await Promise.all([
123
153
  fetchInfo({
124
154
  type: "allPerpMetas"
@@ -135,10 +165,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
135
165
  this.spotMeta = spotMeta;
136
166
  this.collateralTokenNames = buildCollateralTokenNames(spotMeta);
137
167
  this.accountType = resolveHyperliquidAccountType(abstraction);
138
- this.stakedHype = stakingAccountHype(delegator);
168
+ this.stakedHype = _stakingAccountHype(delegator);
169
+ this.hasQueuedWithdrawal = delegator.nPendingWithdrawals > 0;
139
170
  const ws = (await createExchangeWs("hyperliquid", {
140
- demo: this.demo,
141
- credentials: this.credentials
171
+ demo: this.demo
142
172
  })).main;
143
173
  if (lifecycleVersion !== this.lifecycleVersion) {
144
174
  ws.destroy();
@@ -147,7 +177,9 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
147
177
  this.ws = ws;
148
178
  this.messageListenerId = this.ws.addMessageListener((raw) => this.routeRaw(raw));
149
179
  this.openListenerId = this.ws.addOpenListener(() => this.resubscribeAll());
180
+ this.closeListenerId = this.ws.addCloseListener(() => this.onSocketClosed());
150
181
  this.sendStaticSubs();
182
+ this.syncBorrowLendPoll();
151
183
  const coinsToTrack = /* @__PURE__ */ new Set([
152
184
  ...LOCKED_TRACKED_COINS,
153
185
  ...this.trackedCoins
@@ -159,6 +191,8 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
159
191
  async cleanupConnections() {
160
192
  this.lifecycleVersion += 1;
161
193
  this.destroyCurrentSocket();
194
+ this.stopBorrowLendPoll();
195
+ this.stopStakingReads();
162
196
  this.subscribedCoins.clear();
163
197
  }
164
198
  /** Detaches and destroys the active socket without changing the current lifecycle generation. */
@@ -171,6 +205,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
171
205
  this.ws?.removeOpenListener(this.openListenerId);
172
206
  this.openListenerId = null;
173
207
  }
208
+ if (this.closeListenerId) {
209
+ this.ws?.removeCloseListener(this.closeListenerId);
210
+ this.closeListenerId = null;
211
+ }
174
212
  this.ws?.destroy();
175
213
  this.ws = null;
176
214
  }
@@ -254,6 +292,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
254
292
  }
255
293
  resubscribeAll() {
256
294
  this.resetLiveAccountState();
295
+ this.socketDropped = false;
257
296
  this.sendStaticSubs();
258
297
  const user = this.addrLower();
259
298
  for (const coin of this.subscribedCoins) {
@@ -267,6 +306,22 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
267
306
  });
268
307
  }
269
308
  }
309
+ /**
310
+ * Drops the live account state when the socket goes down, and reports the outage once.
311
+ *
312
+ * Without this, the last frame's positions would read as live for the whole outage, or for good
313
+ * if the socket never returns. Nothing is broadcast, since an empty list would read as a flat
314
+ * account; `isInitialized` turns false and the reopen resubscribes. partysocket fires `close` for
315
+ * every failed reconnect attempt too, so only the first close of an outage reaches `onError`.
316
+ */
317
+ onSocketClosed() {
318
+ if (this.socketDropped) {
319
+ return;
320
+ }
321
+ this.socketDropped = true;
322
+ this.resetLiveAccountState();
323
+ this.onError(new Error("Hyperliquid WebSocket closed; account state is unavailable until it reconnects"));
324
+ }
270
325
  routeRaw(raw) {
271
326
  try {
272
327
  const msg = JSON.parse(raw);
@@ -302,6 +357,9 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
302
357
  data: msg.data
303
358
  });
304
359
  break;
360
+ case "error":
361
+ this.onError(new Error(`Hyperliquid WebSocket error: ${String(msg.data)}`));
362
+ break;
305
363
  }
306
364
  } catch (err) {
307
365
  this.onError(err instanceof Error ? err : new Error(String(err)));
@@ -422,6 +480,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
422
480
  this.resetLiveAccountState();
423
481
  }
424
482
  this.accountType = nextAccountType;
483
+ this.syncBorrowLendPoll();
425
484
  this.vaultEquity = sumLiveVaultEquity(data.perpDexStates ?? []);
426
485
  this.hasWebData3 = true;
427
486
  if (!nextAccountType) {
@@ -440,10 +499,15 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
440
499
  onSpotState(data) {
441
500
  const balances = data?.spotState?.balances;
442
501
  if (!balances) return;
502
+ const hadSpotState = this.hasSpotState;
503
+ const previousHype = this.spotCoinBalances.get("HYPE")?.total;
443
504
  this.spotCoinBalances = mapSpotBalances(data.spotState);
444
505
  this.portfolioMarginRatio = data.spotState.portfolioMarginRatio ?? null;
445
506
  this.borrowRatioByToken = mapBorrowRatios(data.spotState);
446
507
  this.hasSpotState = true;
508
+ if (!toDecimal(this.stakedHype).isZero() && (!hadSpotState || spotHypeRose(previousHype, this.spotCoinBalances))) {
509
+ this.requestStakingReadFromSpotState();
510
+ }
447
511
  const balance = this.buildBalance();
448
512
  if (balance !== void 0) {
449
513
  this.onStateUpdate({
@@ -475,9 +539,177 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
475
539
  availableMarginByToken: buildAvailableMarginByToken(this.lastSummary.perDexState, this.trackedAssetData.values()),
476
540
  portfolioMarginRatio: this.portfolioMarginRatio,
477
541
  borrowRatioByToken: this.borrowRatioByToken,
542
+ borrowLend: this.borrowLend,
478
543
  timestamp: Date.now()
479
544
  });
480
545
  }
546
+ /**
547
+ * Runs the borrow/lend poll exactly while the account is in Portfolio Margin.
548
+ *
549
+ * Safe to call on every mode read: a poll already in the state the mode asks for is left alone,
550
+ * so a steady stream of `webData3` frames neither restarts the timer nor refires a read.
551
+ */
552
+ syncBorrowLendPoll() {
553
+ const wantsPoll = this.accountType === HyperliquidAccountType.PORTFOLIO_MARGIN;
554
+ if (wantsPoll === (this.borrowLendPollTimer !== null)) {
555
+ return;
556
+ }
557
+ if (!wantsPoll) {
558
+ this.stopBorrowLendPoll();
559
+ return;
560
+ }
561
+ void this.refreshBorrowLendState();
562
+ this.borrowLendPollTimer = setInterval(() => void this.refreshBorrowLendState(), BORROW_LEND_POLL_INTERVAL_MS);
563
+ }
564
+ /**
565
+ * Stops the borrow/lend poll and drops its figures, so no later mode or session inherits them.
566
+ *
567
+ * Every stop opens a new generation — a session ending, a restart, or the account leaving
568
+ * Portfolio Margin — so a read still in flight from the period just ended can never land.
569
+ */
570
+ stopBorrowLendPoll() {
571
+ if (this.borrowLendPollTimer !== null) {
572
+ clearInterval(this.borrowLendPollTimer);
573
+ this.borrowLendPollTimer = null;
574
+ }
575
+ this.borrowLendPollGeneration += 1;
576
+ this.borrowLend = EMPTY_BORROW_LEND_STATE;
577
+ }
578
+ /**
579
+ * Reads the health factor and reserve rates once, then republishes the balance with them.
580
+ *
581
+ * A failed read is reported and left for the next tick: the previous figures stay in place, since
582
+ * one missed read says nothing about the account that the last good one did not.
583
+ */
584
+ async refreshBorrowLendState() {
585
+ const generation = this.borrowLendPollGeneration;
586
+ let borrowLend;
587
+ try {
588
+ borrowLend = await fetchBorrowLendState(this.addrLower(), this.demo);
589
+ } catch (err) {
590
+ this.onError(err instanceof Error ? err : new Error(String(err)));
591
+ return;
592
+ }
593
+ if (generation !== this.borrowLendPollGeneration) {
594
+ return;
595
+ }
596
+ this.borrowLend = borrowLend;
597
+ const balance = this.buildBalance();
598
+ if (balance !== void 0) {
599
+ this.onStateUpdate({
600
+ balance
601
+ });
602
+ }
603
+ }
604
+ /**
605
+ * Re-reads the staking account, one read at a time: a request made while a read runs is served by
606
+ * a single read after it, however many arrive.
607
+ *
608
+ * That follow-up matters. A read already in flight may have left before the withdrawal it should
609
+ * catch completed, so its answer can still hold that HYPE.
610
+ */
611
+ async requestStakingRead() {
612
+ this.stakingReadRequested = true;
613
+ if (this.stakingReadRunning) {
614
+ return;
615
+ }
616
+ const generation = this.stakingReadGeneration;
617
+ this.stakingReadRunning = true;
618
+ while (this.stakingReadRequested && generation === this.stakingReadGeneration) {
619
+ this.stakingReadRequested = false;
620
+ await this.refreshStakingAccount(generation);
621
+ }
622
+ if (generation === this.stakingReadGeneration) {
623
+ this.stakingReadRunning = false;
624
+ }
625
+ }
626
+ /**
627
+ * Requests at most one staking read per interval from spot frames, preserving one trailing read
628
+ * when more frames ask during the interval.
629
+ */
630
+ requestStakingReadFromSpotState() {
631
+ if (this.stakingEventReadStarting || this.stakingEventReadTimer !== null) {
632
+ this.stakingEventReadPending = true;
633
+ return;
634
+ }
635
+ this.stakingEventReadStarting = true;
636
+ void this.startStakingReadInterval(this.stakingReadGeneration);
637
+ }
638
+ /** Starts one spot-triggered read, then opens the interval selected by its pending count. */
639
+ async startStakingReadInterval(generation) {
640
+ await this.requestStakingRead();
641
+ if (generation !== this.stakingReadGeneration) {
642
+ return;
643
+ }
644
+ this.stakingEventReadStarting = false;
645
+ const interval = this.hasQueuedWithdrawal ? STAKING_QUEUED_READ_INTERVAL_MS : STAKING_SPECULATIVE_READ_INTERVAL_MS;
646
+ this.stakingEventReadTimer = setTimeout(() => {
647
+ this.stakingEventReadTimer = null;
648
+ if (!this.stakingEventReadPending) {
649
+ return;
650
+ }
651
+ this.stakingEventReadPending = false;
652
+ this.requestStakingReadFromSpotState();
653
+ }, interval);
654
+ }
655
+ /**
656
+ * Stops the staking re-reads; the cached figure stays, as it was true when last read.
657
+ *
658
+ * Every stop opens a new generation (a session ending, or a restart), so a read still in flight
659
+ * from before it can never land.
660
+ */
661
+ stopStakingReads() {
662
+ if (this.stakingRetryTimer !== null) {
663
+ clearTimeout(this.stakingRetryTimer);
664
+ this.stakingRetryTimer = null;
665
+ }
666
+ if (this.stakingEventReadTimer !== null) {
667
+ clearTimeout(this.stakingEventReadTimer);
668
+ this.stakingEventReadTimer = null;
669
+ }
670
+ this.stakingEventReadStarting = false;
671
+ this.stakingEventReadPending = false;
672
+ this.stakingReadGeneration += 1;
673
+ this.stakingReadRunning = false;
674
+ this.stakingReadRequested = false;
675
+ }
676
+ /**
677
+ * Reads the staking account once, then republishes the balance with it.
678
+ *
679
+ * A failed read is reported and retried after `STAKING_READ_RETRY_MS`, keeping the last figure in
680
+ * place until one succeeds.
681
+ */
682
+ async refreshStakingAccount(generation) {
683
+ let delegator;
684
+ try {
685
+ delegator = await fetchDelegatorSummary(this.addrLower(), this.demo);
686
+ } catch (err) {
687
+ this.onError(err instanceof Error ? err : new Error(String(err)));
688
+ this.scheduleStakingRetry(generation);
689
+ return;
690
+ }
691
+ if (generation !== this.stakingReadGeneration) {
692
+ return;
693
+ }
694
+ this.stakedHype = _stakingAccountHype(delegator);
695
+ this.hasQueuedWithdrawal = delegator.nPendingWithdrawals > 0;
696
+ const balance = this.buildBalance();
697
+ if (balance !== void 0) {
698
+ this.onStateUpdate({
699
+ balance
700
+ });
701
+ }
702
+ }
703
+ /** Retries a failed staking read once `STAKING_READ_RETRY_MS` has passed, unless the session stopped. */
704
+ scheduleStakingRetry(generation) {
705
+ if (generation !== this.stakingReadGeneration || this.stakingRetryTimer !== null) {
706
+ return;
707
+ }
708
+ this.stakingRetryTimer = setTimeout(() => {
709
+ this.stakingRetryTimer = null;
710
+ void this.requestStakingRead();
711
+ }, STAKING_READ_RETRY_MS);
712
+ }
481
713
  /** The floor of `availableToTrade` over the tracked set; `null` when nothing is tracked. */
482
714
  availableMargin() {
483
715
  return minimumAvailableToTrade(this.trackedAssetData.values());
@@ -502,6 +734,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
502
734
  return result;
503
735
  }
504
736
  }
737
+ function _stakingAccountHype(delegator) {
738
+ return toDecimal(delegator.delegated).plus(toDecimal(delegator.undelegated)).plus(toDecimal(delegator.totalPendingWithdrawal)).toFixed();
739
+ }
740
+ __name(_stakingAccountHype, "_stakingAccountHype");
505
741
  function buildTrackedAssetInfo(data) {
506
742
  return {
507
743
  coin: data.coin,
@@ -540,7 +776,9 @@ function mapSpotBalances(spotState) {
540
776
  hold: balance.hold,
541
777
  // An absent `ltv` means "not collateral", which weighs zero — never a zero weight, which is
542
778
  // a stablecoin at full value. The two states stay apart from the wire onward.
543
- ltv: balance.ltv ?? null
779
+ ltv: balance.ltv ?? null,
780
+ // An absent `borrowed` means the coin was never borrowed; the key rides only a borrowed coin.
781
+ borrowed: balance.borrowed ?? null
544
782
  });
545
783
  }
546
784
  return balances;
@@ -550,10 +788,10 @@ function mapBorrowRatios(spotState) {
550
788
  return new Map(spotState.tokenToPortfolioBorrowRatio ?? []);
551
789
  }
552
790
  __name(mapBorrowRatios, "mapBorrowRatios");
553
- function stakingAccountHype(delegator) {
554
- return toDecimal(delegator.delegated).plus(toDecimal(delegator.undelegated)).toFixed();
791
+ function spotHypeRose(previousHype, balances) {
792
+ return toDecimal(balances.get("HYPE")?.total ?? "0").gt(toDecimal(previousHype ?? "0"));
555
793
  }
556
- __name(stakingAccountHype, "stakingAccountHype");
794
+ __name(spotHypeRose, "spotHypeRose");
557
795
  function sumRestVaultEquity(vaults) {
558
796
  return vaults.reduce((total, vault) => total.plus(toDecimal(vault.equity)), ZERO).toFixed();
559
797
  }
@@ -563,4 +801,4 @@ function sumLiveVaultEquity(perpDexStates) {
563
801
  }
564
802
  __name(sumLiveVaultEquity, "sumLiveVaultEquity");
565
803
 
566
- export { HyperliquidAccountTracker };
804
+ export { HyperliquidAccountTracker, _stakingAccountHype };
@@ -13,7 +13,7 @@ export { ACCOUNT_MODE_LABELS, AccountBalanceCore, AccountMode, CollateralBalance
13
13
  export { BaseAccountTracker } from './base';
14
14
  export { BinanceMultiAssetBalance, BinanceSingleAssetBalance } from './binance/balance';
15
15
  export { BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance } from './bybit/balance';
16
- export { HyperliquidDexState, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance } from './hyperliquid/balance';
16
+ export { HyperliquidBorrowLendReserve, HyperliquidDexState, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance } from './hyperliquid/balance';
17
17
  export { LighterClassicBalance, LighterUnifiedBalance } from './lighter/balance';
18
18
  export { LIGHTER_QUOTE_ASSETS } from './lighter/const';
19
19
  export { OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance } from './okx/balance';
@@ -45,8 +45,7 @@ function createAccountTracker(exchange, config) {
45
45
  return new HyperliquidAccountTracker({
46
46
  demo: config.demo,
47
47
  onError: config.onError,
48
- account: config.account,
49
- credentials: config.credentials
48
+ address: config.account.exchangeIdentifier
50
49
  });
51
50
  case "lighter":
52
51
  case "lighter_rhc":
@@ -38,6 +38,7 @@ class LighterAccountTracker extends BaseAccountTracker {
38
38
  accountIdentifier;
39
39
  apiKeyIndex;
40
40
  apiPrivateKey;
41
+ creds;
41
42
  ws = null;
42
43
  messageListenerId = null;
43
44
  openListenerId = null;
@@ -75,6 +76,7 @@ class LighterAccountTracker extends BaseAccountTracker {
75
76
  this.accountIdentifier = config.account.exchangeIdentifier;
76
77
  this.apiKeyIndex = credentials.api_key_index;
77
78
  this.apiPrivateKey = credentials.api_private_key;
79
+ this.creds = credentials;
78
80
  }
79
81
  /**
80
82
  * The tier Lighter has the account on, and every later change to it.
@@ -177,7 +179,7 @@ class LighterAccountTracker extends BaseAccountTracker {
177
179
  });
178
180
  const ws = (await createExchangeWs(this.connector, {
179
181
  demo: this.demo,
180
- credentials: this.credentials
182
+ credentials: this.creds
181
183
  })).main;
182
184
  if (lifecycleVersion !== this.lifecycleVersion) {
183
185
  ws.destroy();
@@ -502,12 +504,11 @@ class LighterAccountTracker extends BaseAccountTracker {
502
504
  return;
503
505
  }
504
506
  try {
505
- const credentials = this.credentials;
506
507
  const accountIndex = Number(this.accountIdentifier);
507
508
  const authToken = await retrieveLighterAuthToken(this.sdk, this.connector, {
508
509
  accountIndex,
509
- apiKeyIndex: credentials.api_key_index,
510
- apiPrivateKey: credentials.api_private_key
510
+ apiKeyIndex: this.creds.api_key_index,
511
+ apiPrivateKey: this.creds.api_private_key
511
512
  }, this.demo);
512
513
  const limits = await fetchAccountLimits(accountIndex, authToken, this.connector, this.demo);
513
514
  const accountTier = toLighterAccountTier(limits.user_tier);
@@ -65,7 +65,7 @@ class OkxAccountTracker extends BaseAccountTracker {
65
65
  this.handleInitialData(balance, positions);
66
66
  this.ws = await createExchangeWs("okx", {
67
67
  demo: this.demo,
68
- credentials: this.credentials
68
+ credentials: this.creds
69
69
  });
70
70
  this.messageListenerId = this.ws.main.addMessageListener((raw) => this.routeRaw(raw));
71
71
  this.openListenerId = this.ws.main.addOpenListener(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/exchanges-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Pear Protocol Exchanges SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -27,9 +27,9 @@
27
27
  "test:watch": "vitest"
28
28
  },
29
29
  "dependencies": {
30
- "@pear-protocol/core-sdk": "^2.2.0",
31
- "@pear-protocol/types": "^2.2.0",
32
- "@pear-protocol/utils": "^1.1.0",
30
+ "@pear-protocol/core-sdk": "^2.3.0",
31
+ "@pear-protocol/types": "^2.10.0",
32
+ "@pear-protocol/utils": "^1.2.0",
33
33
  "bignumber.js": "^11.1.1",
34
34
  "partysocket": "^1.0.3",
35
35
  "viem": "^2.48.8",