@pear-protocol/exchanges-sdk 0.16.0 → 0.17.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 +32 -0
- package/dist/client/base-ws.js +18 -0
- package/dist/index.d.ts +91 -23
- package/dist/sdk.js +15 -1
- package/dist/syncer/base.js +7 -5
- package/dist/tracker/base.js +0 -2
- package/dist/tracker/binance/tracker.js +1 -1
- package/dist/tracker/bybit/tracker.js +1 -1
- package/dist/tracker/hyperliquid/tracker.js +172 -9
- package/dist/tracker/index.js +1 -2
- package/dist/tracker/lighter/tracker.js +5 -4
- package/dist/tracker/okx/tracker.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -103,6 +103,38 @@ Hyperliquid status requires the builder address and the minimum approved fee in
|
|
|
103
103
|
|
|
104
104
|
Historical Hyperliquid fill synchronization identifies fills by bare `tid` and requests them aggregated by time, so partial fills of one crossing order arrive as a single fill. Consumers that persisted composite `time:coin:tid` identifiers must account for the transition overlap. Hyperliquid serves only the 10,000 most recent fills; an account with a longer history imports from that boundary forward, and the snapshot-alignment synthetics cover the residual gap. Synchronization fails closed when the exchange reports a maker rebate: the current core external-fill contract accepts only non-negative fees, so the SDK will not silently turn a rebate into a cost or advance the synchronization watermark past it.
|
|
105
105
|
|
|
106
|
+
## Public Tracker (address only)
|
|
107
|
+
|
|
108
|
+
Hyperliquid answers account state for any address with no key, so an account the caller does not own, such as a vault's agent, can be tracked without `connect()`. `createPublicTracker` fetches no credentials and needs no trade account:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const tracker = exchanges.createPublicTracker({
|
|
112
|
+
connector: 'hyperliquid',
|
|
113
|
+
address: '0x...',
|
|
114
|
+
onError: (error) => console.warn(error),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await tracker.start(); // rejects if the venue reads or the socket fail
|
|
118
|
+
const offPositions = tracker.trackPosition((positions) => {
|
|
119
|
+
// Main-dex and HIP-3 positions together, e.g. `BTC` and `xyz:TSLA`
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const { balance, positions } = await tracker.snapshot(); // REST, every perp dex, no socket
|
|
123
|
+
|
|
124
|
+
offPositions();
|
|
125
|
+
await tracker.disconnect();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
It is the same `HyperliquidAccountTracker` the trade-account path builds, so live updates, reconnects, snapshots and cleanup behave identically. It only reads: nothing on it can trade.
|
|
129
|
+
|
|
130
|
+
A read that has not landed never looks like an empty account. `trackPosition` first fires once the first `allDexsClearinghouseState` frame arrives, and `isInitialized` stays `false` until then. When the socket drops, the tracker clears its live state (`isInitialized` turns `false`, `getPositions()` returns `[]`) and reports the outage to `onError` once, however many reconnect attempts fail; listeners are not sent an empty list. Once the socket reopens it resubscribes, and `isInitialized` turns `true` again with the first fresh frame. A subscription the venue refuses, and a frame that cannot be mapped, also reach `onError`.
|
|
131
|
+
|
|
132
|
+
To build one directly, `HyperliquidAccountTracker` now takes the address itself:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
new HyperliquidAccountTracker({ demo: false, address: '0x...', onError });
|
|
136
|
+
```
|
|
137
|
+
|
|
106
138
|
## Account Status
|
|
107
139
|
|
|
108
140
|
Use `status(connection)` to check whether the account is ready for trading through PEAR.
|
package/dist/client/base-ws.js
CHANGED
|
@@ -10,6 +10,7 @@ class BaseWs {
|
|
|
10
10
|
pingTimer = null;
|
|
11
11
|
messageListeners = /* @__PURE__ */ new Map();
|
|
12
12
|
openListeners = /* @__PURE__ */ new Map();
|
|
13
|
+
closeListeners = /* @__PURE__ */ new Map();
|
|
13
14
|
nextListenerId = 0;
|
|
14
15
|
async connect() {
|
|
15
16
|
if (this.ws) return;
|
|
@@ -42,6 +43,13 @@ class BaseWs {
|
|
|
42
43
|
});
|
|
43
44
|
ws.addEventListener("close", () => {
|
|
44
45
|
this.stopPing();
|
|
46
|
+
for (const listener of this.closeListeners.values()) {
|
|
47
|
+
try {
|
|
48
|
+
listener();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn("Error in ws close listener", error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
45
53
|
});
|
|
46
54
|
ws.addEventListener("error", () => {
|
|
47
55
|
});
|
|
@@ -57,6 +65,7 @@ class BaseWs {
|
|
|
57
65
|
}, "onOpen");
|
|
58
66
|
const onError = /* @__PURE__ */ __name(() => {
|
|
59
67
|
ws.removeEventListener("open", onOpen);
|
|
68
|
+
this.disconnect();
|
|
60
69
|
reject(new Error("WebSocket connection failed"));
|
|
61
70
|
}, "onError");
|
|
62
71
|
ws.addEventListener("open", onOpen, {
|
|
@@ -98,10 +107,19 @@ class BaseWs {
|
|
|
98
107
|
removeOpenListener(id) {
|
|
99
108
|
this.openListeners.delete(id);
|
|
100
109
|
}
|
|
110
|
+
addCloseListener(listener) {
|
|
111
|
+
const id = String(++this.nextListenerId);
|
|
112
|
+
this.closeListeners.set(id, listener);
|
|
113
|
+
return id;
|
|
114
|
+
}
|
|
115
|
+
removeCloseListener(id) {
|
|
116
|
+
this.closeListeners.delete(id);
|
|
117
|
+
}
|
|
101
118
|
destroy() {
|
|
102
119
|
this.disconnect();
|
|
103
120
|
this.messageListeners.clear();
|
|
104
121
|
this.openListeners.clear();
|
|
122
|
+
this.closeListeners.clear();
|
|
105
123
|
}
|
|
106
124
|
startPing() {
|
|
107
125
|
this.stopPing();
|
package/dist/index.d.ts
CHANGED
|
@@ -54,17 +54,28 @@ type ConnectOptions = {
|
|
|
54
54
|
integratorMinPerpsTakerFee?: number;
|
|
55
55
|
};
|
|
56
56
|
type ExchangeConnection = HttpTypes.ExchangeCredentialsResponse;
|
|
57
|
+
/**
|
|
58
|
+
* A read-only tracker for an address the caller need not own, such as a vault's agent. Only
|
|
59
|
+
* Hyperliquid publishes account state by address, so it is the only connector here.
|
|
60
|
+
*/
|
|
61
|
+
type PublicTrackerConfig = {
|
|
62
|
+
connector: 'hyperliquid';
|
|
63
|
+
address: `0x${string}`;
|
|
64
|
+
onError?: (error: Error) => void;
|
|
65
|
+
};
|
|
57
66
|
|
|
58
67
|
interface WsMessage {
|
|
59
68
|
[key: string]: unknown;
|
|
60
69
|
}
|
|
61
70
|
type MessageListener = (data: string) => void;
|
|
62
71
|
type OpenListener = () => void;
|
|
72
|
+
type CloseListener = () => void;
|
|
63
73
|
declare abstract class BaseWs {
|
|
64
74
|
protected ws: WebSocket | null;
|
|
65
75
|
protected pingTimer: ReturnType<typeof setInterval> | null;
|
|
66
76
|
private messageListeners;
|
|
67
77
|
private openListeners;
|
|
78
|
+
private closeListeners;
|
|
68
79
|
private nextListenerId;
|
|
69
80
|
protected abstract getWsUrl(): string;
|
|
70
81
|
protected abstract getPingIntervalMs(): number;
|
|
@@ -77,6 +88,8 @@ declare abstract class BaseWs {
|
|
|
77
88
|
removeMessageListener(id: string): void;
|
|
78
89
|
addOpenListener(listener: OpenListener): string;
|
|
79
90
|
removeOpenListener(id: string): void;
|
|
91
|
+
addCloseListener(listener: CloseListener): string;
|
|
92
|
+
removeCloseListener(id: string): void;
|
|
80
93
|
destroy(): void;
|
|
81
94
|
private startPing;
|
|
82
95
|
private stopPing;
|
|
@@ -240,11 +253,19 @@ type ExchangeWsByConnector = {
|
|
|
240
253
|
lighter: LighterExchangeWs;
|
|
241
254
|
lighter_rhc: LighterExchangeWs;
|
|
242
255
|
};
|
|
243
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Hyperliquid's account channels are public, so its socket needs no credentials; every other venue
|
|
258
|
+
* still requires them. The tuple keeps the check whole, so a connector not known to be Hyperliquid
|
|
259
|
+
* (a `Connector` union) still requires them too.
|
|
260
|
+
*/
|
|
261
|
+
type CreateExchangeWsConfig<E extends Connector = Connector> = {
|
|
244
262
|
demo: boolean;
|
|
263
|
+
} & ([E] extends ['hyperliquid'] ? {
|
|
264
|
+
credentials?: SupportedCredentials;
|
|
265
|
+
} : {
|
|
245
266
|
credentials: SupportedCredentials;
|
|
246
|
-
};
|
|
247
|
-
declare function createExchangeWs<E extends Connector>(exchange: E, config: CreateExchangeWsConfig): Promise<ExchangeWsByConnector[E]>;
|
|
267
|
+
});
|
|
268
|
+
declare function createExchangeWs<E extends Connector>(exchange: E, config: CreateExchangeWsConfig<E>): Promise<ExchangeWsByConnector[E]>;
|
|
248
269
|
|
|
249
270
|
interface HyperliquidHelper {
|
|
250
271
|
checkAccountActivation(userAddress: `0x${string}`): Promise<boolean>;
|
|
@@ -1780,9 +1801,12 @@ interface AccountSnapshot {
|
|
|
1780
1801
|
type AssetSnapshotMap = Record<string, TrackedAssetInfo | null>;
|
|
1781
1802
|
|
|
1782
1803
|
type ErrorHandler = (error: Error) => void;
|
|
1804
|
+
/**
|
|
1805
|
+
* What every tracker takes. Credentials are not part of it: a tracker that reads private venue
|
|
1806
|
+
* channels declares them in its own config, while Hyperliquid reads public account state by address.
|
|
1807
|
+
*/
|
|
1783
1808
|
type AccountTrackerConfig = {
|
|
1784
1809
|
demo: boolean;
|
|
1785
|
-
credentials: SupportedCredentials;
|
|
1786
1810
|
onError?: ErrorHandler;
|
|
1787
1811
|
};
|
|
1788
1812
|
interface AccountTracker {
|
|
@@ -1803,7 +1827,6 @@ interface AccountTracker {
|
|
|
1803
1827
|
declare abstract class BaseAccountTracker implements AccountTracker {
|
|
1804
1828
|
protected onError: ErrorHandler;
|
|
1805
1829
|
protected demo: boolean;
|
|
1806
|
-
protected credentials: SupportedCredentials;
|
|
1807
1830
|
private balance;
|
|
1808
1831
|
private balanceInitialized;
|
|
1809
1832
|
private positions;
|
|
@@ -2251,31 +2274,19 @@ declare const HyperliquidPortfolioMarginBalance: z.ZodObject<{
|
|
|
2251
2274
|
}, z.core.$strip>;
|
|
2252
2275
|
type HyperliquidPortfolioMarginBalance = z.infer<typeof HyperliquidPortfolioMarginBalance>;
|
|
2253
2276
|
|
|
2254
|
-
/**
|
|
2255
|
-
* Hyperliquid's two read paths: the live WebSocket tracker and the one-shot REST snapshot.
|
|
2256
|
-
*
|
|
2257
|
-
* Both fill the same `HyperliquidBalanceContext` and hand it to the same mapper, which is what
|
|
2258
|
-
* makes "same account state, same balance" true across two different transports. The tracker takes
|
|
2259
|
-
* every CHANGING value off a channel — `allDexsClearinghouseState`, `spotState`, `webData3`,
|
|
2260
|
-
* `allMids`, `activeAssetData` — including vault equity, which rides `webData3`, save one:
|
|
2261
|
-
* Portfolio Margin's health factor and reserve rates ride no channel, so it polls them while the
|
|
2262
|
-
* account is in that mode. Its only other REST reads happen at `start()`: the static dex metadata,
|
|
2263
|
-
* and the staking position, which the venue publishes on no channel at all.
|
|
2264
|
-
*
|
|
2265
|
-
* The snapshot has no channels, so it reads the same figures over REST — vault equity from
|
|
2266
|
-
* `userVaultEquities`, which was measured equal to the `webData3` figure on ten live accounts.
|
|
2267
|
-
*/
|
|
2268
|
-
|
|
2269
2277
|
type HyperliquidAccountTrackerConfig = AccountTrackerConfig & {
|
|
2270
|
-
account
|
|
2271
|
-
|
|
2278
|
+
/** The account whose state is read. Hyperliquid answers for any address, so no credentials are needed. */
|
|
2279
|
+
address: `0x${string}`;
|
|
2272
2280
|
};
|
|
2273
2281
|
declare class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
2274
2282
|
private readonly address;
|
|
2275
2283
|
private ws;
|
|
2276
2284
|
private messageListenerId;
|
|
2277
2285
|
private openListenerId;
|
|
2286
|
+
private closeListenerId;
|
|
2278
2287
|
private lifecycleVersion;
|
|
2288
|
+
/** Whether the socket is down and the outage already reported, so its retries report nothing more. */
|
|
2289
|
+
private socketDropped;
|
|
2279
2290
|
private subscribedCoins;
|
|
2280
2291
|
private lastSummary;
|
|
2281
2292
|
private spotCoinBalances;
|
|
@@ -2296,6 +2307,17 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
2296
2307
|
private hasWebData3;
|
|
2297
2308
|
private vaultEquity;
|
|
2298
2309
|
private stakedHype;
|
|
2310
|
+
/** Whether a staking re-read is running, whether another was asked for meanwhile, and its retry. */
|
|
2311
|
+
private stakingReadRunning;
|
|
2312
|
+
private stakingReadRequested;
|
|
2313
|
+
private stakingRetryTimer;
|
|
2314
|
+
private stakingEventReadStarting;
|
|
2315
|
+
private stakingEventReadTimer;
|
|
2316
|
+
private stakingEventReadPending;
|
|
2317
|
+
/** Whether the latest staking summary reported at least one withdrawal still queued. */
|
|
2318
|
+
private hasQueuedWithdrawal;
|
|
2319
|
+
/** Advances on every stop, so a staking read fired before it is recognised and dropped. */
|
|
2320
|
+
private stakingReadGeneration;
|
|
2299
2321
|
private dexToMeta;
|
|
2300
2322
|
private trackedAssetData;
|
|
2301
2323
|
constructor(config: HyperliquidAccountTrackerConfig);
|
|
@@ -2324,6 +2346,15 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
2324
2346
|
/** Subscribes to the spot state variant required by the active account mode. */
|
|
2325
2347
|
private sendSpotStateSubscription;
|
|
2326
2348
|
private resubscribeAll;
|
|
2349
|
+
/**
|
|
2350
|
+
* Drops the live account state when the socket goes down, and reports the outage once.
|
|
2351
|
+
*
|
|
2352
|
+
* Without this, the last frame's positions would read as live for the whole outage, or for good
|
|
2353
|
+
* if the socket never returns. Nothing is broadcast, since an empty list would read as a flat
|
|
2354
|
+
* account; `isInitialized` turns false and the reopen resubscribes. partysocket fires `close` for
|
|
2355
|
+
* every failed reconnect attempt too, so only the first close of an outage reaches `onError`.
|
|
2356
|
+
*/
|
|
2357
|
+
private onSocketClosed;
|
|
2327
2358
|
private routeRaw;
|
|
2328
2359
|
private handle;
|
|
2329
2360
|
private loadPerpMetas;
|
|
@@ -2366,6 +2397,37 @@ declare class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
2366
2397
|
* one missed read says nothing about the account that the last good one did not.
|
|
2367
2398
|
*/
|
|
2368
2399
|
private refreshBorrowLendState;
|
|
2400
|
+
/**
|
|
2401
|
+
* Re-reads the staking account, one read at a time: a request made while a read runs is served by
|
|
2402
|
+
* a single read after it, however many arrive.
|
|
2403
|
+
*
|
|
2404
|
+
* That follow-up matters. A read already in flight may have left before the withdrawal it should
|
|
2405
|
+
* catch completed, so its answer can still hold that HYPE.
|
|
2406
|
+
*/
|
|
2407
|
+
private requestStakingRead;
|
|
2408
|
+
/**
|
|
2409
|
+
* Requests at most one staking read per interval from spot frames, preserving one trailing read
|
|
2410
|
+
* when more frames ask during the interval.
|
|
2411
|
+
*/
|
|
2412
|
+
private requestStakingReadFromSpotState;
|
|
2413
|
+
/** Starts one spot-triggered read, then opens the interval selected by its pending count. */
|
|
2414
|
+
private startStakingReadInterval;
|
|
2415
|
+
/**
|
|
2416
|
+
* Stops the staking re-reads; the cached figure stays, as it was true when last read.
|
|
2417
|
+
*
|
|
2418
|
+
* Every stop opens a new generation (a session ending, or a restart), so a read still in flight
|
|
2419
|
+
* from before it can never land.
|
|
2420
|
+
*/
|
|
2421
|
+
private stopStakingReads;
|
|
2422
|
+
/**
|
|
2423
|
+
* Reads the staking account once, then republishes the balance with it.
|
|
2424
|
+
*
|
|
2425
|
+
* A failed read is reported and retried after `STAKING_READ_RETRY_MS`, keeping the last figure in
|
|
2426
|
+
* place until one succeeds.
|
|
2427
|
+
*/
|
|
2428
|
+
private refreshStakingAccount;
|
|
2429
|
+
/** Retries a failed staking read once `STAKING_READ_RETRY_MS` has passed, unless the session stopped. */
|
|
2430
|
+
private scheduleStakingRetry;
|
|
2369
2431
|
/** The floor of `availableToTrade` over the tracked set; `null` when nothing is tracked. */
|
|
2370
2432
|
private availableMargin;
|
|
2371
2433
|
/** Clears both sides of the live balance join and the public account cache without emitting. */
|
|
@@ -2458,6 +2520,7 @@ declare class LighterAccountTracker extends BaseAccountTracker {
|
|
|
2458
2520
|
private readonly accountIdentifier;
|
|
2459
2521
|
private readonly apiKeyIndex;
|
|
2460
2522
|
private readonly apiPrivateKey;
|
|
2523
|
+
private readonly creds;
|
|
2461
2524
|
private ws;
|
|
2462
2525
|
private messageListenerId;
|
|
2463
2526
|
private openListenerId;
|
|
@@ -2822,6 +2885,11 @@ declare class ExchangesSDK {
|
|
|
2822
2885
|
*/
|
|
2823
2886
|
status(connection: ExchangeConnection, options?: ConnectOptions): Promise<AccountStatus>;
|
|
2824
2887
|
createTracker(connection: ExchangeConnection): AccountTracker;
|
|
2888
|
+
/**
|
|
2889
|
+
* A read-only tracker for any address, built without `connect()`: Hyperliquid answers account
|
|
2890
|
+
* state for an address with no credentials, so none are fetched and no trade account is needed.
|
|
2891
|
+
*/
|
|
2892
|
+
createPublicTracker(config: PublicTrackerConfig): AccountTracker;
|
|
2825
2893
|
snapshot(tradeAccountId: string): Promise<AccountSnapshot>;
|
|
2826
2894
|
snapshotAssets(tradeAccountId: string, coins: string[]): Promise<AssetSnapshotMap>;
|
|
2827
2895
|
createHelper<E extends ExchangeHelperExchange>(exchange: E): ExchangeHelperMap[E];
|
|
@@ -2830,4 +2898,4 @@ declare class ExchangesSDK {
|
|
|
2830
2898
|
private fetchCredentials;
|
|
2831
2899
|
}
|
|
2832
2900
|
|
|
2833
|
-
export { ACCOUNT_MODE_LABELS, ASSET_DECIMALS, AccountBalance, AccountBalanceCore, type AccountHealthStatus, AccountMode, type AccountPosition, type AccountSnapshot, type AccountStatus, type AccountSyncer, type AccountSyncerConfig, type AccountTierCallback, type AccountTracker, type AccountTrackerConfig, type AccountWarningReason, type ApiKey, type ApproveIntegratorParams, type ApproveIntegratorResult, AssetId, type AssetSnapshotMap, type BalanceCallback, BaseAccountSyncer, BaseAccountTracker, BaseWs, BinanceAccountSyncer, BinanceAccountTracker, type BinanceAccountTrackerConfig, BinanceExchangeWs, BinanceMultiAssetBalance, BinanceSingleAssetBalance, BinanceUserDataWs, type BinanceUserDataWsConfig, BinanceWsApi, type BinanceWsApiConfig, BybitAccountSyncer, BybitAccountTracker, type BybitAccountTrackerConfig, BybitExchangeWs, BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance, BybitWs, type BybitWsConfig, type CctpDepositAddressParams, type CctpDepositAddressResult, type CctpDepositHistorySettlement, type CctpDepositParams, type CctpDepositRegistrationSettlement, type CctpDepositResult, type CctpDepositSettlement, type ChangeAccountTierParams, type ChangeAccountTierResult, type CheckApiWalletParams, CollateralBalance, type ConnectOptions, type ConnectWalletParams, type ConnectWalletResult, type CreateAccountTrackerConfig, type CreateExchangeWsConfig, DecimalString, type EthereumDepositParams, type EthereumDepositResult, type ExchangeConnection, type ExchangeHelperExchange, type ExchangeHelperMap, type ExchangeState, ExchangeWs, type ExchangeWsByConnector, ExchangesSDK, type ExchangesSDKConfig, FUNXYZ_DEPOSIT_MINIMUMS_USD, FUNXYZ_ETHEREUM_MAINNET_CHAIN_ID, FUNXYZ_EVM_CHAIN_NAMES, type FastWithdrawParams, type FastWithdrawResult, type FillSyncOutcome, type FundingSyncOutcome, type FunxyzAccountState, type FunxyzAsset, type FunxyzDepositAddressParams, type FunxyzDepositAddressResult, type FunxyzDepositCredit, type FunxyzDepositParams, type FunxyzDepositResult, type FunxyzDepositSettlement, type FunxyzDepositSource, type FunxyzMarket, HyperliquidAccountSyncer, HyperliquidAccountTracker, type HyperliquidAccountTrackerConfig, HyperliquidBorrowLendReserve, HyperliquidDexState, HyperliquidExchangeWs, type HyperliquidHelper, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance, HyperliquidWs, type IntegratorApprovalRequirements, L2_ASSET_DECIMALS, LIGHTER_NATIVE_USDC_ADDRESSES, LIGHTER_QUOTE_ASSETS, LIGHTER_RHC_CHAIN_ID, LIGHTER_RHC_USDG_ADDRESS, LIGHTER_RHC_USDG_DECIMALS, LIGHTER_USDC_DECIMALS, LighterAccountSyncer, type LighterAccountSyncerConfig, type LighterAccountTier, LighterAccountTracker, type LighterAccountTrackerConfig, type LighterApplyReferralCodeParams, type LighterAuthCredentials, LighterClassicBalance, type LighterConnector, type LighterDeposit, type LighterDepositAddressParams, type LighterDepositAddressResult, type LighterDepositCredit, type LighterDepositHistoryParams, type LighterDepositNetwork, type LighterDepositPage, type LighterDepositParams, type LighterDepositProgress, type LighterDepositResult, type LighterDepositStatus, type LighterDepositWalletAdapter, LighterExchangeWs, type LighterHelper, type LighterLookupResult, type LighterOnboardingStatus, type LighterReferral, type LighterReferralTotals, type LighterReferralTradeStats, LighterRhcAssetId, type LighterRhcDepositAddressParams, type LighterRhcDepositAddressResult, type LighterRhcDepositParams, type LighterRhcDepositResult, type LighterRhcDepositWallet, type LighterRhcHelper, type LighterSharedHelper, LighterUnifiedBalance, type LighterUserReferrals, type LighterUserReferralsParams, type LighterWithdraw, type LighterWithdrawHistoryParams, type LighterWithdrawPage, type LighterWithdrawStatus, type LighterWithdrawType, LighterWs, type ListExternalFillsParams, type ListExternalFundingParams, type ListExternalPositionsParams, MAX_API_KEY_INDEX, MIN_LIGHTER_CCTP_DEPOSIT_USDC, MIN_LIGHTER_FAST_WITHDRAW_COLLATERAL, MIN_LIGHTER_RHC_DEPOSIT_USDG, MIN_USER_API_KEY_INDEX, MarginMode, type MessageListener, OkxAccountSyncer, OkxAccountTracker, type OkxAccountTrackerConfig, OkxExchangeWs, OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance, OkxWs, type OkxWsConfig, type OpenListener, type PositionsCallback, RouteType, Side, SignedDecimalString, type StateUpdate, type SupportedCredentials, type SyncerThrottle, type TrackedAssetCallback, type TrackedAssetInfo, type WalletClientWithAccount, type WithdrawParams, type WithdrawResult, type WsMessage, createAccountSyncer, createAccountTracker, createExchangeWs, createHyperliquidHelper, createLighterHelper, createLighterRhcHelper, findAvailableApiKeyIndex, getAssetDecimals, getLighterNativeUsdcAddress, listFunxyzTransferableChains, readFunxyzChainSource, readFunxyzMinimumUsd, retrieveLighterAuthToken, usdcAbi };
|
|
2901
|
+
export { ACCOUNT_MODE_LABELS, ASSET_DECIMALS, AccountBalance, AccountBalanceCore, type AccountHealthStatus, AccountMode, type AccountPosition, type AccountSnapshot, type AccountStatus, type AccountSyncer, type AccountSyncerConfig, type AccountTierCallback, type AccountTracker, type AccountTrackerConfig, type AccountWarningReason, type ApiKey, type ApproveIntegratorParams, type ApproveIntegratorResult, AssetId, type AssetSnapshotMap, type BalanceCallback, BaseAccountSyncer, BaseAccountTracker, BaseWs, BinanceAccountSyncer, BinanceAccountTracker, type BinanceAccountTrackerConfig, BinanceExchangeWs, BinanceMultiAssetBalance, BinanceSingleAssetBalance, BinanceUserDataWs, type BinanceUserDataWsConfig, BinanceWsApi, type BinanceWsApiConfig, BybitAccountSyncer, BybitAccountTracker, type BybitAccountTrackerConfig, BybitExchangeWs, BybitIsolatedMarginBalance, BybitPortfolioMarginBalance, BybitRegularMarginBalance, BybitWs, type BybitWsConfig, type CctpDepositAddressParams, type CctpDepositAddressResult, type CctpDepositHistorySettlement, type CctpDepositParams, type CctpDepositRegistrationSettlement, type CctpDepositResult, type CctpDepositSettlement, type ChangeAccountTierParams, type ChangeAccountTierResult, type CheckApiWalletParams, type CloseListener, CollateralBalance, type ConnectOptions, type ConnectWalletParams, type ConnectWalletResult, type CreateAccountTrackerConfig, type CreateExchangeWsConfig, DecimalString, type EthereumDepositParams, type EthereumDepositResult, type ExchangeConnection, type ExchangeHelperExchange, type ExchangeHelperMap, type ExchangeState, ExchangeWs, type ExchangeWsByConnector, ExchangesSDK, type ExchangesSDKConfig, FUNXYZ_DEPOSIT_MINIMUMS_USD, FUNXYZ_ETHEREUM_MAINNET_CHAIN_ID, FUNXYZ_EVM_CHAIN_NAMES, type FastWithdrawParams, type FastWithdrawResult, type FillSyncOutcome, type FundingSyncOutcome, type FunxyzAccountState, type FunxyzAsset, type FunxyzDepositAddressParams, type FunxyzDepositAddressResult, type FunxyzDepositCredit, type FunxyzDepositParams, type FunxyzDepositResult, type FunxyzDepositSettlement, type FunxyzDepositSource, type FunxyzMarket, HyperliquidAccountSyncer, HyperliquidAccountTracker, type HyperliquidAccountTrackerConfig, HyperliquidBorrowLendReserve, HyperliquidDexState, HyperliquidExchangeWs, type HyperliquidHelper, HyperliquidPortfolioMarginBalance, HyperliquidPortfolioMarginCollateral, HyperliquidStandardBalance, HyperliquidUnifiedBalance, HyperliquidWs, type IntegratorApprovalRequirements, L2_ASSET_DECIMALS, LIGHTER_NATIVE_USDC_ADDRESSES, LIGHTER_QUOTE_ASSETS, LIGHTER_RHC_CHAIN_ID, LIGHTER_RHC_USDG_ADDRESS, LIGHTER_RHC_USDG_DECIMALS, LIGHTER_USDC_DECIMALS, LighterAccountSyncer, type LighterAccountSyncerConfig, type LighterAccountTier, LighterAccountTracker, type LighterAccountTrackerConfig, type LighterApplyReferralCodeParams, type LighterAuthCredentials, LighterClassicBalance, type LighterConnector, type LighterDeposit, type LighterDepositAddressParams, type LighterDepositAddressResult, type LighterDepositCredit, type LighterDepositHistoryParams, type LighterDepositNetwork, type LighterDepositPage, type LighterDepositParams, type LighterDepositProgress, type LighterDepositResult, type LighterDepositStatus, type LighterDepositWalletAdapter, LighterExchangeWs, type LighterHelper, type LighterLookupResult, type LighterOnboardingStatus, type LighterReferral, type LighterReferralTotals, type LighterReferralTradeStats, LighterRhcAssetId, type LighterRhcDepositAddressParams, type LighterRhcDepositAddressResult, type LighterRhcDepositParams, type LighterRhcDepositResult, type LighterRhcDepositWallet, type LighterRhcHelper, type LighterSharedHelper, LighterUnifiedBalance, type LighterUserReferrals, type LighterUserReferralsParams, type LighterWithdraw, type LighterWithdrawHistoryParams, type LighterWithdrawPage, type LighterWithdrawStatus, type LighterWithdrawType, LighterWs, type ListExternalFillsParams, type ListExternalFundingParams, type ListExternalPositionsParams, MAX_API_KEY_INDEX, MIN_LIGHTER_CCTP_DEPOSIT_USDC, MIN_LIGHTER_FAST_WITHDRAW_COLLATERAL, MIN_LIGHTER_RHC_DEPOSIT_USDG, MIN_USER_API_KEY_INDEX, MarginMode, type MessageListener, OkxAccountSyncer, OkxAccountTracker, type OkxAccountTrackerConfig, OkxExchangeWs, OkxFuturesBalance, OkxMultiCurrencyMarginBalance, OkxPortfolioMarginBalance, OkxSpotBalance, OkxWs, type OkxWsConfig, type OpenListener, type PositionsCallback, type PublicTrackerConfig, RouteType, Side, SignedDecimalString, type StateUpdate, type SupportedCredentials, type SyncerThrottle, type TrackedAssetCallback, type TrackedAssetInfo, type WalletClientWithAccount, type WithdrawParams, type WithdrawResult, type WsMessage, createAccountSyncer, createAccountTracker, createExchangeWs, createHyperliquidHelper, createLighterHelper, createLighterRhcHelper, findAvailableApiKeyIndex, getAssetDecimals, getLighterNativeUsdcAddress, listFunxyzTransferableChains, readFunxyzChainSource, readFunxyzMinimumUsd, retrieveLighterAuthToken, usdcAbi };
|
package/dist/sdk.js
CHANGED
|
@@ -2,7 +2,7 @@ import { configureProxy } from '@pear-protocol/utils';
|
|
|
2
2
|
import { createExchangeHelper } from './helper/factory';
|
|
3
3
|
import { checkAccountStatus } from './status';
|
|
4
4
|
import { createAccountSyncer } from './syncer';
|
|
5
|
-
import { createAccountTracker } from './tracker';
|
|
5
|
+
import { HyperliquidAccountTracker, createAccountTracker } from './tracker';
|
|
6
6
|
|
|
7
7
|
var __defProp = Object.defineProperty;
|
|
8
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
@@ -42,6 +42,20 @@ class ExchangesSDK {
|
|
|
42
42
|
createTracker(connection) {
|
|
43
43
|
return this.buildTracker(connection);
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* A read-only tracker for any address, built without `connect()`: Hyperliquid answers account
|
|
47
|
+
* state for an address with no credentials, so none are fetched and no trade account is needed.
|
|
48
|
+
*/
|
|
49
|
+
createPublicTracker(config) {
|
|
50
|
+
switch (config.connector) {
|
|
51
|
+
case "hyperliquid":
|
|
52
|
+
return new HyperliquidAccountTracker({
|
|
53
|
+
demo: this.demo,
|
|
54
|
+
onError: config.onError,
|
|
55
|
+
address: config.address
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
45
59
|
async snapshot(tradeAccountId) {
|
|
46
60
|
const connection = await this.fetchCredentials(tradeAccountId);
|
|
47
61
|
return this.buildTracker(connection).snapshot();
|
package/dist/syncer/base.js
CHANGED
|
@@ -23,14 +23,14 @@ class BaseAccountSyncer {
|
|
|
23
23
|
}
|
|
24
24
|
// Phase A: Load current Pear state (positions, fills, instruments, watermark).
|
|
25
25
|
async loadPearState() {
|
|
26
|
-
const
|
|
26
|
+
const watermark = await this.sdk.core.sync.fills.since({
|
|
27
|
+
overlapMs: OVERLAP_MS
|
|
28
|
+
});
|
|
29
|
+
const [{ positions: openPositions }, { fills: provisionalFills }, { instruments }] = await Promise.all([
|
|
27
30
|
this.sdk.core.positions.openLite(),
|
|
28
31
|
this.sdk.core.fills.provisionals(),
|
|
29
32
|
this.sdk.core.instruments.lite({
|
|
30
33
|
connector: this.connector
|
|
31
|
-
}),
|
|
32
|
-
this.sdk.core.sync.fills.since({
|
|
33
|
-
overlapMs: OVERLAP_MS
|
|
34
34
|
})
|
|
35
35
|
]);
|
|
36
36
|
const entryBySymbol = await this.loadEntryBySymbol(openPositions);
|
|
@@ -128,7 +128,9 @@ class BaseAccountSyncer {
|
|
|
128
128
|
},
|
|
129
129
|
...firstSyncStartedAt === void 0 ? {} : {
|
|
130
130
|
firstSyncStartedAt
|
|
131
|
-
}
|
|
131
|
+
},
|
|
132
|
+
// Core defers the create when any execution write postdates this server-minted token.
|
|
133
|
+
executionsUpdatedAtEpochMs: watermark.executionsUpdatedAtEpochMs
|
|
132
134
|
};
|
|
133
135
|
if (firstSyncTrim) {
|
|
134
136
|
console.log(`${this.connector}: first-sync trim \u2014 dropped ${firstSyncTrim.trimmedCount} pre-cutoff fills (cutoffTs=${firstSyncTrim.cutoffTs})`);
|
package/dist/tracker/base.js
CHANGED
|
@@ -6,7 +6,6 @@ class BaseAccountTracker {
|
|
|
6
6
|
}
|
|
7
7
|
onError;
|
|
8
8
|
demo;
|
|
9
|
-
credentials;
|
|
10
9
|
balance = null;
|
|
11
10
|
balanceInitialized = false;
|
|
12
11
|
positions = /* @__PURE__ */ new Map();
|
|
@@ -16,7 +15,6 @@ class BaseAccountTracker {
|
|
|
16
15
|
assetEntries = /* @__PURE__ */ new Map();
|
|
17
16
|
constructor(config) {
|
|
18
17
|
this.demo = config.demo;
|
|
19
|
-
this.credentials = config.credentials;
|
|
20
18
|
this.onError = (error) => {
|
|
21
19
|
console.warn("[AccountTracker] error:", error.message);
|
|
22
20
|
config.onError?.(error);
|
|
@@ -61,7 +61,7 @@ class BinanceAccountTracker extends BaseAccountTracker {
|
|
|
61
61
|
this.handlePositionRisk(positionRisk, false);
|
|
62
62
|
const ws = await createExchangeWs("binance", {
|
|
63
63
|
demo: this.demo,
|
|
64
|
-
credentials: this.
|
|
64
|
+
credentials: this.creds
|
|
65
65
|
});
|
|
66
66
|
this.userDataWs = ws.userData;
|
|
67
67
|
this.wsApiWs = ws.wsApi;
|
|
@@ -82,7 +82,7 @@ class BybitAccountTracker extends BaseAccountTracker {
|
|
|
82
82
|
this.handleInitialData(wallet ?? null, positions, toBybitAccountMode(accountInfo.marginMode));
|
|
83
83
|
this.ws = await createExchangeWs("bybit", {
|
|
84
84
|
demo: this.demo,
|
|
85
|
-
credentials: this.
|
|
85
|
+
credentials: this.creds
|
|
86
86
|
});
|
|
87
87
|
this.messageListenerId = this.ws.main.addMessageListener((raw) => this.routeRaw(raw));
|
|
88
88
|
this.openListenerId = this.ws.main.addOpenListener(() => {
|
|
@@ -17,6 +17,9 @@ const EMPTY_SPOT_META = {
|
|
|
17
17
|
universe: []
|
|
18
18
|
};
|
|
19
19
|
const BORROW_LEND_POLL_INTERVAL_MS = 3e4;
|
|
20
|
+
const STAKING_READ_RETRY_MS = 3e4;
|
|
21
|
+
const STAKING_QUEUED_READ_INTERVAL_MS = 6e4;
|
|
22
|
+
const STAKING_SPECULATIVE_READ_INTERVAL_MS = 3e5;
|
|
20
23
|
class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
21
24
|
static {
|
|
22
25
|
__name(this, "HyperliquidAccountTracker");
|
|
@@ -25,7 +28,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
25
28
|
ws = null;
|
|
26
29
|
messageListenerId = null;
|
|
27
30
|
openListenerId = null;
|
|
31
|
+
closeListenerId = null;
|
|
28
32
|
lifecycleVersion = 0;
|
|
33
|
+
/** Whether the socket is down and the outage already reported, so its retries report nothing more. */
|
|
34
|
+
socketDropped = false;
|
|
29
35
|
subscribedCoins = /* @__PURE__ */ new Set();
|
|
30
36
|
lastSummary = emptyClearinghouseSummary();
|
|
31
37
|
spotCoinBalances = /* @__PURE__ */ new Map();
|
|
@@ -46,11 +52,22 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
46
52
|
hasWebData3 = false;
|
|
47
53
|
vaultEquity = "0";
|
|
48
54
|
stakedHype = "0";
|
|
55
|
+
/** Whether a staking re-read is running, whether another was asked for meanwhile, and its retry. */
|
|
56
|
+
stakingReadRunning = false;
|
|
57
|
+
stakingReadRequested = false;
|
|
58
|
+
stakingRetryTimer = null;
|
|
59
|
+
stakingEventReadStarting = false;
|
|
60
|
+
stakingEventReadTimer = null;
|
|
61
|
+
stakingEventReadPending = false;
|
|
62
|
+
/** Whether the latest staking summary reported at least one withdrawal still queued. */
|
|
63
|
+
hasQueuedWithdrawal = false;
|
|
64
|
+
/** Advances on every stop, so a staking read fired before it is recognised and dropped. */
|
|
65
|
+
stakingReadGeneration = 0;
|
|
49
66
|
dexToMeta = /* @__PURE__ */ new Map();
|
|
50
67
|
trackedAssetData = /* @__PURE__ */ new Map();
|
|
51
68
|
constructor(config) {
|
|
52
69
|
super(config);
|
|
53
|
-
this.address = config.
|
|
70
|
+
this.address = config.address;
|
|
54
71
|
}
|
|
55
72
|
/**
|
|
56
73
|
* Reads account mode, spot prices, collateral balances, per-DEX state, staking, vaults and
|
|
@@ -89,7 +106,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
89
106
|
spotBalances: mapSpotBalances(spotState),
|
|
90
107
|
prices: SpotPrices.from(spotMeta, mids),
|
|
91
108
|
vaultEquity: sumRestVaultEquity(vaults),
|
|
92
|
-
stakedHype:
|
|
109
|
+
stakedHype: _stakingAccountHype(delegator),
|
|
93
110
|
availableMargin: minimumAvailableToTrade(assetData.values()),
|
|
94
111
|
collateralTokenNames: buildCollateralTokenNames(spotMeta),
|
|
95
112
|
availableMarginByToken: buildAvailableMarginByToken(summary.perDexState, assetData.values()),
|
|
@@ -127,9 +144,11 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
127
144
|
const user = this.addrLower();
|
|
128
145
|
this.destroyCurrentSocket();
|
|
129
146
|
this.stopBorrowLendPoll();
|
|
147
|
+
this.stopStakingReads();
|
|
130
148
|
this.subscribedCoins.clear();
|
|
131
149
|
this.resetLiveAccountState();
|
|
132
150
|
this.dexToMeta.clear();
|
|
151
|
+
this.socketDropped = false;
|
|
133
152
|
const [allMetas, perpDexs, spotMeta, abstraction, delegator] = await Promise.all([
|
|
134
153
|
fetchInfo({
|
|
135
154
|
type: "allPerpMetas"
|
|
@@ -146,10 +165,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
146
165
|
this.spotMeta = spotMeta;
|
|
147
166
|
this.collateralTokenNames = buildCollateralTokenNames(spotMeta);
|
|
148
167
|
this.accountType = resolveHyperliquidAccountType(abstraction);
|
|
149
|
-
this.stakedHype =
|
|
168
|
+
this.stakedHype = _stakingAccountHype(delegator);
|
|
169
|
+
this.hasQueuedWithdrawal = delegator.nPendingWithdrawals > 0;
|
|
150
170
|
const ws = (await createExchangeWs("hyperliquid", {
|
|
151
|
-
demo: this.demo
|
|
152
|
-
credentials: this.credentials
|
|
171
|
+
demo: this.demo
|
|
153
172
|
})).main;
|
|
154
173
|
if (lifecycleVersion !== this.lifecycleVersion) {
|
|
155
174
|
ws.destroy();
|
|
@@ -158,6 +177,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
158
177
|
this.ws = ws;
|
|
159
178
|
this.messageListenerId = this.ws.addMessageListener((raw) => this.routeRaw(raw));
|
|
160
179
|
this.openListenerId = this.ws.addOpenListener(() => this.resubscribeAll());
|
|
180
|
+
this.closeListenerId = this.ws.addCloseListener(() => this.onSocketClosed());
|
|
161
181
|
this.sendStaticSubs();
|
|
162
182
|
this.syncBorrowLendPoll();
|
|
163
183
|
const coinsToTrack = /* @__PURE__ */ new Set([
|
|
@@ -172,6 +192,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
172
192
|
this.lifecycleVersion += 1;
|
|
173
193
|
this.destroyCurrentSocket();
|
|
174
194
|
this.stopBorrowLendPoll();
|
|
195
|
+
this.stopStakingReads();
|
|
175
196
|
this.subscribedCoins.clear();
|
|
176
197
|
}
|
|
177
198
|
/** Detaches and destroys the active socket without changing the current lifecycle generation. */
|
|
@@ -184,6 +205,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
184
205
|
this.ws?.removeOpenListener(this.openListenerId);
|
|
185
206
|
this.openListenerId = null;
|
|
186
207
|
}
|
|
208
|
+
if (this.closeListenerId) {
|
|
209
|
+
this.ws?.removeCloseListener(this.closeListenerId);
|
|
210
|
+
this.closeListenerId = null;
|
|
211
|
+
}
|
|
187
212
|
this.ws?.destroy();
|
|
188
213
|
this.ws = null;
|
|
189
214
|
}
|
|
@@ -267,6 +292,7 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
267
292
|
}
|
|
268
293
|
resubscribeAll() {
|
|
269
294
|
this.resetLiveAccountState();
|
|
295
|
+
this.socketDropped = false;
|
|
270
296
|
this.sendStaticSubs();
|
|
271
297
|
const user = this.addrLower();
|
|
272
298
|
for (const coin of this.subscribedCoins) {
|
|
@@ -280,6 +306,22 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
280
306
|
});
|
|
281
307
|
}
|
|
282
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Drops the live account state when the socket goes down, and reports the outage once.
|
|
311
|
+
*
|
|
312
|
+
* Without this, the last frame's positions would read as live for the whole outage, or for good
|
|
313
|
+
* if the socket never returns. Nothing is broadcast, since an empty list would read as a flat
|
|
314
|
+
* account; `isInitialized` turns false and the reopen resubscribes. partysocket fires `close` for
|
|
315
|
+
* every failed reconnect attempt too, so only the first close of an outage reaches `onError`.
|
|
316
|
+
*/
|
|
317
|
+
onSocketClosed() {
|
|
318
|
+
if (this.socketDropped) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
this.socketDropped = true;
|
|
322
|
+
this.resetLiveAccountState();
|
|
323
|
+
this.onError(new Error("Hyperliquid WebSocket closed; account state is unavailable until it reconnects"));
|
|
324
|
+
}
|
|
283
325
|
routeRaw(raw) {
|
|
284
326
|
try {
|
|
285
327
|
const msg = JSON.parse(raw);
|
|
@@ -315,6 +357,9 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
315
357
|
data: msg.data
|
|
316
358
|
});
|
|
317
359
|
break;
|
|
360
|
+
case "error":
|
|
361
|
+
this.onError(new Error(`Hyperliquid WebSocket error: ${String(msg.data)}`));
|
|
362
|
+
break;
|
|
318
363
|
}
|
|
319
364
|
} catch (err) {
|
|
320
365
|
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -454,10 +499,15 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
454
499
|
onSpotState(data) {
|
|
455
500
|
const balances = data?.spotState?.balances;
|
|
456
501
|
if (!balances) return;
|
|
502
|
+
const hadSpotState = this.hasSpotState;
|
|
503
|
+
const previousHype = this.spotCoinBalances.get("HYPE")?.total;
|
|
457
504
|
this.spotCoinBalances = mapSpotBalances(data.spotState);
|
|
458
505
|
this.portfolioMarginRatio = data.spotState.portfolioMarginRatio ?? null;
|
|
459
506
|
this.borrowRatioByToken = mapBorrowRatios(data.spotState);
|
|
460
507
|
this.hasSpotState = true;
|
|
508
|
+
if (!toDecimal(this.stakedHype).isZero() && (!hadSpotState || spotHypeRose(previousHype, this.spotCoinBalances))) {
|
|
509
|
+
this.requestStakingReadFromSpotState();
|
|
510
|
+
}
|
|
461
511
|
const balance = this.buildBalance();
|
|
462
512
|
if (balance !== void 0) {
|
|
463
513
|
this.onStateUpdate({
|
|
@@ -551,6 +601,115 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
551
601
|
});
|
|
552
602
|
}
|
|
553
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Re-reads the staking account, one read at a time: a request made while a read runs is served by
|
|
606
|
+
* a single read after it, however many arrive.
|
|
607
|
+
*
|
|
608
|
+
* That follow-up matters. A read already in flight may have left before the withdrawal it should
|
|
609
|
+
* catch completed, so its answer can still hold that HYPE.
|
|
610
|
+
*/
|
|
611
|
+
async requestStakingRead() {
|
|
612
|
+
this.stakingReadRequested = true;
|
|
613
|
+
if (this.stakingReadRunning) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const generation = this.stakingReadGeneration;
|
|
617
|
+
this.stakingReadRunning = true;
|
|
618
|
+
while (this.stakingReadRequested && generation === this.stakingReadGeneration) {
|
|
619
|
+
this.stakingReadRequested = false;
|
|
620
|
+
await this.refreshStakingAccount(generation);
|
|
621
|
+
}
|
|
622
|
+
if (generation === this.stakingReadGeneration) {
|
|
623
|
+
this.stakingReadRunning = false;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Requests at most one staking read per interval from spot frames, preserving one trailing read
|
|
628
|
+
* when more frames ask during the interval.
|
|
629
|
+
*/
|
|
630
|
+
requestStakingReadFromSpotState() {
|
|
631
|
+
if (this.stakingEventReadStarting || this.stakingEventReadTimer !== null) {
|
|
632
|
+
this.stakingEventReadPending = true;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
this.stakingEventReadStarting = true;
|
|
636
|
+
void this.startStakingReadInterval(this.stakingReadGeneration);
|
|
637
|
+
}
|
|
638
|
+
/** Starts one spot-triggered read, then opens the interval selected by its pending count. */
|
|
639
|
+
async startStakingReadInterval(generation) {
|
|
640
|
+
await this.requestStakingRead();
|
|
641
|
+
if (generation !== this.stakingReadGeneration) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
this.stakingEventReadStarting = false;
|
|
645
|
+
const interval = this.hasQueuedWithdrawal ? STAKING_QUEUED_READ_INTERVAL_MS : STAKING_SPECULATIVE_READ_INTERVAL_MS;
|
|
646
|
+
this.stakingEventReadTimer = setTimeout(() => {
|
|
647
|
+
this.stakingEventReadTimer = null;
|
|
648
|
+
if (!this.stakingEventReadPending) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
this.stakingEventReadPending = false;
|
|
652
|
+
this.requestStakingReadFromSpotState();
|
|
653
|
+
}, interval);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Stops the staking re-reads; the cached figure stays, as it was true when last read.
|
|
657
|
+
*
|
|
658
|
+
* Every stop opens a new generation (a session ending, or a restart), so a read still in flight
|
|
659
|
+
* from before it can never land.
|
|
660
|
+
*/
|
|
661
|
+
stopStakingReads() {
|
|
662
|
+
if (this.stakingRetryTimer !== null) {
|
|
663
|
+
clearTimeout(this.stakingRetryTimer);
|
|
664
|
+
this.stakingRetryTimer = null;
|
|
665
|
+
}
|
|
666
|
+
if (this.stakingEventReadTimer !== null) {
|
|
667
|
+
clearTimeout(this.stakingEventReadTimer);
|
|
668
|
+
this.stakingEventReadTimer = null;
|
|
669
|
+
}
|
|
670
|
+
this.stakingEventReadStarting = false;
|
|
671
|
+
this.stakingEventReadPending = false;
|
|
672
|
+
this.stakingReadGeneration += 1;
|
|
673
|
+
this.stakingReadRunning = false;
|
|
674
|
+
this.stakingReadRequested = false;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Reads the staking account once, then republishes the balance with it.
|
|
678
|
+
*
|
|
679
|
+
* A failed read is reported and retried after `STAKING_READ_RETRY_MS`, keeping the last figure in
|
|
680
|
+
* place until one succeeds.
|
|
681
|
+
*/
|
|
682
|
+
async refreshStakingAccount(generation) {
|
|
683
|
+
let delegator;
|
|
684
|
+
try {
|
|
685
|
+
delegator = await fetchDelegatorSummary(this.addrLower(), this.demo);
|
|
686
|
+
} catch (err) {
|
|
687
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
688
|
+
this.scheduleStakingRetry(generation);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (generation !== this.stakingReadGeneration) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
this.stakedHype = _stakingAccountHype(delegator);
|
|
695
|
+
this.hasQueuedWithdrawal = delegator.nPendingWithdrawals > 0;
|
|
696
|
+
const balance = this.buildBalance();
|
|
697
|
+
if (balance !== void 0) {
|
|
698
|
+
this.onStateUpdate({
|
|
699
|
+
balance
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/** Retries a failed staking read once `STAKING_READ_RETRY_MS` has passed, unless the session stopped. */
|
|
704
|
+
scheduleStakingRetry(generation) {
|
|
705
|
+
if (generation !== this.stakingReadGeneration || this.stakingRetryTimer !== null) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
this.stakingRetryTimer = setTimeout(() => {
|
|
709
|
+
this.stakingRetryTimer = null;
|
|
710
|
+
void this.requestStakingRead();
|
|
711
|
+
}, STAKING_READ_RETRY_MS);
|
|
712
|
+
}
|
|
554
713
|
/** The floor of `availableToTrade` over the tracked set; `null` when nothing is tracked. */
|
|
555
714
|
availableMargin() {
|
|
556
715
|
return minimumAvailableToTrade(this.trackedAssetData.values());
|
|
@@ -575,6 +734,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
575
734
|
return result;
|
|
576
735
|
}
|
|
577
736
|
}
|
|
737
|
+
function _stakingAccountHype(delegator) {
|
|
738
|
+
return toDecimal(delegator.delegated).plus(toDecimal(delegator.undelegated)).plus(toDecimal(delegator.totalPendingWithdrawal)).toFixed();
|
|
739
|
+
}
|
|
740
|
+
__name(_stakingAccountHype, "_stakingAccountHype");
|
|
578
741
|
function buildTrackedAssetInfo(data) {
|
|
579
742
|
return {
|
|
580
743
|
coin: data.coin,
|
|
@@ -625,10 +788,10 @@ function mapBorrowRatios(spotState) {
|
|
|
625
788
|
return new Map(spotState.tokenToPortfolioBorrowRatio ?? []);
|
|
626
789
|
}
|
|
627
790
|
__name(mapBorrowRatios, "mapBorrowRatios");
|
|
628
|
-
function
|
|
629
|
-
return toDecimal(
|
|
791
|
+
function spotHypeRose(previousHype, balances) {
|
|
792
|
+
return toDecimal(balances.get("HYPE")?.total ?? "0").gt(toDecimal(previousHype ?? "0"));
|
|
630
793
|
}
|
|
631
|
-
__name(
|
|
794
|
+
__name(spotHypeRose, "spotHypeRose");
|
|
632
795
|
function sumRestVaultEquity(vaults) {
|
|
633
796
|
return vaults.reduce((total, vault) => total.plus(toDecimal(vault.equity)), ZERO).toFixed();
|
|
634
797
|
}
|
|
@@ -638,4 +801,4 @@ function sumLiveVaultEquity(perpDexStates) {
|
|
|
638
801
|
}
|
|
639
802
|
__name(sumLiveVaultEquity, "sumLiveVaultEquity");
|
|
640
803
|
|
|
641
|
-
export { HyperliquidAccountTracker };
|
|
804
|
+
export { HyperliquidAccountTracker, _stakingAccountHype };
|
package/dist/tracker/index.js
CHANGED
|
@@ -45,8 +45,7 @@ function createAccountTracker(exchange, config) {
|
|
|
45
45
|
return new HyperliquidAccountTracker({
|
|
46
46
|
demo: config.demo,
|
|
47
47
|
onError: config.onError,
|
|
48
|
-
|
|
49
|
-
credentials: config.credentials
|
|
48
|
+
address: config.account.exchangeIdentifier
|
|
50
49
|
});
|
|
51
50
|
case "lighter":
|
|
52
51
|
case "lighter_rhc":
|
|
@@ -38,6 +38,7 @@ class LighterAccountTracker extends BaseAccountTracker {
|
|
|
38
38
|
accountIdentifier;
|
|
39
39
|
apiKeyIndex;
|
|
40
40
|
apiPrivateKey;
|
|
41
|
+
creds;
|
|
41
42
|
ws = null;
|
|
42
43
|
messageListenerId = null;
|
|
43
44
|
openListenerId = null;
|
|
@@ -75,6 +76,7 @@ class LighterAccountTracker extends BaseAccountTracker {
|
|
|
75
76
|
this.accountIdentifier = config.account.exchangeIdentifier;
|
|
76
77
|
this.apiKeyIndex = credentials.api_key_index;
|
|
77
78
|
this.apiPrivateKey = credentials.api_private_key;
|
|
79
|
+
this.creds = credentials;
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
82
|
* The tier Lighter has the account on, and every later change to it.
|
|
@@ -177,7 +179,7 @@ class LighterAccountTracker extends BaseAccountTracker {
|
|
|
177
179
|
});
|
|
178
180
|
const ws = (await createExchangeWs(this.connector, {
|
|
179
181
|
demo: this.demo,
|
|
180
|
-
credentials: this.
|
|
182
|
+
credentials: this.creds
|
|
181
183
|
})).main;
|
|
182
184
|
if (lifecycleVersion !== this.lifecycleVersion) {
|
|
183
185
|
ws.destroy();
|
|
@@ -502,12 +504,11 @@ class LighterAccountTracker extends BaseAccountTracker {
|
|
|
502
504
|
return;
|
|
503
505
|
}
|
|
504
506
|
try {
|
|
505
|
-
const credentials = this.credentials;
|
|
506
507
|
const accountIndex = Number(this.accountIdentifier);
|
|
507
508
|
const authToken = await retrieveLighterAuthToken(this.sdk, this.connector, {
|
|
508
509
|
accountIndex,
|
|
509
|
-
apiKeyIndex:
|
|
510
|
-
apiPrivateKey:
|
|
510
|
+
apiKeyIndex: this.creds.api_key_index,
|
|
511
|
+
apiPrivateKey: this.creds.api_private_key
|
|
511
512
|
}, this.demo);
|
|
512
513
|
const limits = await fetchAccountLimits(accountIndex, authToken, this.connector, this.demo);
|
|
513
514
|
const accountTier = toLighterAccountTier(limits.user_tier);
|
|
@@ -65,7 +65,7 @@ class OkxAccountTracker extends BaseAccountTracker {
|
|
|
65
65
|
this.handleInitialData(balance, positions);
|
|
66
66
|
this.ws = await createExchangeWs("okx", {
|
|
67
67
|
demo: this.demo,
|
|
68
|
-
credentials: this.
|
|
68
|
+
credentials: this.creds
|
|
69
69
|
});
|
|
70
70
|
this.messageListenerId = this.ws.main.addMessageListener((raw) => this.routeRaw(raw));
|
|
71
71
|
this.openListenerId = this.ws.main.addOpenListener(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/exchanges-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Pear Protocol Exchanges SDK",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pear-protocol/core-sdk": "^2.
|
|
31
|
-
"@pear-protocol/types": "^2.
|
|
32
|
-
"@pear-protocol/utils": "^1.
|
|
30
|
+
"@pear-protocol/core-sdk": "^2.3.0",
|
|
31
|
+
"@pear-protocol/types": "^2.10.0",
|
|
32
|
+
"@pear-protocol/utils": "^1.2.0",
|
|
33
33
|
"bignumber.js": "^11.1.1",
|
|
34
34
|
"partysocket": "^1.0.3",
|
|
35
35
|
"viem": "^2.48.8",
|