@pear-protocol/exchanges-sdk 0.3.0 → 0.5.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 (57) hide show
  1. package/README.md +78 -12
  2. package/dist/client/binance/rest.js +4 -3
  3. package/dist/client/bybit/rest.js +2 -1
  4. package/dist/client/hyperliquid/client.test.js +41 -0
  5. package/dist/client/hyperliquid/exchange-ws.js +3 -2
  6. package/dist/client/hyperliquid/rest.js +28 -5
  7. package/dist/client/hyperliquid/ws.js +16 -4
  8. package/dist/client/index.js +2 -2
  9. package/dist/client/lighter/constants.js +9 -1
  10. package/dist/client/lighter/constants.test.js +22 -0
  11. package/dist/client/lighter/exchange-ws.js +2 -2
  12. package/dist/client/lighter/rest.js +110 -37
  13. package/dist/client/lighter/rest.test.js +152 -0
  14. package/dist/client/lighter/ws.js +7 -2
  15. package/dist/client/okx/rest.js +20 -10
  16. package/dist/helper/factory.js +2 -2
  17. package/dist/helper/hyperliquid/helper.js +28 -13
  18. package/dist/helper/hyperliquid/helper.test.js +45 -0
  19. package/dist/helper/lighter/helper.js +77 -16
  20. package/dist/helper/lighter/helper.test.js +289 -0
  21. package/dist/helper/lighter/types.js +9 -8
  22. package/dist/index.d.ts +189 -38
  23. package/dist/sdk.js +22 -16
  24. package/dist/sdk.test.js +101 -0
  25. package/dist/status/hyperliquid/status.js +18 -5
  26. package/dist/status/hyperliquid/status.test.js +81 -0
  27. package/dist/status/lighter/status.js +18 -11
  28. package/dist/status/lighter/status.test.js +75 -0
  29. package/dist/syncer/base.js +0 -2
  30. package/dist/syncer/base.test.js +0 -1
  31. package/dist/syncer/hyperliquid/syncer.js +96 -43
  32. package/dist/syncer/hyperliquid/syncer.test.js +274 -0
  33. package/dist/syncer/lighter/funding.js +25 -0
  34. package/dist/syncer/lighter/funding.test.js +48 -0
  35. package/dist/syncer/lighter/syncer.js +30 -6
  36. package/dist/syncer/lighter/syncer.test.js +132 -6
  37. package/dist/tracker/base.js +23 -6
  38. package/dist/tracker/base.test.js +132 -0
  39. package/dist/tracker/binance/tracker.js +48 -18
  40. package/dist/tracker/binance/tracker.test.js +138 -0
  41. package/dist/tracker/bybit/tracker.js +78 -23
  42. package/dist/tracker/bybit/tracker.test.js +145 -0
  43. package/dist/tracker/hyperliquid/mapper.js +244 -44
  44. package/dist/tracker/hyperliquid/mapper.test.js +255 -0
  45. package/dist/tracker/hyperliquid/tracker.js +241 -74
  46. package/dist/tracker/hyperliquid/tracker.test.js +587 -0
  47. package/dist/tracker/index.js +15 -37
  48. package/dist/tracker/lighter/mapper.js +163 -33
  49. package/dist/tracker/lighter/mapper.test.js +199 -0
  50. package/dist/tracker/lighter/tracker.js +219 -45
  51. package/dist/tracker/lighter/tracker.test.js +515 -0
  52. package/dist/tracker/lighter/types.js +10 -2
  53. package/dist/tracker/okx/tracker.js +50 -16
  54. package/dist/tracker/okx/tracker.test.js +126 -0
  55. package/dist/tracker/types.js +2 -1
  56. package/dist/tracker/utils.test.js +8 -0
  57. package/package.json +6 -5
