@pear-protocol/exchanges-sdk 0.6.7 → 0.7.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.
Files changed (40) hide show
  1. package/README.md +105 -25
  2. package/dist/client/bybit/rest.js +8 -1
  3. package/dist/client/hyperliquid/rest.js +21 -1
  4. package/dist/client/lighter/rest.js +6 -12
  5. package/dist/client/okx/rest.js +5 -1
  6. package/dist/index.d.ts +1110 -74
  7. package/dist/sdk.js +9 -2
  8. package/dist/tracker/balance-core.js +89 -0
  9. package/dist/tracker/balance.js +25 -0
  10. package/dist/tracker/basket-size.js +194 -0
  11. package/dist/tracker/binance/balance.js +11 -0
  12. package/dist/tracker/binance/mapper.js +87 -42
  13. package/dist/tracker/binance/tracker.js +29 -14
  14. package/dist/tracker/binance/types.js +0 -6
  15. package/dist/tracker/bybit/balance.js +14 -0
  16. package/dist/tracker/bybit/mapper.js +109 -52
  17. package/dist/tracker/bybit/tracker.js +242 -33
  18. package/dist/tracker/bybit/types.js +0 -7
  19. package/dist/tracker/hyperliquid/account-mode.js +14 -0
  20. package/dist/tracker/hyperliquid/balance.js +184 -0
  21. package/dist/tracker/hyperliquid/basket-size.js +93 -0
  22. package/dist/tracker/hyperliquid/coin-name.js +18 -0
  23. package/dist/tracker/hyperliquid/collateral.js +48 -0
  24. package/dist/tracker/hyperliquid/decimal.js +28 -0
  25. package/dist/tracker/hyperliquid/mapper.js +399 -176
  26. package/dist/tracker/hyperliquid/spot-pricing.js +106 -0
  27. package/dist/tracker/hyperliquid/tracker.js +237 -78
  28. package/dist/tracker/hyperliquid/types.js +1 -7
  29. package/dist/tracker/index.js +9 -7
  30. package/dist/tracker/lighter/balance.js +87 -0
  31. package/dist/tracker/lighter/basket-size.js +74 -0
  32. package/dist/tracker/lighter/mapper.js +324 -145
  33. package/dist/tracker/lighter/tracker.js +228 -117
  34. package/dist/tracker/lighter/types.js +1 -7
  35. package/dist/tracker/okx/balance.js +17 -0
  36. package/dist/tracker/okx/mapper.js +156 -58
  37. package/dist/tracker/okx/tracker.js +228 -29
  38. package/dist/tracker/okx/types.js +0 -8
  39. package/dist/tracker/types.js +0 -24
  40. package/package.json +5 -7
@@ -1,14 +1,23 @@
1
1
  import BigNumber from 'bignumber.js';
2
2
  import { createExchangeWs } from '../../client';
3
- import { fetchOrderBookDetails, fetchAccount, fetchAccountLimits } from '../../client/lighter/rest';
4
- import { retrieveLighterAuthToken } from '../../helper/lighter/auth-token.helper';
3
+ import { fetchOrderBookDetails, fetchAccount } from '../../client/lighter/rest';
5
4
  import { BaseAccountTracker } from '../base';
6
- import { mapPositions, buildAccountBalance, mapMarketDefaultAssetInfo, mapBalance, toLighterAccountTier } from './mapper';
7
- import { LighterAccountType } from './types';
5
+ import { computeMaxBasketSize } from '../basket-size';
6
+ import { toLighterBasketCalculation } from './basket-size';
7
+ import { buildSpotPriceIndex, mapPositions, mapRestBalance, mapMarketDefaultAssetInfo, toLighterAccountType, mapLiveBalance } from './mapper';
8
8
 
9
9
  var __defProp = Object.defineProperty;
