@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.
- package/README.md +105 -25
- package/dist/client/bybit/rest.js +8 -1
- package/dist/client/hyperliquid/rest.js +21 -1
- package/dist/client/lighter/rest.js +6 -12
- package/dist/client/okx/rest.js +5 -1
- package/dist/helper/lighter/helper.js +6 -2
- package/dist/index.d.ts +1112 -74
- package/dist/sdk.js +9 -2
- package/dist/tracker/balance-core.js +89 -0
- package/dist/tracker/balance.js +25 -0
- package/dist/tracker/basket-size.js +194 -0
- package/dist/tracker/binance/balance.js +11 -0
- package/dist/tracker/binance/mapper.js +87 -42
- package/dist/tracker/binance/tracker.js +29 -14
- package/dist/tracker/binance/types.js +0 -6
- package/dist/tracker/bybit/balance.js +14 -0
- package/dist/tracker/bybit/mapper.js +109 -52
- package/dist/tracker/bybit/tracker.js +242 -33
- package/dist/tracker/bybit/types.js +0 -7
- package/dist/tracker/hyperliquid/account-mode.js +14 -0
- package/dist/tracker/hyperliquid/balance.js +184 -0
- package/dist/tracker/hyperliquid/basket-size.js +93 -0
- package/dist/tracker/hyperliquid/coin-name.js +18 -0
- package/dist/tracker/hyperliquid/collateral.js +48 -0
- package/dist/tracker/hyperliquid/decimal.js +28 -0
- package/dist/tracker/hyperliquid/mapper.js +399 -176
- package/dist/tracker/hyperliquid/spot-pricing.js +106 -0
- package/dist/tracker/hyperliquid/tracker.js +237 -78
- package/dist/tracker/hyperliquid/types.js +1 -7
- package/dist/tracker/index.js +9 -7
- package/dist/tracker/lighter/balance.js +87 -0
- package/dist/tracker/lighter/basket-size.js +74 -0
- package/dist/tracker/lighter/mapper.js +324 -145
- package/dist/tracker/lighter/tracker.js +228 -117
- package/dist/tracker/lighter/types.js +1 -7
- package/dist/tracker/okx/balance.js +17 -0
- package/dist/tracker/okx/mapper.js +156 -58
- package/dist/tracker/okx/tracker.js +228 -29
- package/dist/tracker/okx/types.js +0 -8
- package/dist/tracker/types.js +0 -24
- package/package.json +7 -9
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import BigNumber from 'bignumber.js';
|
|
2
2
|
import { positionKey } from '../utils';
|
|
3
|
-
import { OkxAccountType } from './types';
|
|
4
3
|
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
6
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
const DECIMAL_PATTERN = /^\d+(\.\d+)?$/;
|
|
7
|
+
const SIGNED_DECIMAL_PATTERN = /^-?\d+(\.\d+)?$/;
|
|
8
|
+
function acctLvToAccountMode(acctLv) {
|
|
9
|
+
switch (acctLv) {
|
|
10
|
+
case "1":
|
|
11
|
+
return "okx:spot";
|
|
12
|
+
case "2":
|
|
13
|
+
return "okx:futures";
|
|
14
|
+
case "3":
|
|
15
|
+
return "okx:multiCurrencyMargin";
|
|
16
|
+
case "4":
|
|
17
|
+
return "okx:portfolioMargin";
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
__name(acctLvToAccountMode, "acctLvToAccountMode");
|
|
22
|
+
function toWithdrawable(rows, details) {
|
|
23
|
+
return {
|
|
24
|
+
total: withdrawableFromMaxWithdrawals(rows, details),
|
|
25
|
+
byCurrency: new Map(rows.map((row) => [
|
|
26
|
+
row.ccy,
|
|
27
|
+
row.maxWd
|
|
28
|
+
]))
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
__name(toWithdrawable, "toWithdrawable");
|
|
7
32
|
function mergeRawCoins(existing, incoming) {
|
|
8
33
|
const merged = new Map(existing);
|
|
9
34
|
for (const d of incoming) {
|
|
@@ -12,50 +37,33 @@ function mergeRawCoins(existing, incoming) {
|
|
|
12
37
|
return merged;
|
|
13
38
|
}
|
|
14
39
|
__name(mergeRawCoins, "mergeRawCoins");
|
|
15
|
-
function buildBalance(raw,
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
40
|
+
function buildBalance(raw, accountMode, withdrawable, timestamp) {
|
|
41
|
+
if (accountMode === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const perCollateral = toCollateralRows(raw.details ?? [], withdrawable.byCurrency);
|
|
45
|
+
switch (accountMode) {
|
|
46
|
+
case "okx:spot":
|
|
47
|
+
return {
|
|
48
|
+
accountMode,
|
|
49
|
+
...splitPoolCore(raw, withdrawable, perCollateral, timestamp)
|
|
50
|
+
};
|
|
51
|
+
case "okx:futures":
|
|
52
|
+
return {
|
|
53
|
+
accountMode,
|
|
54
|
+
...splitPoolCore(raw, withdrawable, perCollateral, timestamp)
|
|
55
|
+
};
|
|
56
|
+
case "okx:multiCurrencyMargin":
|
|
57
|
+
return {
|
|
58
|
+
accountMode,
|
|
59
|
+
...sharedPoolCore(raw, withdrawable, perCollateral, timestamp)
|
|
60
|
+
};
|
|
61
|
+
case "okx:portfolioMargin":
|
|
62
|
+
return {
|
|
63
|
+
accountMode,
|
|
64
|
+
...sharedPoolCore(raw, withdrawable, perCollateral, timestamp)
|
|
65
|
+
};
|
|
42
66
|
}
|
|
43
|
-
const availableToTrade = raw.availEq || raw.adjEq || "0";
|
|
44
|
-
return {
|
|
45
|
-
accountType,
|
|
46
|
-
free,
|
|
47
|
-
used,
|
|
48
|
-
total,
|
|
49
|
-
availableToTrade,
|
|
50
|
-
totalEquity: raw.totalEq ?? "0",
|
|
51
|
-
walletBalance: walletBalanceSum.toFixed(),
|
|
52
|
-
unrealizedPnl: raw.upl || "0",
|
|
53
|
-
initialMarginUsed: raw.imr || "0",
|
|
54
|
-
maintenanceMargin: raw.mmr || "0",
|
|
55
|
-
marginRatio: raw.mgnRatio || "0.0000",
|
|
56
|
-
assets,
|
|
57
|
-
timestamp: Date.now()
|
|
58
|
-
};
|
|
59
67
|
}
|
|
60
68
|
__name(buildBalance, "buildBalance");
|
|
61
69
|
function mapSinglePosition(p) {
|
|
@@ -106,20 +114,110 @@ function mapPositionUpdates(raw) {
|
|
|
106
114
|
return positions;
|
|
107
115
|
}
|
|
108
116
|
__name(mapPositionUpdates, "mapPositionUpdates");
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
function sharedPoolCore(raw, withdrawable, perCollateral, timestamp) {
|
|
118
|
+
return {
|
|
119
|
+
totalEquity: decimalOrNull(raw.totalEq),
|
|
120
|
+
withdrawableBalance: withdrawable.total,
|
|
121
|
+
availableMargin: availableMarginOf(raw),
|
|
122
|
+
poolsShared: true,
|
|
123
|
+
perCollateral,
|
|
124
|
+
timestamp
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
__name(sharedPoolCore, "sharedPoolCore");
|
|
128
|
+
function splitPoolCore(raw, withdrawable, perCollateral, timestamp) {
|
|
129
|
+
const scalarsDescribeTheAccount = atMostOneFundedPool(raw.details ?? []);
|
|
130
|
+
return {
|
|
131
|
+
totalEquity: scalarsDescribeTheAccount ? decimalOrNull(raw.totalEq) : null,
|
|
132
|
+
withdrawableBalance: scalarsDescribeTheAccount ? withdrawable.total : null,
|
|
133
|
+
availableMargin: scalarsDescribeTheAccount ? availableMarginOf(raw) : null,
|
|
134
|
+
poolsShared: false,
|
|
135
|
+
perCollateral,
|
|
136
|
+
timestamp
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
__name(splitPoolCore, "splitPoolCore");
|
|
140
|
+
function toCollateralRows(details, withdrawableByCurrency) {
|
|
141
|
+
const rows = [];
|
|
142
|
+
for (const detail of details) {
|
|
143
|
+
if (!isFunded(detail.eq)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
rows.push({
|
|
147
|
+
asset: detail.ccy,
|
|
148
|
+
equity: detail.eq,
|
|
149
|
+
availableMargin: decimalOrNull(detail.availEq),
|
|
150
|
+
withdrawable: decimalOrNull(withdrawableByCurrency.get(detail.ccy))
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return rows.sort(byCurrencyName);
|
|
154
|
+
}
|
|
155
|
+
__name(toCollateralRows, "toCollateralRows");
|
|
156
|
+
function atMostOneFundedPool(details) {
|
|
157
|
+
let fundedPools = 0;
|
|
158
|
+
for (const detail of details) {
|
|
159
|
+
if (holdsAnyBalance(detail.eq)) {
|
|
160
|
+
fundedPools += 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return fundedPools <= 1;
|
|
164
|
+
}
|
|
165
|
+
__name(atMostOneFundedPool, "atMostOneFundedPool");
|
|
166
|
+
function holdsAnyBalance(value) {
|
|
167
|
+
if (value === void 0 || !SIGNED_DECIMAL_PATTERN.test(value)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return !new BigNumber(value).isZero();
|
|
171
|
+
}
|
|
172
|
+
__name(holdsAnyBalance, "holdsAnyBalance");
|
|
173
|
+
function isFunded(value) {
|
|
174
|
+
return isDecimal(value) && !new BigNumber(value).isZero();
|
|
175
|
+
}
|
|
176
|
+
__name(isFunded, "isFunded");
|
|
177
|
+
function byCurrencyName(a, b) {
|
|
178
|
+
if (a.asset === b.asset) {
|
|
179
|
+
return 0;
|
|
121
180
|
}
|
|
181
|
+
return a.asset < b.asset ? -1 : 1;
|
|
182
|
+
}
|
|
183
|
+
__name(byCurrencyName, "byCurrencyName");
|
|
184
|
+
function decimalOrNull(value) {
|
|
185
|
+
return isDecimal(value) ? value : null;
|
|
186
|
+
}
|
|
187
|
+
__name(decimalOrNull, "decimalOrNull");
|
|
188
|
+
function withdrawableFromMaxWithdrawals(rows, details) {
|
|
189
|
+
if (rows.length === 0) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const withdrawable = rows.filter((row) => isFunded(row.maxWd));
|
|
193
|
+
if (withdrawable.length === 0) {
|
|
194
|
+
return "0";
|
|
195
|
+
}
|
|
196
|
+
const single = withdrawable.length === 1 ? withdrawable[0] : void 0;
|
|
197
|
+
if (single === void 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return toUsd(single.maxWd, details.get(single.ccy));
|
|
201
|
+
}
|
|
202
|
+
__name(withdrawableFromMaxWithdrawals, "withdrawableFromMaxWithdrawals");
|
|
203
|
+
function availableMarginOf(raw) {
|
|
204
|
+
return decimalOrNull(raw.availEq);
|
|
205
|
+
}
|
|
206
|
+
__name(availableMarginOf, "availableMarginOf");
|
|
207
|
+
function toUsd(amount, detail) {
|
|
208
|
+
if (detail === void 0 || !isDecimal(detail.eq) || !isDecimal(detail.eqUsd)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const equity = new BigNumber(detail.eq);
|
|
212
|
+
if (equity.isZero()) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return new BigNumber(amount).times(detail.eqUsd).div(equity).toFixed();
|
|
216
|
+
}
|
|
217
|
+
__name(toUsd, "toUsd");
|
|
218
|
+
function isDecimal(value) {
|
|
219
|
+
return value !== void 0 && DECIMAL_PATTERN.test(value);
|
|
122
220
|
}
|
|
123
|
-
__name(
|
|
221
|
+
__name(isDecimal, "isDecimal");
|
|
124
222
|
|
|
125
|
-
export {
|
|
223
|
+
export { acctLvToAccountMode, buildBalance, mapPositionUpdates, mapPositions, mapSinglePosition, mergeRawCoins, toWithdrawable };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createExchangeWs } from '../../client';
|
|
2
|
-
import { fetchLeverageInfos, fetchAccountConfig, fetchBalance, fetchPositions } from '../../client/okx/rest';
|
|
2
|
+
import { fetchLeverageInfos, fetchAccountConfig, fetchBalance, fetchPositions, fetchMaxWithdrawals } from '../../client/okx/rest';
|
|
3
3
|
import { BaseAccountTracker } from '../base';
|
|
4
|
-
import {
|
|
4
|
+
import { mapPositions, buildBalance, acctLvToAccountMode, mergeRawCoins, mapPositionUpdates, toWithdrawable } from './mapper';
|
|
5
5
|
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
7
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
const MIN_WITHDRAWABLE_REFRESH_MS = 2e3;
|
|
8
9
|
class OkxAccountTracker extends BaseAccountTracker {
|
|
9
10
|
static {
|
|
10
11
|
__name(this, "OkxAccountTracker");
|
|
@@ -15,22 +16,40 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
15
16
|
openListenerId = null;
|
|
16
17
|
rawCoins = /* @__PURE__ */ new Map();
|
|
17
18
|
lastBalance = null;
|
|
18
|
-
|
|
19
|
+
accountMode = null;
|
|
20
|
+
withdrawable = emptyWithdrawable();
|
|
21
|
+
lastWithdrawableReadAt = 0;
|
|
22
|
+
withdrawableRefreshTimer = null;
|
|
23
|
+
withdrawableReadInFlight = null;
|
|
24
|
+
withdrawableReadPending = false;
|
|
25
|
+
lifecycleVersion = 0;
|
|
19
26
|
trackedAssetInfos = /* @__PURE__ */ new Map();
|
|
20
27
|
constructor(config) {
|
|
21
28
|
super(config);
|
|
22
29
|
this.creds = config.credentials;
|
|
23
30
|
}
|
|
24
31
|
async snapshot() {
|
|
25
|
-
const { config, balance, positions } = await this.fetchSnapshotData();
|
|
26
|
-
const accountType = acctLvToAccountType(config.acctLv);
|
|
27
|
-
const rawCoins = mergeRawCoins(/* @__PURE__ */ new Map(), balance.details ?? []);
|
|
32
|
+
const { config, balance, positions, withdrawable } = await this.fetchSnapshotData();
|
|
28
33
|
return {
|
|
29
|
-
balance: buildBalance(balance,
|
|
34
|
+
balance: buildBalance(balance, acctLvToAccountMode(config.acctLv), withdrawable, Date.now()),
|
|
30
35
|
positions: Array.from(mapPositions(positions).values())
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
38
|
/**
|
|
39
|
+
* OKX sizes no basket in v1: the SDK reads no per-asset max leverage from it, and sizing off
|
|
40
|
+
* `availableMargin` alone would ship a number no venue field backs. The missing input is
|
|
41
|
+
* `GET /api/v5/public/position-tiers` → `maxLever`/`imr` per tier, which the client does not
|
|
42
|
+
* call — and it is NOT `GET /api/v5/account/position-tiers`, a different endpoint carrying no
|
|
43
|
+
* margin or leverage data at all.
|
|
44
|
+
*/
|
|
45
|
+
async maxBasketSize(_input) {
|
|
46
|
+
return unsizedBasket();
|
|
47
|
+
}
|
|
48
|
+
/** The REST path answers the same way — there is no size either way, not a slower one. */
|
|
49
|
+
async snapshotMaxBasketSize(_input) {
|
|
50
|
+
return unsizedBasket();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
34
53
|
* Stateless one-shot per-asset leverage read via `leverage-info`, which accepts up to 20 instIds
|
|
35
54
|
* per request (chunked in the client), so N assets cost ceil(N/20) calls. Cross margin only,
|
|
36
55
|
* matching the account-level assumption used everywhere else in this connector.
|
|
@@ -53,8 +72,10 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
53
72
|
return result;
|
|
54
73
|
}
|
|
55
74
|
async start() {
|
|
56
|
-
const { config, balance, positions } = await this.fetchSnapshotData();
|
|
57
|
-
this.
|
|
75
|
+
const { config, balance, positions, withdrawable } = await this.fetchSnapshotData();
|
|
76
|
+
this.accountMode = acctLvToAccountMode(config.acctLv);
|
|
77
|
+
this.withdrawable = withdrawable;
|
|
78
|
+
this.lastWithdrawableReadAt = Date.now();
|
|
58
79
|
this.handleInitialData(balance, positions);
|
|
59
80
|
this.ws = await createExchangeWs("okx", {
|
|
60
81
|
demo: this.demo,
|
|
@@ -67,6 +88,12 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
67
88
|
this.subscribe();
|
|
68
89
|
}
|
|
69
90
|
async cleanupConnections() {
|
|
91
|
+
this.lifecycleVersion += 1;
|
|
92
|
+
this.withdrawableReadPending = false;
|
|
93
|
+
if (this.withdrawableRefreshTimer) {
|
|
94
|
+
clearTimeout(this.withdrawableRefreshTimer);
|
|
95
|
+
this.withdrawableRefreshTimer = null;
|
|
96
|
+
}
|
|
70
97
|
if (this.messageListenerId) {
|
|
71
98
|
this.ws?.main.removeMessageListener(this.messageListenerId);
|
|
72
99
|
this.messageListenerId = null;
|
|
@@ -82,17 +109,41 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
82
109
|
return this.ws?.isConnected ?? false;
|
|
83
110
|
}
|
|
84
111
|
async fetchSnapshotData() {
|
|
85
|
-
const [config, balance, positions] = await Promise.all([
|
|
112
|
+
const [config, balance, positions, read] = await Promise.all([
|
|
86
113
|
fetchAccountConfig(this.creds, this.demo),
|
|
87
114
|
fetchBalance(this.creds, this.demo),
|
|
88
|
-
fetchPositions(this.creds, this.demo)
|
|
115
|
+
fetchPositions(this.creds, this.demo),
|
|
116
|
+
this.readMaxWithdrawals()
|
|
89
117
|
]);
|
|
118
|
+
const withdrawable = toWithdrawableOrNothing(read, mergeRawCoins(/* @__PURE__ */ new Map(), balance.details ?? []));
|
|
90
119
|
return {
|
|
91
120
|
config,
|
|
92
121
|
balance,
|
|
93
|
-
positions
|
|
122
|
+
positions,
|
|
123
|
+
withdrawable
|
|
94
124
|
};
|
|
95
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Read how much can leave the trading account, per currency, across everything it owns.
|
|
128
|
+
*
|
|
129
|
+
* Best-effort on purpose: this is a fourth signed call that a read-only key may be refused, and
|
|
130
|
+
* a refusal must cost the caller a `null` field rather than the whole balance.
|
|
131
|
+
*
|
|
132
|
+
* A refusal is reported as `failed` rather than as an empty row set, because only the caller
|
|
133
|
+
* knows what to do with it: the snapshot path has no earlier figure and reports nothing known,
|
|
134
|
+
* while the live path keeps the figure its last successful read published.
|
|
135
|
+
*/
|
|
136
|
+
async readMaxWithdrawals() {
|
|
137
|
+
return fetchMaxWithdrawals(this.creds, this.demo).then((rows) => ({
|
|
138
|
+
kind: "rows",
|
|
139
|
+
rows
|
|
140
|
+
})).catch((err) => {
|
|
141
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
142
|
+
return {
|
|
143
|
+
kind: "failed"
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
96
147
|
ensureAssetTracked(instId) {
|
|
97
148
|
if (this.trackedAssetInfos.has(instId)) return;
|
|
98
149
|
fetchLeverageInfos(this.creds, [
|
|
@@ -141,10 +192,7 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
141
192
|
handleInitialData(balance, positions) {
|
|
142
193
|
this.lastBalance = balance;
|
|
143
194
|
this.rawCoins = mergeRawCoins(/* @__PURE__ */ new Map(), balance.details ?? []);
|
|
144
|
-
|
|
145
|
-
if (built) this.onStateUpdate({
|
|
146
|
-
balance: built
|
|
147
|
-
});
|
|
195
|
+
this.emitBalance(balance);
|
|
148
196
|
this.onStateUpdate({
|
|
149
197
|
positions: mapPositions(positions)
|
|
150
198
|
});
|
|
@@ -196,24 +244,175 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
196
244
|
});
|
|
197
245
|
}
|
|
198
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Fold one account push into the whole account, then publish what that whole account says.
|
|
249
|
+
*
|
|
250
|
+
* OKX pushes only what moved: an update names the currencies whose figures changed and leaves
|
|
251
|
+
* every other currency out. So the rows are taken from the per-currency merge rather than from
|
|
252
|
+
* the push, and everything read off them — the pool count above all — sees every currency the
|
|
253
|
+
* account holds. A push naming one of two funded currencies would otherwise read as a
|
|
254
|
+
* single-pool account and publish scalars that describe neither pool.
|
|
255
|
+
*
|
|
256
|
+
* The account-level figures inherit on `??`, never on `||`, because ABSENT and BLANK say
|
|
257
|
+
* opposite things here. A field this push left out was not restated and its previous value still
|
|
258
|
+
* stands; a field present as `''` is OKX withdrawing the figure, which it does per account mode —
|
|
259
|
+
* `availableMarginOf` records the mode that blanks `availEq`. Inheriting a blank would republish
|
|
260
|
+
* a number the venue no longer states, and it would do it BEHIND `decimalOrNull`, whose whole job
|
|
261
|
+
* is to publish a blank as `null`: overwriting the blank here means that guard never sees it.
|
|
262
|
+
*/
|
|
199
263
|
handleBalanceUpdate(raw) {
|
|
200
264
|
this.rawCoins = mergeRawCoins(this.rawCoins, raw.details ?? []);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
265
|
+
const previous = this.lastBalance ?? nothingStated();
|
|
266
|
+
const merged = {
|
|
267
|
+
totalEq: raw.totalEq ?? previous.totalEq,
|
|
268
|
+
adjEq: raw.adjEq ?? previous.adjEq,
|
|
269
|
+
availEq: raw.availEq ?? previous.availEq,
|
|
270
|
+
mmr: raw.mmr ?? previous.mmr,
|
|
271
|
+
imr: raw.imr ?? previous.imr,
|
|
272
|
+
upl: raw.upl ?? previous.upl,
|
|
273
|
+
mgnRatio: raw.mgnRatio ?? previous.mgnRatio,
|
|
274
|
+
// The account's whole row set, which is what the pool count has to be counted over.
|
|
275
|
+
details: Array.from(this.rawCoins.values())
|
|
276
|
+
};
|
|
211
277
|
this.lastBalance = merged;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
278
|
+
this.emitBalance(merged);
|
|
279
|
+
this.refreshWithdrawableSoon();
|
|
280
|
+
}
|
|
281
|
+
/** Publish the balance built from the newest account payload and the cached transferable amounts. */
|
|
282
|
+
emitBalance(balance) {
|
|
283
|
+
const built = buildBalance(balance, this.accountMode, this.withdrawable, Date.now());
|
|
284
|
+
if (built) {
|
|
285
|
+
this.onStateUpdate({
|
|
286
|
+
balance: built
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Queue the next transferable-amount read, no sooner than the venue's own rate limit allows.
|
|
292
|
+
*
|
|
293
|
+
* Nothing waits on it: the account update is already published from the cached figure. One
|
|
294
|
+
* queued read covers every account tick that lands before it runs, which is what stops a burst
|
|
295
|
+
* of ticks becoming a burst of signed calls — and because a tick always leaves a read queued or
|
|
296
|
+
* marked pending, a moved figure is still picked up within one interval rather than dropped.
|
|
297
|
+
*/
|
|
298
|
+
refreshWithdrawableSoon() {
|
|
299
|
+
if (this.withdrawableReadInFlight !== null) {
|
|
300
|
+
this.withdrawableReadPending = true;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (this.withdrawableRefreshTimer !== null) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const waitMs = Math.max(0, MIN_WITHDRAWABLE_REFRESH_MS - (Date.now() - this.lastWithdrawableReadAt));
|
|
307
|
+
this.withdrawableRefreshTimer = setTimeout(() => {
|
|
308
|
+
this.withdrawableRefreshTimer = null;
|
|
309
|
+
this.lastWithdrawableReadAt = Date.now();
|
|
310
|
+
void this.refreshWithdrawableBalance();
|
|
311
|
+
}, waitMs);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Hold one transferable-amount read open at a time, and queue the ticks that arrive during it.
|
|
315
|
+
*
|
|
316
|
+
* Serialising the reads is what keeps the published figure the newest one: two overlapping reads
|
|
317
|
+
* can come back in either order, and the older answer landing last would publish an amount the
|
|
318
|
+
* venue has already moved past.
|
|
319
|
+
*/
|
|
320
|
+
refreshWithdrawableBalance() {
|
|
321
|
+
const lifecycleVersion = this.lifecycleVersion;
|
|
322
|
+
const read = this.performWithdrawableRefresh(lifecycleVersion).finally(() => {
|
|
323
|
+
if (this.withdrawableReadInFlight === read) {
|
|
324
|
+
this.withdrawableReadInFlight = null;
|
|
325
|
+
}
|
|
326
|
+
if (this.withdrawableReadPending && lifecycleVersion === this.lifecycleVersion) {
|
|
327
|
+
this.withdrawableReadPending = false;
|
|
328
|
+
this.refreshWithdrawableSoon();
|
|
329
|
+
}
|
|
215
330
|
});
|
|
331
|
+
this.withdrawableReadInFlight = read;
|
|
332
|
+
return read;
|
|
216
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Re-read the transferable amount behind the live path, then re-publish only if it moved.
|
|
336
|
+
*
|
|
337
|
+
* Fire-and-forget: the account update is already published with the previous figure, so a slow
|
|
338
|
+
* read delays the correction instead of blocking the balance. A read that FAILS changes nothing
|
|
339
|
+
* at all — the last good figure stays published, because a refusal says OKX did not answer, not
|
|
340
|
+
* that the account has nothing to transfer. The re-publish reads the newest account payload
|
|
341
|
+
* rather than the one this read started from, so an account update that landed meanwhile is not
|
|
342
|
+
* rolled back.
|
|
343
|
+
*/
|
|
344
|
+
async performWithdrawableRefresh(lifecycleVersion) {
|
|
345
|
+
try {
|
|
346
|
+
const read = await this.readMaxWithdrawals();
|
|
347
|
+
if (lifecycleVersion !== this.lifecycleVersion) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (read.kind === "failed") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const withdrawable = toWithdrawable(read.rows, this.rawCoins);
|
|
354
|
+
if (sameWithdrawable(withdrawable, this.withdrawable)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.withdrawable = withdrawable;
|
|
358
|
+
if (this.lastBalance) {
|
|
359
|
+
this.emitBalance(this.lastBalance);
|
|
360
|
+
}
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (lifecycleVersion === this.lifecycleVersion) {
|
|
363
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function toWithdrawableOrNothing(read, details) {
|
|
369
|
+
switch (read.kind) {
|
|
370
|
+
case "rows":
|
|
371
|
+
return toWithdrawable(read.rows, details);
|
|
372
|
+
case "failed":
|
|
373
|
+
return emptyWithdrawable();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
__name(toWithdrawableOrNothing, "toWithdrawableOrNothing");
|
|
377
|
+
function nothingStated() {
|
|
378
|
+
return {
|
|
379
|
+
totalEq: "",
|
|
380
|
+
adjEq: "",
|
|
381
|
+
availEq: "",
|
|
382
|
+
mmr: "",
|
|
383
|
+
imr: "",
|
|
384
|
+
upl: "",
|
|
385
|
+
mgnRatio: "",
|
|
386
|
+
details: []
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
__name(nothingStated, "nothingStated");
|
|
390
|
+
function emptyWithdrawable() {
|
|
391
|
+
return {
|
|
392
|
+
total: null,
|
|
393
|
+
byCurrency: /* @__PURE__ */ new Map()
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
__name(emptyWithdrawable, "emptyWithdrawable");
|
|
397
|
+
function sameWithdrawable(left, right) {
|
|
398
|
+
if (left.total !== right.total || left.byCurrency.size !== right.byCurrency.size) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
for (const [currency, amount] of left.byCurrency) {
|
|
402
|
+
if (right.byCurrency.get(currency) !== amount) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
__name(sameWithdrawable, "sameWithdrawable");
|
|
409
|
+
function unsizedBasket() {
|
|
410
|
+
return {
|
|
411
|
+
maxNotional: null,
|
|
412
|
+
legs: [],
|
|
413
|
+
bindingConstraint: null
|
|
414
|
+
};
|
|
217
415
|
}
|
|
416
|
+
__name(unsizedBasket, "unsizedBasket");
|
|
218
417
|
|
|
219
418
|
export { OkxAccountTracker };
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
var OkxAccountType = /* @__PURE__ */ (function(OkxAccountType2) {
|
|
2
|
-
OkxAccountType2["SPOT"] = "OKX_SPOT";
|
|
3
|
-
OkxAccountType2["SINGLE_CURRENCY_MARGIN"] = "OKX_SINGLE";
|
|
4
|
-
OkxAccountType2["MULTI_CURRENCY_MARGIN"] = "OKX_MULTI";
|
|
5
|
-
OkxAccountType2["PORTFOLIO_MARGIN"] = "OKX_PORTFOLIO";
|
|
6
|
-
return OkxAccountType2;
|
|
7
|
-
})({});
|
|
8
1
|
|
|
9
|
-
export { OkxAccountType };
|
package/dist/tracker/types.js
CHANGED
|
@@ -1,25 +1 @@
|
|
|
1
|
-
import { BinanceAccountType } from './binance/types';
|
|
2
|
-
import { BybitAccountType } from './bybit/types';
|
|
3
|
-
import { HyperliquidAccountType } from './hyperliquid/types';
|
|
4
|
-
import { LighterAccountType } from './lighter/types';
|
|
5
|
-
import { OkxAccountType } from './okx/types';
|
|
6
1
|
|
|
7
|
-
const ACCOUNT_TYPE_LABELS = {
|
|
8
|
-
[BinanceAccountType.SINGLE_ASSET]: "Single-Asset Mode",
|
|
9
|
-
[BinanceAccountType.MULTI_ASSET]: "Multi-Asset Mode",
|
|
10
|
-
[BybitAccountType.ISOLATED_MARGIN]: "Isolated Margin",
|
|
11
|
-
[BybitAccountType.REGULAR_MARGIN]: "Cross Margin",
|
|
12
|
-
[BybitAccountType.PORTFOLIO_MARGIN]: "Portfolio Margin",
|
|
13
|
-
[HyperliquidAccountType.DEFAULT]: "Standard",
|
|
14
|
-
[HyperliquidAccountType.DISABLED]: "Standard",
|
|
15
|
-
[HyperliquidAccountType.PORTFOLIO_MARGIN]: "Portfolio Margin",
|
|
16
|
-
[HyperliquidAccountType.UNIFIED_ACCOUNT]: "Unified Account",
|
|
17
|
-
[LighterAccountType.CLASSIC]: "Classic",
|
|
18
|
-
[LighterAccountType.UNIFIED]: "Unified",
|
|
19
|
-
[OkxAccountType.SPOT]: "Spot Mode",
|
|
20
|
-
[OkxAccountType.SINGLE_CURRENCY_MARGIN]: "Single-Currency Margin",
|
|
21
|
-
[OkxAccountType.MULTI_CURRENCY_MARGIN]: "Multi-Currency Margin",
|
|
22
|
-
[OkxAccountType.PORTFOLIO_MARGIN]: "Portfolio Margin"
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export { ACCOUNT_TYPE_LABELS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/exchanges-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pear Protocol Exchanges SDK",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -22,22 +22,20 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"clean": "rm -rf dist .turbo",
|
|
25
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
-
"test": "vitest run",
|
|
27
|
-
"test:watch": "vitest"
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
28
26
|
},
|
|
29
27
|
"dependencies": {
|
|
30
|
-
"@pear-protocol/core-sdk": "^1.17.
|
|
31
|
-
"@pear-protocol/types": "^1.26.
|
|
28
|
+
"@pear-protocol/core-sdk": "^1.17.4",
|
|
29
|
+
"@pear-protocol/types": "^1.26.1",
|
|
32
30
|
"@pear-protocol/utils": "^0.5.1",
|
|
33
31
|
"bignumber.js": "^11.1.1",
|
|
34
32
|
"partysocket": "^1.0.3",
|
|
35
|
-
"viem": "^2.48.8"
|
|
33
|
+
"viem": "^2.48.8",
|
|
34
|
+
"zod": "4.1.12"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@backend/typescript-config": "workspace:*",
|
|
39
38
|
"tsup": "8.5.1",
|
|
40
|
-
"typescript": "5.9.3"
|
|
41
|
-
"vitest": "4.1.2"
|
|
39
|
+
"typescript": "5.9.3"
|
|
42
40
|
}
|
|
43
41
|
}
|