@@ -0,0 +1,145 @@
1
+ import { vi, describe, beforeEach, it, expect } from 'vitest';
2
+ import { BybitAccountTracker } from './tracker';
3
+
4
+ const { fetchWalletBalanceMock, fetchPositionsMock, fetchAccountInfoMock, fetchPositionLeverageMock } = vi.hoisted(
5
+ () => ({
6
+ fetchWalletBalanceMock: vi.fn(),
7
+ fetchPositionsMock: vi.fn(),
8
+ fetchAccountInfoMock: vi.fn(),
9
+ fetchPositionLeverageMock: vi.fn()
10
+ })
11
+ );
12
+ vi.mock("../../client/bybit/rest", () => ({
13
+ fetchWalletBalance: fetchWalletBalanceMock,
14
+ fetchPositions: fetchPositionsMock,
15
+ fetchAccountInfo: fetchAccountInfoMock,
16
+ fetchPositionLeverage: fetchPositionLeverageMock
17
+ }));
18
+ const creds = { api_key: "k", api_secret: "s" };
19
+ function buildTracker() {
20
+ return new BybitAccountTracker({ demo: true, credentials: creds });
21
+ }
22
+ describe("BybitAccountTracker.snapshot", () => {
23
+ beforeEach(() => {
24
+ fetchWalletBalanceMock.mockReset();
25
+ fetchPositionsMock.mockReset();
26
+ fetchAccountInfoMock.mockReset();
27
+ });
28
+ it("returns mapped balance + positions across USDT and USDC settle coins with no socket opened", async () => {
29
+ fetchWalletBalanceMock.mockResolvedValue({
30
+ totalEquity: "2000",
31
+ totalWalletBalance: "2000",
32
+ totalMarginBalance: "2000",
33
+ totalAvailableBalance: "1800",
34
+ totalPerpUPL: "50",
35
+ totalInitialMargin: "100",
36
+ totalMaintenanceMargin: "20",
37
+ accountMMRate: "0.0100",
38
+ coin: [
39
+ {
40
+ coin: "USDT",
41
+ equity: "2000",
42
+ usdValue: "2000",
43
+ walletBalance: "2000",
44
+ unrealisedPnl: "50",
45
+ totalPositionIM: "100",
46
+ totalPositionMM: "20",
47
+ totalOrderIM: "0",
48
+ locked: "0",
49
+ borrowAmount: "0",
50
+ marginCollateral: true,
51
+ collateralSwitch: true
52
+ }
53
+ ]
54
+ });
55
+ fetchPositionsMock.mockImplementation(async (_creds, settleCoin) => {
56
+ if (settleCoin === "USDT") {
57
+ return [
58
+ {
59
+ symbol: "BTCUSDT",
60
+ side: "Buy",
61
+ size: "1",
62
+ avgPrice: "50000",
63
+ unrealisedPnl: "10",
64
+ leverage: "5",
65
+ tradeMode: "0",
66
+ liqPrice: "40000"
67
+ }
68
+ ];
69
+ }
70
+ return [];
71
+ });
72
+ fetchAccountInfoMock.mockResolvedValue({ marginMode: "REGULAR_MARGIN", updatedTime: "0" });
73
+ const snapshot = await buildTracker().snapshot();
74
+ expect(snapshot.balance).toMatchObject({
75
+ accountType: "REGULAR_MARGIN",
76
+ totalEquity: "2000",
77
+ availableToTrade: "1800",
78
+ maintenanceMargin: "20",
79
+ marginRatio: "0.0100"
80
+ });
81
+ expect(snapshot.positions).toHaveLength(1);
82
+ expect(snapshot.positions[0]).toMatchObject({
83
+ symbol: "BTCUSDT",
84
+ side: "long",
85
+ leverage: "5",
86
+ marginType: "cross",
87
+ liquidationPrice: "40000"
88
+ });
89
+ expect(fetchPositionsMock).toHaveBeenCalledWith(creds, "USDT", true);
90
+ expect(fetchPositionsMock).toHaveBeenCalledWith(creds, "USDC", true);
91
+ expect(fetchPositionLeverageMock).not.toHaveBeenCalled();
92
+ });
93
+ it("returns a null balance when the wallet fetch is empty", async () => {
94
+ fetchWalletBalanceMock.mockResolvedValue(void 0);
95
+ fetchPositionsMock.mockResolvedValue([]);
96
+ fetchAccountInfoMock.mockResolvedValue({ marginMode: "REGULAR_MARGIN", updatedTime: "0" });
97
+ const snapshot = await buildTracker().snapshot();
98
+ expect(snapshot.balance).toBeNull();
99
+ expect(snapshot.positions).toEqual([]);
100
+ });
101
+ });
102
+ describe("BybitAccountTracker.snapshotAssets", () => {
103
+ beforeEach(() => {
104
+ fetchPositionsMock.mockReset();
105
+ fetchPositionLeverageMock.mockReset();
106
+ });
107
+ it(">2 symbols: resolves positioned symbols from the two settleCoin reads and fills in the rest per symbol", async () => {
108
+ fetchPositionsMock.mockImplementation(
109
+ async (_creds, settleCoin) => settleCoin === "USDT" ? [
110
+ { symbol: "BTCUSDT", size: "1", leverage: "10", tradeMode: "0" },
111
+ { symbol: "ETHUSDT", size: "2", leverage: "5", tradeMode: "1" }
112
+ ] : []
113
+ );
114
+ fetchPositionLeverageMock.mockResolvedValue({ symbol: "DOGEUSDT", size: "0", leverage: "3", tradeMode: "0" });
115
+ const result = await buildTracker().snapshotAssets(["BTCUSDT", "ETHUSDT", "DOGEUSDT"]);
116
+ expect(fetchPositionsMock).toHaveBeenCalledTimes(2);
117
+ expect(fetchPositionLeverageMock).toHaveBeenCalledTimes(1);
118
+ expect(fetchPositionLeverageMock).toHaveBeenCalledWith(creds, "DOGEUSDT", true);
119
+ expect(result).toEqual({
120
+ BTCUSDT: { coin: "BTCUSDT", leverage: "10", marginType: "cross" },
121
+ ETHUSDT: { coin: "ETHUSDT", leverage: "5", marginType: "isolated" },
122
+ DOGEUSDT: { coin: "DOGEUSDT", leverage: "3", marginType: "cross" }
123
+ });
124
+ });
125
+ it("\u22642 symbols: skips the settleCoin batch and reads per symbol directly", async () => {
126
+ fetchPositionLeverageMock.mockResolvedValue({ symbol: "BTCUSDT", size: "0", leverage: "10", tradeMode: "0" });
127
+ const result = await buildTracker().snapshotAssets(["BTCUSDT"]);
128
+ expect(fetchPositionsMock).not.toHaveBeenCalled();
129
+ expect(fetchPositionLeverageMock).toHaveBeenCalledTimes(1);
130
+ expect(result.BTCUSDT).toEqual({ coin: "BTCUSDT", leverage: "10", marginType: "cross" });
131
+ });
132
+ it("maps a failed fill-in to null while the others succeed", async () => {
133
+ fetchPositionLeverageMock.mockImplementation(async (_creds, symbol) => {
134
+ if (symbol === "NOPEUSDT") throw new Error("10001");
135
+ return { symbol, size: "0", leverage: "10", tradeMode: "0" };
136
+ });
137
+ const result = await buildTracker().snapshotAssets(["BTCUSDT", "NOPEUSDT"]);
138
+ expect(result.BTCUSDT).toMatchObject({ leverage: "10", marginType: "cross" });
139
+ expect(result.NOPEUSDT).toBeNull();
140
+ });
141
+ it("throws when every per-symbol read fails", async () => {
142
+ fetchPositionLeverageMock.mockRejectedValue(new Error("down"));
143
+ await expect(buildTracker().snapshotAssets(["BTCUSDT", "ETHUSDT"])).rejects.toThrow("every position read failed");
144
+ });
145
+ });
@@ -1,35 +1,141 @@
1
1
  import BigNumber from 'bignumber.js';
