@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.
- 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/index.d.ts +1110 -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 +5 -7
|
@@ -1,62 +1,58 @@
|
|
|
1
1
|
import BigNumber from 'bignumber.js';
|
|
2
2
|
import { positionKey } from '../utils';
|
|
3
|
-
import { BybitAccountType } from './types';
|
|
4
3
|
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
6
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const positionInitialMargin = c.totalPositionIM === "" ? "0" : c.totalPositionIM ?? "0";
|
|
17
|
-
const orderInitialMargin = c.totalOrderIM === "" ? "0" : c.totalOrderIM ?? "0";
|
|
18
|
-
const locked = c.locked ?? "0";
|
|
19
|
-
const equityBN = new BigNumber(equity);
|
|
20
|
-
const positionInitialMarginBN = new BigNumber(positionInitialMargin);
|
|
21
|
-
const orderInitialMarginBN = new BigNumber(orderInitialMargin);
|
|
22
|
-
const lockedBN = new BigNumber(locked);
|
|
23
|
-
if (equityBN.isZero() && new BigNumber(c.walletBalance ?? "0").isZero()) continue;
|
|
24
|
-
let coinFree;
|
|
25
|
-
let coinUsed;
|
|
26
|
-
if (isPortfolio) {
|
|
27
|
-
coinFree = equityBN.minus(lockedBN).toFixed();
|
|
28
|
-
coinUsed = "0";
|
|
29
|
-
} else {
|
|
30
|
-
coinFree = equityBN.minus(positionInitialMarginBN).minus(orderInitialMarginBN).minus(lockedBN).toFixed();
|
|
31
|
-
coinUsed = positionInitialMarginBN.plus(orderInitialMarginBN).toFixed();
|
|
32
|
-
}
|
|
33
|
-
free[c.coin] = coinFree;
|
|
34
|
-
used[c.coin] = coinUsed;
|
|
35
|
-
total[c.coin] = equity;
|
|
36
|
-
assets.push({
|
|
37
|
-
asset: c.coin,
|
|
38
|
-
free: coinFree,
|
|
39
|
-
used: coinUsed,
|
|
40
|
-
total: equity,
|
|
41
|
-
walletBalance: c.walletBalance ?? "0",
|
|
42
|
-
usdValue: c.usdValue ?? "0"
|
|
43
|
-
});
|
|
6
|
+
const DECIMAL_PATTERN = /^\d+(\.\d+)?$/;
|
|
7
|
+
function toBybitAccountMode(marginMode) {
|
|
8
|
+
switch (marginMode) {
|
|
9
|
+
case "ISOLATED_MARGIN":
|
|
10
|
+
return "bybit:isolatedMargin";
|
|
11
|
+
case "REGULAR_MARGIN":
|
|
12
|
+
return "bybit:regularMargin";
|
|
13
|
+
case "PORTFOLIO_MARGIN":
|
|
14
|
+
return "bybit:portfolioMargin";
|
|
44
15
|
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
__name(toBybitAccountMode, "toBybitAccountMode");
|
|
19
|
+
function toWithdrawable(raw, coins) {
|
|
20
|
+
const byCoin = new Map(Object.entries(raw?.availableWithdrawalMap ?? {}));
|
|
45
21
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
22
|
+
total: withdrawableTotal(byCoin, coins),
|
|
23
|
+
byCoin
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
__name(toWithdrawable, "toWithdrawable");
|
|
27
|
+
function buildBalance(raw, accountMode, withdrawable, timestamp) {
|
|
28
|
+
if (accountMode === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const core = {
|
|
32
|
+
totalEquity: decimalOrNull(raw.totalEquity),
|
|
33
|
+
withdrawableBalance: withdrawable.total,
|
|
34
|
+
availableMargin: decimalOrNull(raw.totalAvailableBalance),
|
|
35
|
+
poolsShared: true,
|
|
36
|
+
perCollateral: toCollateralRows(raw.coin ?? [], withdrawable.byCoin),
|
|
37
|
+
timestamp
|
|
59
38
|
};
|
|
39
|
+
switch (accountMode) {
|
|
40
|
+
case "bybit:isolatedMargin":
|
|
41
|
+
return {
|
|
42
|
+
accountMode,
|
|
43
|
+
...core
|
|
44
|
+
};
|
|
45
|
+
case "bybit:regularMargin":
|
|
46
|
+
return {
|
|
47
|
+
accountMode,
|
|
48
|
+
...core
|
|
49
|
+
};
|
|
50
|
+
case "bybit:portfolioMargin":
|
|
51
|
+
return {
|
|
52
|
+
accountMode,
|
|
53
|
+
...core
|
|
54
|
+
};
|
|
55
|
+
}
|
|
60
56
|
}
|
|
61
57
|
__name(buildBalance, "buildBalance");
|
|
62
58
|
function mergeRawCoins(existing, incoming) {
|
|
@@ -102,5 +98,66 @@ function mapPositionUpdates(raw) {
|
|
|
102
98
|
return positions;
|
|
103
99
|
}
|
|
104
100
|
__name(mapPositionUpdates, "mapPositionUpdates");
|
|
101
|
+
function toCollateralRows(coins, withdrawableByCoin) {
|
|
102
|
+
const rows = [];
|
|
103
|
+
for (const coin of coins) {
|
|
104
|
+
if (!isFunded(coin.equity)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
rows.push({
|
|
108
|
+
asset: coin.coin,
|
|
109
|
+
equity: coin.equity,
|
|
110
|
+
availableMargin: null,
|
|
111
|
+
withdrawable: decimalOrNull(withdrawableByCoin.get(coin.coin))
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return rows.sort(byCoinName);
|
|
115
|
+
}
|
|
116
|
+
__name(toCollateralRows, "toCollateralRows");
|
|
117
|
+
function withdrawableTotal(byCoin, coins) {
|
|
118
|
+
if (byCoin.size === 0) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const withdrawable = Array.from(byCoin).filter(([, amount]) => isFunded(amount));
|
|
122
|
+
if (withdrawable.length === 0) {
|
|
123
|
+
return "0";
|
|
124
|
+
}
|
|
125
|
+
const single = withdrawable.length === 1 ? withdrawable[0] : void 0;
|
|
126
|
+
if (single === void 0) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return toUsd(single[1], coins.find((coin) => coin.coin === single[0]));
|
|
130
|
+
}
|
|
131
|
+
__name(withdrawableTotal, "withdrawableTotal");
|
|
132
|
+
function toUsd(amount, coin) {
|
|
133
|
+
if (coin === void 0 || !isDecimal(coin.equity) || !isDecimal(coin.usdValue)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const equity = new BigNumber(coin.equity);
|
|
137
|
+
if (equity.isZero()) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return new BigNumber(amount).times(coin.usdValue).div(equity).toFixed();
|
|
141
|
+
}
|
|
142
|
+
__name(toUsd, "toUsd");
|
|
143
|
+
function isFunded(value) {
|
|
144
|
+
return isDecimal(value) && !new BigNumber(value).isZero();
|
|
145
|
+
}
|
|
146
|
+
__name(isFunded, "isFunded");
|
|
147
|
+
function isDecimal(value) {
|
|
148
|
+
return value !== void 0 && DECIMAL_PATTERN.test(value);
|
|
149
|
+
}
|
|
150
|
+
__name(isDecimal, "isDecimal");
|
|
151
|
+
function byCoinName(a, b) {
|
|
152
|
+
if (a.asset === b.asset) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
return a.asset < b.asset ? -1 : 1;
|
|
156
|
+
}
|
|
157
|
+
__name(byCoinName, "byCoinName");
|
|
158
|
+
function decimalOrNull(value) {
|
|
159
|
+
return isDecimal(value) ? value : null;
|
|
160
|
+
}
|
|
161
|
+
__name(decimalOrNull, "decimalOrNull");
|
|
105
162
|
|
|
106
|
-
export { buildBalance, mapPositionUpdates, mapPositions, mapSinglePosition, mergeRawCoins };
|
|
163
|
+
export { buildBalance, mapPositionUpdates, mapPositions, mapSinglePosition, mergeRawCoins, toBybitAccountMode, toWithdrawable };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createExchangeWs } from '../../client';
|
|
2
|
-
import { fetchPositions, fetchPositionLeverage, fetchWalletBalance, fetchAccountInfo } from '../../client/bybit/rest';
|
|
2
|
+
import { fetchPositions, fetchPositionLeverage, fetchWalletBalance, fetchAccountInfo, fetchTransferableAmount } from '../../client/bybit/rest';
|
|
3
3
|
import { BaseAccountTracker } from '../base';
|
|
4
|
-
import {
|
|
4
|
+
import { buildBalance, toBybitAccountMode, mapPositions, toWithdrawable, mergeRawCoins, mapPositionUpdates } 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 BybitAccountTracker extends BaseAccountTracker {
|
|
9
10
|
static {
|
|
10
11
|
__name(this, "BybitAccountTracker");
|
|
@@ -15,26 +16,40 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
15
16
|
openListenerId = null;
|
|
16
17
|
rawCoins = /* @__PURE__ */ new Map();
|
|
17
18
|
lastWallet = 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 { wallet, positions, accountInfo } = await this.fetchSnapshotData();
|
|
26
|
-
const
|
|
27
|
-
let balance = null;
|
|
28
|
-
if (wallet) {
|
|
29
|
-
const rawCoins = mergeRawCoins(/* @__PURE__ */ new Map(), wallet.coin ?? []);
|
|
30
|
-
balance = buildBalance(wallet, rawCoins, accountType);
|
|
31
|
-
}
|
|
32
|
+
const { wallet, positions, accountInfo, withdrawable } = await this.fetchSnapshotData();
|
|
33
|
+
const balance = wallet ? buildBalance(wallet, toBybitAccountMode(accountInfo.marginMode), withdrawable, Date.now()) : null;
|
|
32
34
|
return {
|
|
33
35
|
balance,
|
|
34
36
|
positions: Array.from(mapPositions(positions).values())
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
40
|
+
* Bybit sizes no basket in v1: the SDK reads no per-asset max leverage from it, and sizing off
|
|
41
|
+
* `availableMargin` alone would ship a number no venue field backs. The missing input is
|
|
42
|
+
* `/v5/position/list` → `leverage` (the account's own per-symbol setting), with
|
|
43
|
+
* `/v5/market/risk-limit` → `maxLeverage` behind it for the tier a larger size would fall into.
|
|
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
|
+
/**
|
|
38
53
|
* Stateless one-shot per-asset settings read. Bybit has no batch leverage endpoint, but
|
|
39
54
|
* `position/list` by settleCoin returns every open position (with leverage) in one call, so
|
|
40
55
|
* only requested symbols without an open position need a per-symbol fill-in read (`?symbol=`
|
|
@@ -74,8 +89,10 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
74
89
|
return result;
|
|
75
90
|
}
|
|
76
91
|
async start() {
|
|
77
|
-
const { wallet, positions, accountInfo } = await this.fetchSnapshotData();
|
|
78
|
-
this.
|
|
92
|
+
const { wallet, positions, accountInfo, withdrawable } = await this.fetchSnapshotData();
|
|
93
|
+
this.withdrawable = withdrawable;
|
|
94
|
+
this.lastWithdrawableReadAt = Date.now();
|
|
95
|
+
this.handleInitialData(wallet ?? null, positions, toBybitAccountMode(accountInfo.marginMode));
|
|
79
96
|
this.ws = await createExchangeWs("bybit", {
|
|
80
97
|
demo: this.demo,
|
|
81
98
|
credentials: this.credentials
|
|
@@ -87,6 +104,12 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
87
104
|
this.subscribe();
|
|
88
105
|
}
|
|
89
106
|
async cleanupConnections() {
|
|
107
|
+
this.lifecycleVersion += 1;
|
|
108
|
+
this.withdrawableReadPending = false;
|
|
109
|
+
if (this.withdrawableRefreshTimer) {
|
|
110
|
+
clearTimeout(this.withdrawableRefreshTimer);
|
|
111
|
+
this.withdrawableRefreshTimer = null;
|
|
112
|
+
}
|
|
90
113
|
if (this.messageListenerId) {
|
|
91
114
|
this.ws?.main.removeMessageListener(this.messageListenerId);
|
|
92
115
|
this.messageListenerId = null;
|
|
@@ -108,13 +131,47 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
108
131
|
fetchPositions(this.creds, "USDC", this.demo),
|
|
109
132
|
fetchAccountInfo(this.creds, this.demo)
|
|
110
133
|
]);
|
|
134
|
+
const withdrawable = toWithdrawableOrNothing(await this.readWithdrawable(wallet));
|
|
111
135
|
return {
|
|
112
136
|
wallet,
|
|
113
137
|
positions: [
|
|
114
138
|
positionsUsd,
|
|
115
139
|
positionsUsdc
|
|
116
140
|
].flat(),
|
|
117
|
-
accountInfo
|
|
141
|
+
accountInfo,
|
|
142
|
+
withdrawable
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Read how much can leave the trading account, for the coins the wallet payload lists.
|
|
147
|
+
*
|
|
148
|
+
* Best-effort on purpose: this is a second signed call that a read-only key may be refused, and
|
|
149
|
+
* a refusal must cost the caller a `null` field rather than the whole balance.
|
|
150
|
+
*
|
|
151
|
+
* A refusal is reported as `failed` rather than as an empty answer, because only the caller knows
|
|
152
|
+
* what to do with it: the snapshot path has no earlier figure and reports nothing known, while
|
|
153
|
+
* the live path keeps the figure its last successful read published.
|
|
154
|
+
*/
|
|
155
|
+
async readWithdrawable(wallet) {
|
|
156
|
+
const coins = wallet?.coin ?? [];
|
|
157
|
+
if (coins.length === 0) {
|
|
158
|
+
return {
|
|
159
|
+
kind: "amounts",
|
|
160
|
+
withdrawable: emptyWithdrawable()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const withdrawal = await fetchTransferableAmount(this.creds, coins.map((entry) => entry.coin), this.demo).catch((err) => {
|
|
164
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
165
|
+
return void 0;
|
|
166
|
+
});
|
|
167
|
+
if (withdrawal === void 0) {
|
|
168
|
+
return {
|
|
169
|
+
kind: "failed"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
kind: "amounts",
|
|
174
|
+
withdrawable: toWithdrawable(withdrawal, coins)
|
|
118
175
|
};
|
|
119
176
|
}
|
|
120
177
|
ensureAssetTracked(coin) {
|
|
@@ -146,15 +203,12 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
146
203
|
]
|
|
147
204
|
});
|
|
148
205
|
}
|
|
149
|
-
handleInitialData(wallet, positions,
|
|
150
|
-
this.
|
|
206
|
+
handleInitialData(wallet, positions, accountMode) {
|
|
207
|
+
this.accountMode = accountMode;
|
|
151
208
|
if (wallet) {
|
|
152
209
|
this.lastWallet = wallet;
|
|
153
210
|
this.rawCoins = mergeRawCoins(/* @__PURE__ */ new Map(), wallet.coin ?? []);
|
|
154
|
-
|
|
155
|
-
if (balance) this.onStateUpdate({
|
|
156
|
-
balance
|
|
157
|
-
});
|
|
211
|
+
this.emitBalance(wallet);
|
|
158
212
|
}
|
|
159
213
|
this.onStateUpdate({
|
|
160
214
|
positions: mapPositions(positions)
|
|
@@ -205,26 +259,181 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
205
259
|
}
|
|
206
260
|
}
|
|
207
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Fold one wallet push into the whole account, then publish what that whole account says.
|
|
264
|
+
*
|
|
265
|
+
* Bybit pushes only what moved: an update names the coins whose figures changed and leaves every
|
|
266
|
+
* other coin out. So the coin list is taken from the per-coin merge rather than from the push,
|
|
267
|
+
* and everything read off it sees every coin the account holds — the published rows, and the
|
|
268
|
+
* coin list the next transferable-amount read asks about. A push naming one of two funded coins
|
|
269
|
+
* would otherwise drop the other's row and let its per-coin maximum answer for the account.
|
|
270
|
+
*
|
|
271
|
+
* The account-level figures inherit on `??`, never on `||`, because ABSENT and BLANK say
|
|
272
|
+
* opposite things here. A field this push left out was not restated and its previous value still
|
|
273
|
+
* stands; a field present as `''` is Bybit withdrawing the figure, which it does for
|
|
274
|
+
* `totalAvailableBalance` among others. Inheriting a blank would republish a number the venue no
|
|
275
|
+
* longer states, and it would do it BEHIND `decimalOrNull`, whose whole job is to publish a blank
|
|
276
|
+
* as `null`: overwriting the blank here means that guard never sees it.
|
|
277
|
+
*/
|
|
208
278
|
handleWalletUpdate(raw) {
|
|
209
279
|
this.rawCoins = mergeRawCoins(this.rawCoins, raw.coin ?? []);
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
280
|
+
const previous = this.lastWallet ?? nothingStated();
|
|
281
|
+
const merged = {
|
|
282
|
+
totalEquity: raw.totalEquity ?? previous.totalEquity,
|
|
283
|
+
totalWalletBalance: raw.totalWalletBalance ?? previous.totalWalletBalance,
|
|
284
|
+
totalMarginBalance: raw.totalMarginBalance ?? previous.totalMarginBalance,
|
|
285
|
+
totalAvailableBalance: raw.totalAvailableBalance ?? previous.totalAvailableBalance,
|
|
286
|
+
totalPerpUPL: raw.totalPerpUPL ?? previous.totalPerpUPL,
|
|
287
|
+
totalInitialMargin: raw.totalInitialMargin ?? previous.totalInitialMargin,
|
|
288
|
+
totalMaintenanceMargin: raw.totalMaintenanceMargin ?? previous.totalMaintenanceMargin,
|
|
289
|
+
accountMMRate: raw.accountMMRate ?? previous.accountMMRate,
|
|
290
|
+
// The account's whole coin list, which is what the rows and the next read both have to see.
|
|
291
|
+
coin: Array.from(this.rawCoins.values())
|
|
292
|
+
};
|
|
221
293
|
this.lastWallet = merged;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
294
|
+
this.emitBalance(merged);
|
|
295
|
+
this.refreshWithdrawableSoon();
|
|
296
|
+
}
|
|
297
|
+
/** Publish the balance built from the newest wallet payload and the cached transferable amounts. */
|
|
298
|
+
emitBalance(wallet) {
|
|
299
|
+
const balance = buildBalance(wallet, this.accountMode, this.withdrawable, Date.now());
|
|
300
|
+
if (balance) {
|
|
301
|
+
this.onStateUpdate({
|
|
302
|
+
balance
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Queue the next transferable-amount read, no sooner than the venue's own rate limit allows.
|
|
308
|
+
*
|
|
309
|
+
* Nothing waits on it: the wallet update is already published from the cached figure. One queued
|
|
310
|
+
* read covers every wallet tick that lands before it runs, which is what stops a burst of ticks
|
|
311
|
+
* becoming a burst of signed calls — and because a tick always leaves a read queued or marked
|
|
312
|
+
* pending, a moved figure is still picked up within one interval rather than dropped.
|
|
313
|
+
*/
|
|
314
|
+
refreshWithdrawableSoon() {
|
|
315
|
+
if (this.withdrawableReadInFlight !== null) {
|
|
316
|
+
this.withdrawableReadPending = true;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (this.withdrawableRefreshTimer !== null) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const waitMs = Math.max(0, MIN_WITHDRAWABLE_REFRESH_MS - (Date.now() - this.lastWithdrawableReadAt));
|
|
323
|
+
this.withdrawableRefreshTimer = setTimeout(() => {
|
|
324
|
+
this.withdrawableRefreshTimer = null;
|
|
325
|
+
this.lastWithdrawableReadAt = Date.now();
|
|
326
|
+
void this.refreshWithdrawableBalance();
|
|
327
|
+
}, waitMs);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Hold one transferable-amount read open at a time, and queue the ticks that arrive during it.
|
|
331
|
+
*
|
|
332
|
+
* Serialising the reads is what keeps the published figure the newest one: two overlapping reads
|
|
333
|
+
* can come back in either order, and the older answer landing last would publish an amount the
|
|
334
|
+
* venue has already moved past.
|
|
335
|
+
*/
|
|
336
|
+
refreshWithdrawableBalance() {
|
|
337
|
+
const lifecycleVersion = this.lifecycleVersion;
|
|
338
|
+
const read = this.performWithdrawableRefresh(lifecycleVersion).finally(() => {
|
|
339
|
+
if (this.withdrawableReadInFlight === read) {
|
|
340
|
+
this.withdrawableReadInFlight = null;
|
|
341
|
+
}
|
|
342
|
+
if (this.withdrawableReadPending && lifecycleVersion === this.lifecycleVersion) {
|
|
343
|
+
this.withdrawableReadPending = false;
|
|
344
|
+
this.refreshWithdrawableSoon();
|
|
345
|
+
}
|
|
225
346
|
});
|
|
347
|
+
this.withdrawableReadInFlight = read;
|
|
348
|
+
return read;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Re-read the transferable amount behind the live path, then re-publish only if it moved.
|
|
352
|
+
*
|
|
353
|
+
* Fire-and-forget: the wallet update is already published with the previous figure, so a slow
|
|
354
|
+
* read delays the correction instead of blocking the balance. A read that FAILS changes nothing
|
|
355
|
+
* at all — the last good figure stays published, because a refusal says Bybit did not answer,
|
|
356
|
+
* not that the account has nothing to transfer. Both the read and the re-publish take the NEWEST
|
|
357
|
+
* wallet rather than the one that queued them, so a wallet update that landed meanwhile is
|
|
358
|
+
* neither missed nor rolled back.
|
|
359
|
+
*/
|
|
360
|
+
async performWithdrawableRefresh(lifecycleVersion) {
|
|
361
|
+
try {
|
|
362
|
+
const wallet = this.lastWallet;
|
|
363
|
+
if (wallet === null) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const read = await this.readWithdrawable(wallet);
|
|
367
|
+
if (lifecycleVersion !== this.lifecycleVersion) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (read.kind === "failed") {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (sameWithdrawable(read.withdrawable, this.withdrawable)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
this.withdrawable = read.withdrawable;
|
|
377
|
+
if (this.lastWallet) {
|
|
378
|
+
this.emitBalance(this.lastWallet);
|
|
379
|
+
}
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (lifecycleVersion === this.lifecycleVersion) {
|
|
382
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
226
385
|
}
|
|
227
386
|
}
|
|
387
|
+
function toWithdrawableOrNothing(read) {
|
|
388
|
+
switch (read.kind) {
|
|
389
|
+
case "amounts":
|
|
390
|
+
return read.withdrawable;
|
|
391
|
+
case "failed":
|
|
392
|
+
return emptyWithdrawable();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
__name(toWithdrawableOrNothing, "toWithdrawableOrNothing");
|
|
396
|
+
function nothingStated() {
|
|
397
|
+
return {
|
|
398
|
+
totalEquity: "",
|
|
399
|
+
totalWalletBalance: "",
|
|
400
|
+
totalMarginBalance: "",
|
|
401
|
+
totalAvailableBalance: "",
|
|
402
|
+
totalPerpUPL: "",
|
|
403
|
+
totalInitialMargin: "",
|
|
404
|
+
totalMaintenanceMargin: "",
|
|
405
|
+
accountMMRate: "",
|
|
406
|
+
coin: []
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
__name(nothingStated, "nothingStated");
|
|
410
|
+
function emptyWithdrawable() {
|
|
411
|
+
return {
|
|
412
|
+
total: null,
|
|
413
|
+
byCoin: /* @__PURE__ */ new Map()
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
__name(emptyWithdrawable, "emptyWithdrawable");
|
|
417
|
+
function sameWithdrawable(left, right) {
|
|
418
|
+
if (left.total !== right.total || left.byCoin.size !== right.byCoin.size) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
for (const [coin, amount] of left.byCoin) {
|
|
422
|
+
if (right.byCoin.get(coin) !== amount) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
__name(sameWithdrawable, "sameWithdrawable");
|
|
429
|
+
function unsizedBasket() {
|
|
430
|
+
return {
|
|
431
|
+
maxNotional: null,
|
|
432
|
+
legs: [],
|
|
433
|
+
bindingConstraint: null
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
__name(unsizedBasket, "unsizedBasket");
|
|
228
437
|
function toTrackedAssetInfo(coin, pos) {
|
|
229
438
|
return {
|
|
230
439
|
coin,
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
var BybitAccountType = /* @__PURE__ */ (function(BybitAccountType2) {
|
|
2
|
-
BybitAccountType2["ISOLATED_MARGIN"] = "ISOLATED_MARGIN";
|
|
3
|
-
BybitAccountType2["REGULAR_MARGIN"] = "REGULAR_MARGIN";
|
|
4
|
-
BybitAccountType2["PORTFOLIO_MARGIN"] = "PORTFOLIO_MARGIN";
|
|
5
|
-
return BybitAccountType2;
|
|
6
|
-
})({});
|
|
7
1
|
|
|
8
|
-
export { BybitAccountType };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HyperliquidAccountType } from './types';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
const HYPERLIQUID_ACCOUNT_TYPES = Object.values(HyperliquidAccountType);
|
|
6
|
+
function resolveHyperliquidAccountType(abstraction) {
|
|
7
|
+
if (!abstraction) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return HYPERLIQUID_ACCOUNT_TYPES.includes(abstraction) ? abstraction : null;
|
|
11
|
+
}
|
|
12
|
+
__name(resolveHyperliquidAccountType, "resolveHyperliquidAccountType");
|
|
13
|
+
|
|
14
|
+
export { resolveHyperliquidAccountType };
|