@pear-protocol/exchanges-sdk 0.6.6 → 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 (41) 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/helper/lighter/helper.js +6 -2
  7. package/dist/index.d.ts +1112 -74
  8. package/dist/sdk.js +9 -2
  9. package/dist/tracker/balance-core.js +89 -0
  10. package/dist/tracker/balance.js +25 -0
  11. package/dist/tracker/basket-size.js +194 -0
  12. package/dist/tracker/binance/balance.js +11 -0
  13. package/dist/tracker/binance/mapper.js +87 -42
  14. package/dist/tracker/binance/tracker.js +29 -14
  15. package/dist/tracker/binance/types.js +0 -6
  16. package/dist/tracker/bybit/balance.js +14 -0
  17. package/dist/tracker/bybit/mapper.js +109 -52
  18. package/dist/tracker/bybit/tracker.js +242 -33
  19. package/dist/tracker/bybit/types.js +0 -7
  20. package/dist/tracker/hyperliquid/account-mode.js +14 -0
  21. package/dist/tracker/hyperliquid/balance.js +184 -0
  22. package/dist/tracker/hyperliquid/basket-size.js +93 -0
  23. package/dist/tracker/hyperliquid/coin-name.js +18 -0
  24. package/dist/tracker/hyperliquid/collateral.js +48 -0
  25. package/dist/tracker/hyperliquid/decimal.js +28 -0
  26. package/dist/tracker/hyperliquid/mapper.js +399 -176
  27. package/dist/tracker/hyperliquid/spot-pricing.js +106 -0
  28. package/dist/tracker/hyperliquid/tracker.js +237 -78
  29. package/dist/tracker/hyperliquid/types.js +1 -7
  30. package/dist/tracker/index.js +9 -7
  31. package/dist/tracker/lighter/balance.js +87 -0
  32. package/dist/tracker/lighter/basket-size.js +74 -0
  33. package/dist/tracker/lighter/mapper.js +324 -145
  34. package/dist/tracker/lighter/tracker.js +228 -117
  35. package/dist/tracker/lighter/types.js +1 -7
  36. package/dist/tracker/okx/balance.js +17 -0
  37. package/dist/tracker/okx/mapper.js +156 -58
  38. package/dist/tracker/okx/tracker.js +228 -29
  39. package/dist/tracker/okx/types.js +0 -8
  40. package/dist/tracker/types.js +0 -24
  41. package/package.json +7 -9
@@ -0,0 +1,184 @@
1
+ import { z } from 'zod';
2
+ import { DecimalString, AccountBalanceCore, SignedDecimalString } from '../balance-core';
3
+
4
+ const HyperliquidDexState = z.object({
5
+ dex: z.string(),
6
+ collateralToken: z.number().int().nullable(),
7
+ accountValue: DecimalString,
8
+ crossNotional: DecimalString,
9
+ crossMaintenanceMargin: DecimalString,
10
+ withdrawable: DecimalString
11
+ });
12
+ const HyperliquidStandardBalance = AccountBalanceCore.extend({
13
+ accountMode: z.literal("hyperliquid:standard"),
14
+ /**
15
+ * Spot holdings valued in USD.
16
+ *
17
+ * Null when a NON-ZERO holding cannot be priced — a balance carries only a token index, so its
18
+ * mid is reached through a USDC-quoted pair or through one pair quoted in a token that has one,
19
+ * and some tokens are beyond both. Counting an unpriceable holding as zero would report a number
20
+ * the venue never stated.
21
+ */
22
+ spotEquity: DecimalString.nullable(),
23
+ /**
24
+ * The perps wallet as CASH: transfers + realized + funding, i.e. Σ `marginSummary.accountValue`
25
+ * with `unrealizedPnl` taken back out. This is the venue's own `Perps` figure, reproduced inside
26
+ * 0.08% on all nine Standard accounts captured 2026-08-18 and in both directions of PnL, and it
27
+ * is the perps term the venue's Total Equity adds — which is why `totalEquity` is exactly
28
+ * `spotEquity + perpsEquity + vaultEquity + stakedEquity` and stays that way.
29
+ *
30
+ * SIGNED, because an account whose unrealized profit exceeds its account value carries negative
31
+ * cash — measured at −230.12 on `0x5b43…92ab`. It replaced a separate `balance` field carrying
32
+ * this same quantity under a second name; the account value that field was subtracted FROM is
33
+ * `perpsEquity + unrealizedPnl`, and is published per dex in `perDexState[].accountValue`.
34
+ */
35
+ perpsEquity: SignedDecimalString,
36
+ vaultEquity: DecimalString,
37
+ /**
38
+ * The whole staking account — delegated HYPE plus undelegated — valued at its spot mid.
39
+ *
40
+ * Null when a NON-ZERO staking balance cannot be priced — the HYPE mid needs both `spotMeta` and
41
+ * `allMids`, so it is absent until each has arrived, and counting staked HYPE as zero would
42
+ * report a number the venue never stated. An empty staking account is worth zero at any price and
43
+ * still reads `0`.
44
+ */
45
+ stakedEquity: DecimalString.nullable(),
46
+ unrealizedPnl: SignedDecimalString,
47
+ maintenanceMargin: DecimalString,
48
+ /**
49
+ * The cross pool's own utilisation: Σ `crossMaintenanceMarginUsed` over Σ `crossMarginSummary
50
+ * .accountValue`, aggregated across every dex before the single division.
51
+ *
52
+ * Isolated legs are excluded for the same reason `maintenanceMargin` above excludes them: they
53
+ * carry their own margin and liquidate independently of the cross pool, so a field named for the
54
+ * cross pool reports the cross pool. Measured against the venue's own badge on
55
+ * `0x17c3…a868`: 109,558.03 / 7,510,025.45 = 1.46%, where that account's worst isolated leg reads
56
+ * 16.11% and its worst single dex 5.07%. Neither of those is the account's cross risk.
57
+ *
58
+ * THE VENUE IS INTERNALLY INCONSISTENT HERE, and both spellings below match it deliberately. Its
59
+ * ratio badge and its Cross Account Leverage divide by cross ACCOUNT VALUE — unrealized PnL
60
+ * included — while the `Perps` figure `perpsEquity` reproduces is that account value net of the
61
+ * same PnL. Neither is wrong; they are two different questions, and aligning one to the other
62
+ * would break its own verified match against the venue.
63
+ */
64
+ crossMarginRatio: DecimalString,
65
+ crossAccountLeverage: DecimalString,
66
+ /** Kept per dex — the aggregate hides HIP-3 positions entirely. */
67
+ perDexState: z.array(HyperliquidDexState)
68
+ });
69
+ const HyperliquidUnifiedBalance = AccountBalanceCore.extend({
70
+ accountMode: z.literal("hyperliquid:unified"),
71
+ /**
72
+ * The whole spot wallet at mid, since spot IS the collateral in this mode.
73
+ *
74
+ * Null when a NON-ZERO holding cannot be priced, for the reason Standard's `spotEquity` is: no
75
+ * route to USDC reaches every token, and counting an unpriceable holding as zero would report a
76
+ * number the venue never stated.
77
+ */
78
+ tradingEquity: DecimalString.nullable(),
79
+ vaultEquity: DecimalString,
80
+ /**
81
+ * The whole staking account — delegated HYPE plus undelegated — valued at its spot mid. Null when
82
+ * a NON-ZERO staking balance cannot be priced.
83
+ */
84
+ stakedEquity: DecimalString.nullable(),
85
+ /**
86
+ * Trading equity plus what sits outside trading. Null whenever either term is — one unknown term
87
+ * makes the sum unknown.
88
+ */
89
+ portfolioValue: DecimalString.nullable(),
90
+ unrealizedPnl: SignedDecimalString,
91
+ /** Cross maintenance only — isolated legs carry their own margin. */
92
+ perpsMaintenanceMargin: DecimalString,
93
+ unifiedAccountRatio: DecimalString,
94
+ /**
95
+ * Cross notional over the COLLATERAL backing it — the spot holdings of the dex collateral tokens,
96
+ * at their own mids — and NOT over `portfolioValue`.
97
+ *
98
+ * Staking, vault deposits and non-collateral spot coins are outside the denominator: the venue
99
+ * renders 0.28x on `0x8fec806c…`, where the portfolio-value spelling reads 0.0211 because 89% of
100
+ * that wallet sits in HYPE, staking and a vault.
101
+ *
102
+ * Null when a NON-ZERO collateral holding cannot be priced: a denominator nobody could state
103
+ * leaves the quotient unknown, where a zero would claim the account carries no leverage at all.
104
+ */
105
+ unifiedAccountLeverage: DecimalString.nullable(),
106
+ perDexState: z.array(HyperliquidDexState)
107
+ });
108
+ const HyperliquidPortfolioMarginCollateral = z.object({
109
+ asset: z.string(),
110
+ /** Net Balance: the venue's `total`, in the coin's own units, negative on a borrowed coin. */
111
+ equity: SignedDecimalString,
112
+ /** The per-pool floor of `activeAssetData.availableToTrade`; null where nothing was measured. */
113
+ availableMargin: DecimalString.nullable(),
114
+ /**
115
+ * Available Balance: `total − hold`, UNFLOORED and signed.
116
+ *
117
+ * In PM the `hold` is net of borrowing headroom, so this is free balance PLUS what the account
118
+ * may still borrow of the coin — which is why the venue labels it Available Balance and renders
119
+ * the 999,999.99999999 USDT0 case verbatim. Flooring the hold at zero would delete the borrow
120
+ * term and turn a borrowing account's spendable balance into its debt.
121
+ */
122
+ withdrawable: SignedDecimalString,
123
+ /**
124
+ * The coin's initial LTV, and `null` where the wire publishes no `ltv` key.
125
+ *
126
+ * `null` is the venue's `--`: the coin is not collateral and contributes nothing to the leverage
127
+ * denominator. `"0"` is the venue's `N/A`: a stablecoin at full weight. The two are opposite
128
+ * ends of the scale, so a consumer must not read the absent key as a zero weight.
129
+ */
130
+ ltv: DecimalString.nullable(),
131
+ /**
132
+ * `borrowed / borrow cap` for this coin, and `null` where the venue renders `--` — no borrow.
133
+ * NOT clipped at one: `0x0871deb3…` reports 1.0081 on USDT0.
134
+ */
135
+ borrowCapUsed: DecimalString.nullable()
136
+ });
137
+ const HyperliquidPortfolioMarginBalance = AccountBalanceCore.extend({
138
+ accountMode: z.literal("hyperliquid:portfolioMargin"),
139
+ /**
140
+ * One row per spot holding the venue lists — every balance where `total ≠ 0` OR `hold ≠ 0`, not
141
+ * one row per dex collateral token. In PM every spot holding is potentially collateral, and the
142
+ * collateral-token spelling hid $14.57M of HYPE behind a single USDC row on `0x0871deb3…`.
143
+ */
144
+ perCollateral: z.array(HyperliquidPortfolioMarginCollateral),
145
+ /** The whole spot wallet at mid. Null when a NON-ZERO holding has no route to USDC to price it. */
146
+ tradingEquity: DecimalString.nullable(),
147
+ vaultEquity: DecimalString,
148
+ /**
149
+ * The whole staking account — delegated HYPE plus undelegated — valued at its spot mid. Null when
150
+ * a NON-ZERO staking balance cannot be priced.
151
+ */
152
+ stakedEquity: DecimalString.nullable(),
153
+ /**
154
+ * The venue's Portfolio Value, which is Trading Equity under its other name: Σ spot × mid, with
155
+ * staking and vault deposits EXCLUDED. Both labels appear on the venue's own screens for the
156
+ * same quantity, and `totalEquity` is the one that adds the two outside balances back.
157
+ */
158
+ portfolioValue: DecimalString.nullable(),
159
+ unrealizedPnl: SignedDecimalString,
160
+ perpsMaintenanceMargin: DecimalString,
161
+ /**
162
+ * The venue's own `spotClearinghouseState.portfolioMarginRatio`, clipped at `1.0000` by the
163
+ * venue itself. Null when the wire states none.
164
+ *
165
+ * Read, never derived. The closed form behind it was solved to 0.4% on the seven debt-free
166
+ * accounts measured — `Σ crossMaintenanceMarginUsed` over capped collateral weighted
167
+ * `(1 + ltv) / 2`, less isolated margin — and does NOT extend to the seven carrying debt, where
168
+ * five candidate debt terms were each ruled out on at least three accounts.
169
+ */
170
+ portfolioMarginRatio: DecimalString.nullable(),
171
+ /**
172
+ * Cross notional over LTV-haircut collateral. Null when a holding that carries an `ltv` weight
173
+ * cannot be priced, since the denominator is then unknown rather than smaller.
174
+ */
175
+ portfolioAccountLeverage: DecimalString.nullable(),
176
+ /**
177
+ * Borrow Capacity Used: the WORST `tokenToPortfolioBorrowRatio` across coins, null when the
178
+ * account borrows nothing. Not clipped at one, and never the USDC entry alone.
179
+ */
180
+ borrowCapUsed: DecimalString.nullable(),
181
+ perDexState: z.array(HyperliquidDexState)
182
+ });
183
+
184
+ export { HyperliquidDexState, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance };
@@ -0,0 +1,93 @@
1
+ import BigNumber from 'bignumber.js';
2
+ import { splitDexCoin } from './coin-name';
3
+ import { toDecimal } from './decimal';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ const UNRESOLVED_POOL = "";
8
+ function buildCappedLegs(legs, markets) {
9
+ return legs.map((leg) => {
10
+ const market = markets.get(leg.asset) ?? null;
11
+ const leverage = effectiveLeverage(leg.leverage, market);
12
+ return {
13
+ asset: leg.asset,
14
+ pool: market?.pool ?? UNRESOLVED_POOL,
15
+ weight: leg.weight,
16
+ effectiveLeverage: leverage,
17
+ marginMode: leg.marginMode,
18
+ maxNotional: tierBoundaryNotional(market?.marginTiers ?? null, leverage)
19
+ };
20
+ });
21
+ }
22
+ __name(buildCappedLegs, "buildCappedLegs");
23
+ function buildPoolBudgets(legs, markets, assetData) {
24
+ const dataByPool = /* @__PURE__ */ new Map();
25
+ for (const leg of legs) {
26
+ const pool = markets.get(leg.asset)?.pool ?? null;
27
+ const data = assetData.get(leg.asset);
28
+ if (pool === null || data === void 0) {
29
+ continue;
30
+ }
31
+ const bucket = dataByPool.get(pool) ?? [];
32
+ bucket.push(data);
33
+ dataByPool.set(pool, bucket);
34
+ }
35
+ const budgets = {};
36
+ for (const [pool, data] of dataByPool) {
37
+ const floor = minimumAvailableToTrade(data);
38
+ if (floor !== null) {
39
+ budgets[pool] = floor;
40
+ }
41
+ }
42
+ return budgets;
43
+ }
44
+ __name(buildPoolBudgets, "buildPoolBudgets");
45
+ function effectiveLeverage(requestedLeverage, market) {
46
+ let leverage = toDecimal(requestedLeverage);
47
+ if (market?.venueMaxLeverage !== null && market?.venueMaxLeverage !== void 0) {
48
+ leverage = BigNumber.min(leverage, market.venueMaxLeverage);
49
+ }
50
+ if (market?.accountLeverage !== null && market?.accountLeverage !== void 0) {
51
+ leverage = BigNumber.min(leverage, market.accountLeverage);
52
+ }
53
+ return BigNumber.max(leverage, 1).toFixed();
54
+ }
55
+ __name(effectiveLeverage, "effectiveLeverage");
56
+ function tierBoundaryNotional(marginTiers, effectiveLeverage2) {
57
+ if (!marginTiers) {
58
+ return null;
59
+ }
60
+ const leverage = toDecimal(effectiveLeverage2);
61
+ const boundary = marginTiers.find((tier) => leverage.isGreaterThan(tier.maxLeverage));
62
+ return boundary ? boundary.lowerBound : null;
63
+ }
64
+ __name(tierBoundaryNotional, "tierBoundaryNotional");
65
+ function minimumAvailableToTrade(assetData) {
66
+ let floor = null;
67
+ for (const data of assetData) {
68
+ for (const available of data.availableToTrade ?? []) {
69
+ const value = toDecimal(available);
70
+ if (floor === null || value.isLessThan(floor)) {
71
+ floor = value;
72
+ }
73
+ }
74
+ }
75
+ return floor === null ? null : floor.toFixed();
76
+ }
77
+ __name(minimumAvailableToTrade, "minimumAvailableToTrade");
78
+ function findLegMarket(dexToMeta, tokenNames, coin, accountLeverage) {
79
+ const { dex, bareCoin } = splitDexCoin(coin);
80
+ const meta = dexToMeta.get(dex);
81
+ const asset = meta?.universe.find((candidate) => candidate.name === coin || candidate.name === bareCoin);
82
+ const marginTiers = meta?.marginTables.find(([id]) => id === asset?.marginTableId)?.[1].marginTiers;
83
+ const collateralToken = meta?.collateralToken;
84
+ return {
85
+ pool: collateralToken === void 0 ? null : tokenNames.get(collateralToken) ?? null,
86
+ venueMaxLeverage: asset?.maxLeverage ?? null,
87
+ accountLeverage,
88
+ marginTiers: marginTiers?.length ? marginTiers : null
89
+ };
90
+ }
91
+ __name(findLegMarket, "findLegMarket");
92
+
93
+ export { buildCappedLegs, buildPoolBudgets, effectiveLeverage, findLegMarket, minimumAvailableToTrade, tierBoundaryNotional };
@@ -0,0 +1,18 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ function splitDexCoin(coin) {
4
+ const separator = coin.indexOf(":");
5
+ if (separator === -1) {
6
+ return {
7
+ dex: "",
8
+ bareCoin: coin
9
+ };
10
+ }
11
+ return {
12
+ dex: coin.slice(0, separator),
13
+ bareCoin: coin.slice(separator + 1)
14
+ };
15
+ }
16
+ __name(splitDexCoin, "splitDexCoin");
17
+
18
+ export { splitDexCoin };
@@ -0,0 +1,48 @@
1
+ import { minimumAvailableToTrade } from './basket-size';
2
+ import { splitDexCoin } from './coin-name';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ function buildCollateralTokenNames(spotMeta) {
7
+ const names = /* @__PURE__ */ new Map();
8
+ for (const token of spotMeta.tokens ?? []) {
9
+ names.set(token.index, token.name);
10
+ }
11
+ return names;
12
+ }
13
+ __name(buildCollateralTokenNames, "buildCollateralTokenNames");
14
+ function buildAvailableMarginByToken(perDexState, assetData) {
15
+ const tokenByDex = collateralTokenByDex(perDexState);
16
+ const assetsByToken = /* @__PURE__ */ new Map();
17
+ for (const data of assetData) {
18
+ const token = tokenByDex.get(splitDexCoin(data.coin).dex);
19
+ if (token === void 0) {
20
+ continue;
21
+ }
22
+ const bucket = assetsByToken.get(token) ?? [];
23
+ bucket.push(data);
24
+ assetsByToken.set(token, bucket);
25
+ }
26
+ const floors = /* @__PURE__ */ new Map();
27
+ for (const [token, assets] of assetsByToken) {
28
+ const floor = minimumAvailableToTrade(assets);
29
+ if (floor !== null) {
30
+ floors.set(token, floor);
31
+ }
32
+ }
33
+ return floors;
34
+ }
35
+ __name(buildAvailableMarginByToken, "buildAvailableMarginByToken");
36
+ function collateralTokenByDex(perDexState) {
37
+ const tokenByDex = /* @__PURE__ */ new Map();
38
+ for (const entry of perDexState) {
39
+ if (entry.collateralToken === null) {
40
+ continue;
41
+ }
42
+ tokenByDex.set(entry.dex, entry.collateralToken);
43
+ }
44
+ return tokenByDex;
45
+ }
46
+ __name(collateralTokenByDex, "collateralTokenByDex");
47
+
48
+ export { buildAvailableMarginByToken, buildCollateralTokenNames };
@@ -0,0 +1,28 @@
1
+ import BigNumber from 'bignumber.js';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ const ZERO = new BigNumber(0);
6
+ const ONE = new BigNumber(1);
7
+ const WIRE_DECIMAL = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
8
+ function toPrice(value) {
9
+ const price = toDecimal(value);
10
+ if (!price.isGreaterThan(ZERO)) {
11
+ return null;
12
+ }
13
+ return price;
14
+ }
15
+ __name(toPrice, "toPrice");
16
+ function toDecimal(value) {
17
+ if (value === null || value === void 0) {
18
+ return ZERO;
19
+ }
20
+ const trimmed = value.trim();
21
+ if (!WIRE_DECIMAL.test(trimmed)) {
22
+ return ZERO;
23
+ }
24
+ return new BigNumber(trimmed);
25
+ }
26
+ __name(toDecimal, "toDecimal");
27
+
28
+ export { ONE, ZERO, toDecimal, toPrice };