2
2
  import { positionKey } from '../utils';
3
- import { COLLATERAL_TOKEN_MAP } from './types';
3
+ import { COLLATERAL_TOKEN_MAP, HyperliquidAccountType } from './types';
4
4
 
5
- const COLLATERAL_COINS = new Set(Object.values(COLLATERAL_TOKEN_MAP));
6
- function mapBalance(summary, spotCoinBalances, accountType, availableToTradeByCollateral) {
7
- if (!accountType) return null;
5
+ const LEGACY_COLLATERAL_TOKEN_MAP = new Map(
6
+ Object.entries(COLLATERAL_TOKEN_MAP).map(([token, coin]) => [Number(token), coin])
7
+ );
8
+ const FULL_UTILIZATION_RATIO = "1.0000";
9
+ const ZERO_COLLATERAL_SUMMARY = {
10
+ accountValue: "0",
11
+ totalMarginUsed: "0",
12
+ crossMaintenanceMarginUsed: "0",
13
+ isolatedMaintenanceMarginUsed: "0",
14
+ isolatedMarginUsed: "0",
15
+ withdrawable: "0",
16
+ totalUnrealizedPnl: "0"
17
+ };
18
+ function aggregateClearinghouseStates(states) {
19
+ const summary = {
20
+ accountValue: "0",
21
+ totalMarginUsed: "0",
22
+ totalRawUsd: "0",
23
+ crossMaintenanceMarginUsed: "0",
24
+ isolatedMaintenanceMarginUsed: "0",
25
+ maximumStandardMarginRatio: "0.0000",
26
+ withdrawable: "0",
27
+ totalUnrealizedPnl: "0",
28
+ byCollateralToken: /* @__PURE__ */ new Map()
29
+ };
30
+ const assetPositions = [];
31
+ for (const dexState of states) {
32
+ const state = dexState.state;
33
+ const positions = state.assetPositions ?? [];
34
+ const unrealizedPnl = sum(positions.map((position) => position.position.unrealizedPnl));
35
+ const isolatedRisks = positions.flatMap((position) => {
36
+ if (position.position.leverage.type !== "isolated") {
37
+ return [];
38
+ }
39
+ return [{ collateral: position.position.marginUsed, maintenance: isolatedMaintenanceMargin(dexState, position) }];
40
+ });
41
+ const isolatedMaintenanceMarginUsed = sum(isolatedRisks.map((risk) => risk.maintenance));
42
+ const isolatedMarginUsed = sum(isolatedRisks.map((risk) => risk.collateral));
43
+ let dexMaximumMarginRatio = ratio(state.crossMaintenanceMarginUsed, state.crossMarginSummary.accountValue);
44
+ for (const isolatedRisk of isolatedRisks) {
45
+ dexMaximumMarginRatio = BigNumber.max(
46
+ dexMaximumMarginRatio,
47
+ ratio(isolatedRisk.maintenance, isolatedRisk.collateral)
48
+ ).toFixed(4);
49
+ }
50
+ summary.accountValue = add(summary.accountValue, state.marginSummary.accountValue);
51
+ summary.totalMarginUsed = add(summary.totalMarginUsed, state.marginSummary.totalMarginUsed);
52
+ summary.totalRawUsd = add(summary.totalRawUsd, state.marginSummary.totalRawUsd);
53
+ summary.crossMaintenanceMarginUsed = add(summary.crossMaintenanceMarginUsed, state.crossMaintenanceMarginUsed);
54
+ summary.isolatedMaintenanceMarginUsed = add(summary.isolatedMaintenanceMarginUsed, isolatedMaintenanceMarginUsed);
55
+ summary.maximumStandardMarginRatio = BigNumber.max(
56
+ summary.maximumStandardMarginRatio,
57
+ dexMaximumMarginRatio
58
+ ).toFixed(4);
59
+ summary.withdrawable = add(summary.withdrawable, state.withdrawable);
60
+ summary.totalUnrealizedPnl = add(summary.totalUnrealizedPnl, unrealizedPnl);
61
+ assetPositions.push(...positions);
62
+ if (dexState.collateralToken === null) {
63
+ continue;
64
+ }
65
+ const collateral = summary.byCollateralToken.get(dexState.collateralToken) ?? ZERO_COLLATERAL_SUMMARY;
66
+ summary.byCollateralToken.set(dexState.collateralToken, {
67
+ accountValue: add(collateral.accountValue, state.marginSummary.accountValue),
68
+ totalMarginUsed: add(collateral.totalMarginUsed, state.marginSummary.totalMarginUsed),
69
+ crossMaintenanceMarginUsed: add(collateral.crossMaintenanceMarginUsed, state.crossMaintenanceMarginUsed),
70
+ isolatedMaintenanceMarginUsed: add(collateral.isolatedMaintenanceMarginUsed, isolatedMaintenanceMarginUsed),
71
+ isolatedMarginUsed: add(collateral.isolatedMarginUsed, isolatedMarginUsed),
72
+ withdrawable: add(collateral.withdrawable, state.withdrawable),
73
+ totalUnrealizedPnl: add(collateral.totalUnrealizedPnl, unrealizedPnl)
74
+ });
75
+ }
76
+ return { summary, assetPositions };
77
+ }
78
+ function mapBalance(summary, spotCoinBalances, accountType, collateralTokenMap = LEGACY_COLLATERAL_TOKEN_MAP) {
79
+ if (!accountType) {
80
+ return null;
81
+ }
82
+ switch (accountType) {
83
+ case HyperliquidAccountType.DEFAULT:
84
+ case HyperliquidAccountType.DISABLED:
85
+ return mapStandardBalance(summary, accountType, collateralTokenMap);
86
+ case HyperliquidAccountType.UNIFIED_ACCOUNT:
87
+ return mapUnifiedBalance(summary, spotCoinBalances, accountType, collateralTokenMap);
88
+ case HyperliquidAccountType.DEX_ABSTRACTION:
89
+ case HyperliquidAccountType.PORTFOLIO_MARGIN:
90
+ return null;
91
+ }
92
+ }
93
+ function mapPositions(raw) {
94
+ const positions = /* @__PURE__ */ new Map();
95
+ for (const assetPosition of raw) {
96
+ const position = assetPosition.position;
97
+ const size = new BigNumber(position.szi || "0");
98
+ if (size.isZero()) {
99
+ continue;
100
+ }
101
+ const coin = position.coin || "";
102
+ const side = size.isPositive() ? "long" : "short";
103
+ positions.set(positionKey(coin, side), {
104
+ symbol: coin,
105
+ side,
106
+ size: size.toFixed(),
107
+ entryPrice: position.entryPx || "0",
108
+ unrealizedPnl: position.unrealizedPnl || "0",
109
+ leverage: position.leverage.value.toString(),
110
+ marginType: position.leverage.type,
111
+ liquidationPrice: position.liquidationPx || null
112
+ });
113
+ }
114
+ return positions;
115
+ }
116
+ function mapStandardBalance(summary, accountType, collateralTokenMap) {
8
117
  const free = {};
9
118
  const used = {};
10
119
  const total = {};
11
120
  const assets = [];
12
- for (const [, coin] of spotCoinBalances) {
13
- if (!COLLATERAL_COINS.has(coin.coin)) continue;
14
- const availableOverride = availableToTradeByCollateral.get(coin.coin);
15
- const coinFree = availableOverride !== void 0 ? availableOverride : new BigNumber(coin.total).minus(coin.hold).toFixed();
16
- const coinUsed = coin.hold;
17
- const coinTotal = coin.total;
18
- free[coin.coin] = coinFree;
19
- used[coin.coin] = coinUsed;
20
- total[coin.coin] = coinTotal;
121
+ for (const [token, collateral] of summary.byCollateralToken) {
122
+ const coin = collateralTokenMap.get(token);
123
+ if (!coin) {
124
+ continue;
125
+ }
126
+ free[coin] = collateral.withdrawable;
127
+ used[coin] = collateral.totalMarginUsed;
128
+ total[coin] = collateral.accountValue;
21
129
  assets.push({
22
- asset: coin.coin,
23
- free: coinFree,
24
- used: coinUsed,
25
- total: coinTotal,
26
- walletBalance: coinTotal,
27
- usdValue: coinTotal
130
+ asset: coin,
131
+ free: collateral.withdrawable,
132
+ used: collateral.totalMarginUsed,
133
+ total: collateral.accountValue,
134
+ walletBalance: subtract(collateral.accountValue, collateral.totalUnrealizedPnl),
135
+ usdValue: collateral.accountValue
28
136
  });
29
137
  }
30
- const accountValueBN = new BigNumber(summary.accountValue);
31
- const walletBalance = accountValueBN.minus(summary.totalUnrealizedPnl).toFixed();
32
- const marginRatio = accountValueBN.gt(0) ? new BigNumber(summary.crossMaintenanceMarginUsed).div(accountValueBN).toFixed(4) : "0.0000";
138
+ const maintenanceMargin = add(summary.crossMaintenanceMarginUsed, summary.isolatedMaintenanceMarginUsed);
33
139
  return {
34
140
  accountType,
35
141
  free,
@@ -37,35 +143,129 @@ function mapBalance(summary, spotCoinBalances, accountType, availableToTradeByCo
37
143
  total,
38
144
  availableToTrade: summary.withdrawable,
39
145
  totalEquity: summary.accountValue,
40
- walletBalance,
146
+ walletBalance: subtract(summary.accountValue, summary.totalUnrealizedPnl),
41
147
  unrealizedPnl: summary.totalUnrealizedPnl,
42
148
  initialMarginUsed: summary.totalMarginUsed,
43
- maintenanceMargin: summary.crossMaintenanceMarginUsed,
44
- marginRatio,
149
+ maintenanceMargin,
150
+ marginRatio: summary.maximumStandardMarginRatio,
45
151
  assets,
46
152
  timestamp: Date.now()
47
153
  };
48
154
  }
49
- function mapPositions(raw) {
50
- const positions = /* @__PURE__ */ new Map();
51
- for (const ap of raw) {
52
- const pos = ap.position;
53
- const sziBN = new BigNumber(pos.szi || "0");
54
- if (sziBN.isZero()) continue;
55
- const coin = pos.coin || "";
56
- const side = sziBN.isPositive() ? "long" : "short";
57
- positions.set(positionKey(coin, side), {
58
- symbol: coin,
59
- side,
60
- size: sziBN.toFixed(),
61
- entryPrice: pos.entryPx || "0",
62
- unrealizedPnl: pos.unrealizedPnl || "0",
63
- leverage: pos.leverage.value.toString(),
64
- marginType: pos.leverage.type,
65
- liquidationPrice: pos.liquidationPx || null
155
+ function mapUnifiedBalance(summary, spotCoinBalances, accountType, collateralTokenMap) {
156
+ const free = {};
157
+ const used = {};
158
+ const total = {};
159
+ const assets = [];
160
+ let totalEquity = new BigNumber(0);
161
+ let availableToTrade = new BigNumber(0);
162
+ for (const balance of spotCoinBalances.values()) {
163
+ if (!collateralTokenMap.has(balance.token)) {
164
+ continue;
165
+ }
166
+ const collateral = summary.byCollateralToken.get(balance.token) ?? ZERO_COLLATERAL_SUMMARY;
167
+ const coinUsed = balance.hold;
168
+ const coinFree = subtract(balance.total, coinUsed);
169
+ const coinWalletBalance = subtract(balance.total, collateral.totalUnrealizedPnl);
170
+ free[balance.coin] = coinFree;
171
+ used[balance.coin] = coinUsed;
172
+ total[balance.coin] = balance.total;
173
+ totalEquity = totalEquity.plus(balance.total);
174
+ availableToTrade = availableToTrade.plus(balance.availableAfterMaintenance ?? "0");
175
+ assets.push({
176
+ asset: balance.coin,
177
+ free: coinFree,
178
+ used: coinUsed,
179
+ total: balance.total,
180
+ walletBalance: coinWalletBalance,
181
+ usdValue: balance.total
66
182
  });
67
183
  }
68
- return positions;
184
+ const maintenanceMargin = add(summary.crossMaintenanceMarginUsed, summary.isolatedMaintenanceMarginUsed);
185
+ const totalEquityValue = totalEquity.toFixed();
186
+ const walletBalance = subtract(totalEquityValue, summary.totalUnrealizedPnl);
187
+ return {
188
+ accountType,
189
+ free,
190
+ used,
191
+ total,
192
+ availableToTrade: availableToTrade.toFixed(),
193
+ // Unified spot totals are the authoritative equity balances and already include perp PnL.
194
+ totalEquity: totalEquityValue,
195
+ walletBalance,
196
+ unrealizedPnl: summary.totalUnrealizedPnl,
197
+ initialMarginUsed: summary.totalMarginUsed,
198
+ maintenanceMargin,
199
+ marginRatio: unifiedMarginRatio(summary, spotCoinBalances),
200
+ assets,
201
+ timestamp: Date.now()
202
+ };
203
+ }
204
+ function unifiedMarginRatio(summary, spotCoinBalances) {
205
+ let maximumRatio = new BigNumber(0);
206
+ for (const [token, collateral] of summary.byCollateralToken) {
207
+ const spotBalance = [...spotCoinBalances.values()].find((balance) => balance.token === token);
208
+ const available = new BigNumber(spotBalance?.total ?? "0").minus(collateral.isolatedMarginUsed);
209
+ maximumRatio = BigNumber.max(maximumRatio, ratio(collateral.crossMaintenanceMarginUsed, available.toFixed()));
210
+ }
211
+ return maximumRatio.toFixed(4);
212
+ }
213
+ function isolatedMaintenanceMargin(dexState, assetPosition) {
214
+ const position = assetPosition.position;
215
+ if (position.leverage.type !== "isolated" || position.maxLeverage <= 0) {
216
+ return "0";
217
+ }
218
+ const notional = new BigNumber(position.positionValue).abs();
219
+ const marginTiers = findMarginTiers(dexState, position.coin);
220
+ const firstMarginTier = marginTiers?.[0];
221
+ if (!marginTiers || !firstMarginTier || marginTiers.some((tier) => tier.maxLeverage <= 0)) {
222
+ return notional.div(position.maxLeverage).div(2).toFixed();
223
+ }
224
+ let activeTier = firstMarginTier;
225
+ let maintenanceDeduction = new BigNumber(0);
226
+ for (const nextTier of marginTiers.slice(1)) {
227
+ if (notional.lt(nextTier.lowerBound)) {
228
+ break;
229
+ }
230
+ const previousRate = maintenanceMarginRate(activeTier.maxLeverage);
231
+ const nextRate = maintenanceMarginRate(nextTier.maxLeverage);
232
+ maintenanceDeduction = maintenanceDeduction.plus(
233
+ new BigNumber(nextTier.lowerBound).times(nextRate.minus(previousRate))
234
+ );
235
+ activeTier = nextTier;
236
+ }
237
+ return notional.times(maintenanceMarginRate(activeTier.maxLeverage)).minus(maintenanceDeduction).toFixed();
238
+ }
239
+ function findMarginTiers(dexState, coin) {
240
+ if (!dexState.meta) {
241
+ return null;
242
+ }
243
+ const unprefixedCoin = dexState.dex && coin.startsWith(`${dexState.dex}:`) ? coin.slice(dexState.dex.length + 1) : coin;
244
+ const asset = dexState.meta.universe.find(
245
+ (candidate) => candidate.name === coin || candidate.name === unprefixedCoin
246
+ );
247
+ const marginTable = dexState.meta.marginTables.find(([id]) => id === asset?.marginTableId)?.[1];
248
+ return marginTable?.marginTiers.length ? marginTable.marginTiers : null;
249
+ }
250
+ function maintenanceMarginRate(maxLeverage) {
251
+ return new BigNumber(1).div(maxLeverage).div(2);
252
+ }
253
+ function add(left, right) {
254
+ return new BigNumber(left || "0").plus(right || "0").toFixed();
255
+ }
256
+ function subtract(left, right) {
257
+ return new BigNumber(left || "0").minus(right || "0").toFixed();
258
+ }
259
+ function sum(values) {
260
+ return values.reduce((total, value) => total.plus(value || "0"), new BigNumber(0)).toFixed();
261
+ }
262
+ function ratio(numerator, denominator) {
263
+ const numeratorValue = new BigNumber(numerator || "0");
264
+ const denominatorValue = new BigNumber(denominator || "0");
265
+ if (denominatorValue.gt(0)) {
266
+ return numeratorValue.div(denominatorValue).toFixed(4);
267
+ }
268
+ return numeratorValue.gt(0) ? FULL_UTILIZATION_RATIO : "0.0000";
69
269
  }
70
270
 
71
- export { mapBalance, mapPositions };
271
+ export { aggregateClearinghouseStates, mapBalance, mapPositions };