10
10
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
11
- const ACCOUNT_TIER_REFRESH_INTERVAL_MS = 6e4;
11
+ const POSITION_POLL_INTERVAL_MS = 2e3;
12
+ const MARKET_REFRESH_INTERVAL_MS = 6e4;
13
+ const DECIMAL_PATTERN = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
14
+ function isZeroSize(size) {
15
+ if (!DECIMAL_PATTERN.test(size)) {
16
+ return true;
17
+ }
18
+ return new BigNumber(size).isZero();
19
+ }
20
+ __name(isZeroSize, "isZeroSize");
12
21
  function toPosRecord(positions) {
13
22
  const posRecord = {};
14
23
  for (const p of positions) {
@@ -22,45 +31,44 @@ class LighterAccountTracker extends BaseAccountTracker {
22
31
  __name(this, "LighterAccountTracker");
23
32
  }
24
33
  accountIdentifier;
25
- sdk;
26
34
  ws = null;
27
35
  messageListenerId = null;
28
36
  openListenerId = null;
29
- pollTimer = null;
30
- pollInFlight = null;
37
+ positionPollTimer = null;
38
+ positionPollInFlight = null;
39
+ marketRefreshTimer = null;
40
+ marketRefreshInFlight = null;
31
41
  startInFlight = null;
32
42
  lifecycleVersion = 0;
33
43
  stateVersion = 0;
34
44
  assetsByAssetId = /* @__PURE__ */ new Map();
35
- rawPositionsByMarketId = /* @__PURE__ */ new Map();
45
+ // Written only by `account_all`. The live balance reads its unrealized PnL and maintenance
46
+ // margin from here, so keeping REST out of it is what makes that balance wholly websocket-derived.
47
+ wsPositionsByMarketId = /* @__PURE__ */ new Map();
36
48
  marketsByMarketId = /* @__PURE__ */ new Map();
37
- summary = null;
38
- accountTier;
39
- lastAccountTierFetchAt = 0;
49
+ marketsBySymbol = /* @__PURE__ */ new Map();
50
+ // Seeded empty, which still prices the quote asset — spot dollars are dollars before any
51
+ // market data arrives, and a balance the index cannot price would otherwise read as unknown.
52
+ spotPriceBySymbol = buildSpotPriceIndex([]);
53
+ stats = null;
40
54
  hasAssets = false;
41
55
  constructor(config) {
42
56
  super(config);
43
57
  this.accountIdentifier = config.account.exchangeIdentifier;
44
- this.sdk = config.sdk;
45
58
  }
46
- /** Stateless one-shot account read; tier enrichment is optional and requires SDK-backed auth. */
59
+ /** Stateless one-shot account read — public endpoints only, and no websocket. */
47
60
  async snapshot() {
48
- const [account, markets, accountTier] = await Promise.all([
61
+ const [account, markets] = await Promise.all([
49
62
  this.fetchPrimaryAccount(),
50
- fetchOrderBookDetails(this.demo),
51
- this.fetchAccountTier()
63
+ fetchOrderBookDetails(this.demo)
52
64
  ]);
53
65
  if (!account) return {
54
66
  balance: null,
55
67
  positions: []
56
68
  };
57
69
  const { positions } = mapPositions(toPosRecord(account.positions ?? []));
58
- const marketsByMarketId = new Map(markets.map((market) => [
59
- market.market_id,
60
- market
61
- ]));
62
70
  return {
63
- balance: buildAccountBalance(account, marketsByMarketId, accountTier),
71
+ balance: mapRestBalance(account, toMarketContext(markets.perp, markets.spot), Date.now()),
64
72
  positions: Array.from(positions.values())
65
73
  };
66
74
  }
@@ -75,22 +83,48 @@ class LighterAccountTracker extends BaseAccountTracker {
75
83
  this.fetchPrimaryAccount(),
76
84
  fetchOrderBookDetails(this.demo)
77
85
  ]);
78
- const { trackedAssets } = mapPositions(toPosRecord(account?.positions ?? []), true);
79
- const defaults = /* @__PURE__ */ new Map();
80
- for (const m of markets) {
81
- defaults.set(String(m.market_id), mapMarketDefaultAssetInfo(m));
82
- }
86
+ const settings = toAccountSettings(markets.perp, account?.positions ?? []);
83
87
  const result = {};
84
88
  for (const marketId of marketIds) {
85
- result[marketId] = trackedAssets.get(marketId) ?? defaults.get(marketId) ?? null;
89
+ result[marketId] = settings.get(marketId) ?? null;
86
90
  }
87
91
  return result;
88
92
  }
93
+ /** Sizes a basket against the live account state, falling back to REST before the socket is up. */
94
+ async maxBasketSize(input) {
95
+ if (!this.stats || this.marketsBySymbol.size === 0) {
96
+ return this.snapshotMaxBasketSize(input);
97
+ }
98
+ return computeMaxBasketSize(toLighterBasketCalculation(input.legs, {
99
+ availableBalance: this.stats.total.available_balance,
100
+ perpBySymbol: this.marketsBySymbol,
101
+ settingsByMarketId: this.liveAccountSettings()
102
+ }));
103
+ }
104
+ /** Sizes a basket over REST alone, so the answer needs no running socket. */
105
+ async snapshotMaxBasketSize(input) {
106
+ const [account, markets] = await Promise.all([
107
+ this.fetchPrimaryAccount(),
108
+ fetchOrderBookDetails(this.demo)
109
+ ]);
110
+ if (!account) {
111
+ return computeMaxBasketSize({
112
+ budgets: {},
113
+ legs: []
114
+ });
115
+ }
116
+ const context = {
117
+ availableBalance: account.available_balance,
118
+ perpBySymbol: toMarketsBySymbol(markets.perp),
119
+ settingsByMarketId: toAccountSettings(markets.perp, account.positions ?? [])
120
+ };
121
+ return computeMaxBasketSize(toLighterBasketCalculation(input.legs, context));
122
+ }
89
123
  start() {
90
124
  if (this.startInFlight) {
91
125
  return this.startInFlight;
92
126
  }
93
- if (this.ws || this.pollTimer) {
127
+ if (this.ws || this.positionPollTimer) {
94
128
  return Promise.resolve();
95
129
  }
96
130
  const start = this.performStart().finally(() => {
@@ -104,16 +138,13 @@ class LighterAccountTracker extends BaseAccountTracker {
104
138
  /** Creates one tracker lifecycle while repeated callers share the same pending start. */
105
139
  async performStart() {
106
140
  const lifecycleVersion = ++this.lifecycleVersion;
107
- const [markets, accountTier] = await Promise.all([
108
- fetchOrderBookDetails(this.demo),
109
- this.fetchAccountTier()
110
- ]);
141
+ const markets = await fetchOrderBookDetails(this.demo);
111
142
  if (lifecycleVersion !== this.lifecycleVersion) return;
112
- this.accountTier = accountTier;
113
- this.lastAccountTierFetchAt = Date.now();
143
+ this.spotPriceBySymbol = buildSpotPriceIndex(markets.spot);
114
144
  const trackedAssets = /* @__PURE__ */ new Map();
115
- for (const m of markets) {
145
+ for (const m of markets.perp) {
116
146
  this.marketsByMarketId.set(m.market_id, m);
147
+ this.marketsBySymbol.set(m.symbol, m);
117
148
  trackedAssets.set(String(m.market_id), mapMarketDefaultAssetInfo(m));
118
149
  }
119
150
  this.onStateUpdate({
@@ -131,15 +162,20 @@ class LighterAccountTracker extends BaseAccountTracker {
131
162
  this.messageListenerId = this.ws.addMessageListener((raw) => this.routeRaw(raw));
132
163
  this.openListenerId = this.ws.addOpenListener(() => this.handleWsOpen());
133
164
  this.handleWsOpen();
134
- void this.pollAccount();
135
- this.pollTimer = setInterval(() => void this.pollAccount(), 2e3);
165
+ void this.pollPositions();
166
+ this.positionPollTimer = setInterval(() => void this.pollPositions(), POSITION_POLL_INTERVAL_MS);
167
+ this.marketRefreshTimer = setInterval(() => void this.refreshMarkets(), MARKET_REFRESH_INTERVAL_MS);
136
168
  }
137
169
  async cleanupConnections() {
138
170
  this.lifecycleVersion += 1;
139
171
  this.startInFlight = null;
140
- if (this.pollTimer) {
141
- clearInterval(this.pollTimer);
142
- this.pollTimer = null;
172
+ if (this.positionPollTimer) {
173
+ clearInterval(this.positionPollTimer);
174
+ this.positionPollTimer = null;
175
+ }
176
+ if (this.marketRefreshTimer) {
177
+ clearInterval(this.marketRefreshTimer);
178
+ this.marketRefreshTimer = null;
143
179
  }
144
180
  if (this.messageListenerId) {
145
181
  this.ws?.removeMessageListener(this.messageListenerId);
@@ -163,8 +199,8 @@ class LighterAccountTracker extends BaseAccountTracker {
163
199
  handleWsOpen() {
164
200
  this.stateVersion += 1;
165
201
  this.assetsByAssetId.clear();
166
- this.rawPositionsByMarketId.clear();
167
- this.summary = null;
202
+ this.wsPositionsByMarketId.clear();
203
+ this.stats = null;
168
204
  this.hasAssets = false;
169
205
  this.resetAccountState();
170
206
  const trackedAssets = /* @__PURE__ */ new Map();
@@ -230,20 +266,20 @@ class LighterAccountTracker extends BaseAccountTracker {
230
266
  ...data.positions
231
267
  };
232
268
  if (isSnapshot) {
233
- this.rawPositionsByMarketId.clear();
269
+ this.wsPositionsByMarketId.clear();
234
270
  }
235
271
  for (const [marketId, position] of Object.entries(data.positions)) {
236
- if (new BigNumber(position.position).isZero()) {
237
- const previousPosition = this.rawPositionsByMarketId.get(marketId);
272
+ if (isZeroSize(position.position)) {
273
+ const previousPosition = this.wsPositionsByMarketId.get(marketId);
238
274
  if (previousPosition) {
239
275
  positionsForMapping[marketId] = {
240
276
  ...position,
241
277
  sign: previousPosition.sign
242
278
  };
243
279
  }
244
- this.rawPositionsByMarketId.delete(marketId);
280
+ this.wsPositionsByMarketId.delete(marketId);
245
281
  } else {
246
- this.rawPositionsByMarketId.set(marketId, position);
282
+ this.wsPositionsByMarketId.set(marketId, position);
247
283
  }
248
284
  }
249
285
  const { positions, trackedAssets } = mapPositions(positionsForMapping, !isSnapshot);
@@ -255,97 +291,89 @@ class LighterAccountTracker extends BaseAccountTracker {
255
291
  if (balance) update.balance = balance;
256
292
  if (Object.keys(update).length > 0) this.onStateUpdate(update);
257
293
  }
294
+ /** Keeps both published scopes: cross drives every "Cross" field, total drives the account ones. */
258
295
  onUserStats(data) {
259
296
  this.stateVersion += 1;
260
- const s = data.stats;
261
- this.summary = {
262
- collateral: s.collateral || "0",
263
- portfolioValue: s.portfolio_value || "0",
264
- crossPortfolioValue: s.cross_stats?.portfolio_value,
265
- availableToTrade: s.available_balance || "0",
266
- marginUsagePct: s.margin_usage || "0",
267
- accountType: s.account_trading_mode === 1 ? LighterAccountType.UNIFIED : LighterAccountType.CLASSIC,
268
- accountTier: this.accountTier
297
+ const stats = data.stats;
298
+ this.stats = {
299
+ accountType: toLighterAccountType(stats.account_trading_mode),
300
+ total: stats.total_stats,
301
+ cross: stats.cross_stats
269
302
  };
270
303
  const balance = this.buildBalance();
271
304
  if (balance) this.onStateUpdate({
272
305
  balance
273
306
  });
274
307
  }
308
+ /**
309
+ * The account's balance, built from websocket state alone.
310
+ *
311
+ * Every input is websocket-sourced — `user_stats` for the two account scopes, `account_all` for
312
+ * the assets and positions — except the market context, which is venue-wide metadata this
313
+ * tracker re-reads on its own timer. This is the ONLY producer of the value `getBalance()`
314
+ * returns while the tracker is running; `snapshot()` is the separate REST path and never writes
315
+ * here.
316
+ *
317
+ * Null until both channels have delivered a frame, because a balance built from one of them
318
+ * would be missing half the account.
319
+ */
275
320
  buildBalance() {
276
- if (!this.summary || !this.hasAssets) return null;
277
- return mapBalance({
278
- ...this.summary,
279
- accountTier: this.accountTier
280
- }, this.assetsByAssetId, this.rawPositionsByMarketId.values(), this.marketsByMarketId);
321
+ if (!this.stats || !this.hasAssets) return null;
322
+ return mapLiveBalance(this.stats, this.assetsByAssetId.values(), this.wsPositionsByMarketId.values(), this.marketContext(), Date.now());
323
+ }
324
+ /** The market data both read paths join account state against, as the latest read left it. */
325
+ marketContext() {
326
+ return {
327
+ perpByMarketId: this.marketsByMarketId,
328
+ spotPriceBySymbol: this.spotPriceBySymbol
329
+ };
330
+ }
331
+ /** The account's current per-market settings as the base tracker already holds them. */
332
+ liveAccountSettings() {
333
+ const settings = /* @__PURE__ */ new Map();
334
+ for (const marketId of this.marketsByMarketId.keys()) {
335
+ const info = this.getTrackedAsset(String(marketId));
336
+ if (info) {
337
+ settings.set(String(marketId), info);
338
+ }
339
+ }
340
+ return settings;
281
341
  }
282
342
  async fetchPrimaryAccount() {
283
343
  const data = await fetchAccount(Number(this.accountIdentifier), this.demo);
284
344
  return data.accounts[0];
285
345
  }
286
- async fetchAccountTier() {
287
- if (!this.sdk || this.demo) {
288
- return void 0;
289
- }
290
- try {
291
- const credentials = this.credentials;
292
- const accountIndex = Number(this.accountIdentifier);
293
- const token = await retrieveLighterAuthToken(this.sdk, {
294
- accountIndex,
295
- apiKeyIndex: credentials.api_key_index,
296
- apiPrivateKey: credentials.api_private_key
297
- }, this.demo);
298
- const limits = await fetchAccountLimits(accountIndex, token, this.demo);
299
- return toLighterAccountTier(limits.user_tier);
300
- } catch (err) {
301
- this.onError(err instanceof Error ? err : new Error(String(err)));
302
- return void 0;
303
- }
304
- }
305
- pollAccount() {
306
- if (this.pollInFlight) return this.pollInFlight;
346
+ pollPositions() {
347
+ if (this.positionPollInFlight) return this.positionPollInFlight;
307
348
  const lifecycleVersion = this.lifecycleVersion;
308
349
  const stateVersion = this.stateVersion;
309
- const poll = this.performAccountPoll(lifecycleVersion, stateVersion).finally(() => {
310
- if (this.pollInFlight === poll) {
311
- this.pollInFlight = null;
350
+ const poll = this.performPositionPoll(lifecycleVersion, stateVersion).finally(() => {
351
+ if (this.positionPollInFlight === poll) {
352
+ this.positionPollInFlight = null;
312
353
  }
313
354
  });
314
- this.pollInFlight = poll;
355
+ this.positionPollInFlight = poll;
315
356
  return poll;
316
357
  }
317
- async performAccountPoll(lifecycleVersion, stateVersion) {
358
+ /**
359
+ * Republishes positions and per-market settings from REST.
360
+ *
361
+ * Deliberately publishes NO balance. Every field of both Lighter variants comes off the two
362
+ * websocket channels — `user_stats` carries both account scopes and `account_all` carries the
363
+ * assets and positions behind them — so a REST balance here would only overwrite a live value
364
+ * with an equal one, while making `getBalance()` REST-derived most of the time.
365
+ *
366
+ * It also leaves `wsPositionsByMarketId` alone. That store feeds the live balance's unrealized
367
+ * PnL and maintenance margin, so writing REST rows into it would make a balance labelled live
368
+ * partly REST-derived without any reader being able to tell.
369
+ */
370
+ async performPositionPoll(lifecycleVersion, stateVersion) {
318
371
  try {
319
- const refreshAccountTier = this.shouldRefreshAccountTier();
320
- if (refreshAccountTier) {
321
- this.lastAccountTierFetchAt = Date.now();
322
- }
323
- const [account, refreshedAccountTier] = await Promise.all([
324
- this.fetchPrimaryAccount(),
325
- refreshAccountTier ? this.fetchAccountTier() : Promise.resolve(void 0)
326
- ]);
372
+ const account = await this.fetchPrimaryAccount();
327
373
  if (lifecycleVersion !== this.lifecycleVersion) return;
328
- if (refreshAccountTier && refreshedAccountTier !== void 0 && refreshedAccountTier !== this.accountTier) {
329
- this.accountTier = refreshedAccountTier;
330
- const balance = this.buildBalance();
331
- if (balance) {
332
- this.onStateUpdate({
333
- balance
334
- });
335
- }
336
- }
337
374
  if (!account || stateVersion !== this.stateVersion) return;
338
- const positionRecord = toPosRecord(account.positions ?? []);
339
- const { positions, trackedAssets } = mapPositions(positionRecord, true);
340
- this.stateVersion += 1;
341
- this.rawPositionsByMarketId.clear();
342
- for (const [marketId, position] of Object.entries(positionRecord)) {
343
- if (!new BigNumber(position.position).isZero()) {
344
- this.rawPositionsByMarketId.set(marketId, position);
345
- }
346
- }
375
+ const { positions, trackedAssets } = mapPositions(toPosRecord(account.positions ?? []), true);
347
376
  this.onStateUpdate({
348
- balance: buildAccountBalance(account, this.marketsByMarketId, this.accountTier),
349
377
  positions,
350
378
  positionsMode: "replace",
351
379
  trackedAssets
@@ -356,9 +384,92 @@ class LighterAccountTracker extends BaseAccountTracker {
356
384
  }
357
385
  }
358
386
  }
359
- shouldRefreshAccountTier() {
360
- return this.sdk !== void 0 && Date.now() - this.lastAccountTierFetchAt >= ACCOUNT_TIER_REFRESH_INTERVAL_MS;
387
+ /** Shares one in-flight market read, so a slow response cannot stack requests behind the WAF. */
388
+ refreshMarkets() {
389
+ if (this.marketRefreshInFlight) return this.marketRefreshInFlight;
390
+ const refresh = this.performMarketRefresh(this.lifecycleVersion).finally(() => {
391
+ if (this.marketRefreshInFlight === refresh) {
392
+ this.marketRefreshInFlight = null;
393
+ }
394
+ });
395
+ this.marketRefreshInFlight = refresh;
396
+ return refresh;
397
+ }
398
+ /**
399
+ * Re-reads the venue's markets and republishes the balance they move.
400
+ *
401
+ * `spotEquity` values each holding at the last trade its own spot book printed, so an index read
402
+ * only at `start()` values a session-long tracker at session-old prices — and every scalar built
403
+ * on it drifts with it.
404
+ *
405
+ * The perp books ride along, which is what closes the gap `toPositionRisks` documents: a market
406
+ * Lighter listed after `start()` was unknown to the balance for the rest of the session, so a
407
+ * position on it left the maintenance figure unknown for just as long. The read already carries
408
+ * them, so learning them costs nothing. Their risk parameters come along as a consequence, which
409
+ * only makes a slowly-moving margin fraction current rather than session-old.
410
+ *
411
+ * Perp books are UPSERTED, never replaced. An account can still hold a position on a market that
412
+ * has dropped out of the listing, and the parameters this tracker last saw price that position —
413
+ * where forgetting the market would stop pricing it at all.
414
+ *
415
+ * A read that fails, or that comes back carrying no spot books at all, leaves the last good price
416
+ * index in place. Stale prices still value the account; an empty index would null every spot
417
+ * figure the account has at once, which is a worse answer than a slightly old one.
418
+ *
419
+ * Only the market data moves here. A newly listed market's exchange-default asset settings reach
420
+ * `trackAsset` consumers on the next websocket epoch, where `handleWsOpen` already republishes
421
+ * them for every known market — publishing them here would overwrite the account's own settings.
422
+ */
423
+ async performMarketRefresh(lifecycleVersion) {
424
+ try {
425
+ const markets = await fetchOrderBookDetails(this.demo);
426
+ if (lifecycleVersion !== this.lifecycleVersion) return;
427
+ for (const market of markets.perp) {
428
+ this.marketsByMarketId.set(market.market_id, market);
429
+ this.marketsBySymbol.set(market.symbol, market);
430
+ }
431
+ if (markets.spot.length > 0) {
432
+ this.spotPriceBySymbol = buildSpotPriceIndex(markets.spot);
433
+ }
434
+ const balance = this.buildBalance();
435
+ if (balance) this.onStateUpdate({
436
+ balance
437
+ });
438
+ } catch (err) {
439
+ if (lifecycleVersion === this.lifecycleVersion) {
440
+ this.onError(err instanceof Error ? err : new Error(String(err)));
441
+ }
442
+ }
443
+ }
444
+ }
445
+ function toMarketContext(perpMarkets, spotMarkets) {
446
+ return {
447
+ perpByMarketId: new Map(perpMarkets.map((market) => [
448
+ market.market_id,
449
+ market
450
+ ])),
451
+ spotPriceBySymbol: buildSpotPriceIndex(spotMarkets)
452
+ };
453
+ }
454
+ __name(toMarketContext, "toMarketContext");
455
+ function toMarketsBySymbol(perpMarkets) {
456
+ return new Map(perpMarkets.map((market) => [
457
+ market.symbol,
458
+ market
459
+ ]));
460
+ }
461
+ __name(toMarketsBySymbol, "toMarketsBySymbol");
462
+ function toAccountSettings(perpMarkets, positions) {
463
+ const settings = /* @__PURE__ */ new Map();
464
+ for (const market of perpMarkets) {
465
+ settings.set(String(market.market_id), mapMarketDefaultAssetInfo(market));
466
+ }
467
+ const { trackedAssets } = mapPositions(toPosRecord(positions), true);
468
+ for (const [marketId, info] of trackedAssets) {
469
+ settings.set(marketId, info);
361
470
  }
471
+ return settings;
362
472
  }
473
+ __name(toAccountSettings, "toAccountSettings");
363
474
 
364
475
  export { LighterAccountTracker };
@@ -4,11 +4,5 @@ var LighterAccountType = /* @__PURE__ */ (function(LighterAccountType2) {
4
4
  LighterAccountType2["UNIFIED"] = "unified";
5
5
  return LighterAccountType2;
6
6
  })({});
7
- var LighterAccountTier = /* @__PURE__ */ (function(LighterAccountTier2) {
8
- LighterAccountTier2["STANDARD"] = "standard";
9
- LighterAccountTier2["PREMIUM"] = "premium";
10
- LighterAccountTier2["PLUS"] = "plus";
11
- return LighterAccountTier2;
12
- })({});
13
7
 
14
- export { LighterAccountTier, LighterAccountType };
8
+ export { LighterAccountType };
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import { AccountBalanceCore } from '../balance-core';
3
+
4
+ const OkxSpotBalance = AccountBalanceCore.extend({
5
+ accountMode: z.literal("okx:spot")
6
+ });
7
+ const OkxFuturesBalance = AccountBalanceCore.extend({
8
+ accountMode: z.literal("okx:futures")
9
+ });
10
+ const OkxMultiCurrencyMarginBalance = AccountBalanceCore.extend({
11
+ accountMode: z.literal("okx:multiCurrencyMargin")
12
+ });
13
+ const OkxPortfolioMarginBalance = AccountBalanceCore.extend({
14
+ accountMode: z.literal("okx:portfolioMargin")
15
+ });
16
+
17
+ export { OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance };