@lifi/perps-sdk-provider-lighter 30.0.0 → 31.0.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/dist/cjs/LighterProvider.d.ts.map +1 -1
- package/dist/cjs/LighterProvider.js +62 -54
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts +4 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +8 -7
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/availableToTrade.d.ts +2 -0
- package/dist/cjs/availableToTrade.d.ts.map +1 -1
- package/dist/cjs/availableToTrade.js +6 -7
- package/dist/cjs/availableToTrade.js.map +1 -1
- package/dist/cjs/types/pnl.d.ts.map +1 -1
- package/dist/cjs/types/ws.d.ts +5 -0
- package/dist/cjs/types/ws.d.ts.map +1 -1
- package/dist/cjs/utils/apiClient.d.ts +3 -2
- package/dist/cjs/utils/apiClient.d.ts.map +1 -1
- package/dist/cjs/utils/apiClient.js +5 -3
- package/dist/cjs/utils/apiClient.js.map +1 -1
- package/dist/cjs/utils/fetchDetailedAccount.d.ts +1 -1
- package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/cjs/utils/fetchDetailedAccount.js +2 -1
- package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -1
- package/dist/cjs/utils/mapFill.d.ts.map +1 -1
- package/dist/cjs/utils/mapFill.js +1 -3
- package/dist/cjs/utils/mapFill.js.map +1 -1
- package/dist/cjs/utils/mapPortfolioHistory.d.ts +1 -1
- package/dist/cjs/utils/mapPortfolioHistory.d.ts.map +1 -1
- package/dist/cjs/utils/mapPortfolioHistory.js +36 -21
- package/dist/cjs/utils/mapPortfolioHistory.js.map +1 -1
- package/dist/cjs/utils/mapPosition.d.ts +1 -0
- package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
- package/dist/cjs/utils/mapPosition.js +13 -1
- package/dist/cjs/utils/mapPosition.js.map +1 -1
- package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
- package/dist/cjs/utils/spotPrice.d.ts +5 -1
- package/dist/cjs/utils/spotPrice.d.ts.map +1 -1
- package/dist/cjs/utils/spotPrice.js +14 -1
- package/dist/cjs/utils/spotPrice.js.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.d.ts +3 -3
- package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.js +11 -3
- package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.d.ts +6 -0
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +120 -9
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +71 -61
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountSummary.d.ts +17 -3
- package/dist/esm/accountSummary.d.ts.map +1 -1
- package/dist/esm/accountSummary.js +18 -9
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/availableToTrade.d.ts +2 -0
- package/dist/esm/availableToTrade.d.ts.map +1 -1
- package/dist/esm/availableToTrade.js +4 -6
- package/dist/esm/availableToTrade.js.map +1 -1
- package/dist/esm/types/pnl.d.ts +2 -3
- package/dist/esm/types/pnl.d.ts.map +1 -1
- package/dist/esm/types/ws.d.ts +13 -0
- package/dist/esm/types/ws.d.ts.map +1 -1
- package/dist/esm/types/ws.js +0 -11
- package/dist/esm/types/ws.js.map +1 -1
- package/dist/esm/utils/apiClient.d.ts +9 -2
- package/dist/esm/utils/apiClient.d.ts.map +1 -1
- package/dist/esm/utils/apiClient.js +9 -2
- package/dist/esm/utils/apiClient.js.map +1 -1
- package/dist/esm/utils/fetchDetailedAccount.d.ts +1 -1
- package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/esm/utils/fetchDetailedAccount.js +2 -1
- package/dist/esm/utils/fetchDetailedAccount.js.map +1 -1
- package/dist/esm/utils/mapFill.d.ts.map +1 -1
- package/dist/esm/utils/mapFill.js +6 -10
- package/dist/esm/utils/mapFill.js.map +1 -1
- package/dist/esm/utils/mapPortfolioHistory.d.ts +3 -6
- package/dist/esm/utils/mapPortfolioHistory.d.ts.map +1 -1
- package/dist/esm/utils/mapPortfolioHistory.js +38 -26
- package/dist/esm/utils/mapPortfolioHistory.js.map +1 -1
- package/dist/esm/utils/mapPosition.d.ts +7 -0
- package/dist/esm/utils/mapPosition.d.ts.map +1 -1
- package/dist/esm/utils/mapPosition.js +17 -0
- package/dist/esm/utils/mapPosition.js.map +1 -1
- package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/esm/utils/ohlcvInterval.js +5 -3
- package/dist/esm/utils/ohlcvInterval.js.map +1 -1
- package/dist/esm/utils/spotPrice.d.ts +10 -1
- package/dist/esm/utils/spotPrice.d.ts.map +1 -1
- package/dist/esm/utils/spotPrice.js +14 -0
- package/dist/esm/utils/spotPrice.js.map +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts +12 -5
- package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/esm/utils/withdrawableBalances.js +17 -5
- package/dist/esm/utils/withdrawableBalances.js.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.d.ts +24 -0
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +153 -15
- package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountSummary.d.ts +17 -3
- package/dist/types/accountSummary.d.ts.map +1 -1
- package/dist/types/availableToTrade.d.ts +2 -0
- package/dist/types/availableToTrade.d.ts.map +1 -1
- package/dist/types/types/pnl.d.ts +2 -3
- package/dist/types/types/pnl.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +13 -0
- package/dist/types/types/ws.d.ts.map +1 -1
- package/dist/types/utils/apiClient.d.ts +9 -2
- package/dist/types/utils/apiClient.d.ts.map +1 -1
- package/dist/types/utils/fetchDetailedAccount.d.ts +1 -1
- package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/types/utils/mapFill.d.ts.map +1 -1
- package/dist/types/utils/mapPortfolioHistory.d.ts +3 -6
- package/dist/types/utils/mapPortfolioHistory.d.ts.map +1 -1
- package/dist/types/utils/mapPosition.d.ts +7 -0
- package/dist/types/utils/mapPosition.d.ts.map +1 -1
- package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/types/utils/spotPrice.d.ts +10 -1
- package/dist/types/utils/spotPrice.d.ts.map +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts +12 -5
- package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/types/websocket/LighterWsProvider.d.ts +24 -0
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +119 -74
- package/src/accountSummary.ts +37 -14
- package/src/availableToTrade.ts +6 -10
- package/src/types/pnl.ts +2 -3
- package/src/types/ws.ts +23 -0
- package/src/utils/apiClient.ts +16 -7
- package/src/utils/fetchDetailedAccount.ts +2 -2
- package/src/utils/mapFill.ts +6 -13
- package/src/utils/mapPortfolioHistory.ts +42 -27
- package/src/utils/mapPosition.ts +17 -0
- package/src/utils/ohlcvInterval.ts +5 -3
- package/src/utils/spotPrice.ts +21 -1
- package/src/utils/withdrawableBalances.ts +26 -7
- package/src/websocket/LighterWsProvider.ts +222 -14
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
wsLog,
|
|
18
18
|
} from '@lifi/perps-sdk'
|
|
19
19
|
import {
|
|
20
|
+
type AccountSummary,
|
|
20
21
|
type Fill,
|
|
21
22
|
type MarketContext,
|
|
22
23
|
PerpsErrorCode,
|
|
@@ -24,11 +25,14 @@ import {
|
|
|
24
25
|
type Subscription,
|
|
25
26
|
} from '@lifi/perps-types'
|
|
26
27
|
import type { Address } from 'viem'
|
|
28
|
+
import { lighterPortfolioValue } from '../accountSummary.js'
|
|
27
29
|
import {
|
|
28
30
|
DEFAULT_LIGHTER_REST_URL,
|
|
29
31
|
DEFAULT_LIGHTER_WS_URL,
|
|
30
32
|
LIGHTER_BASE_FEE_TIER,
|
|
33
|
+
LIGHTER_COLLATERAL_ASSETS,
|
|
31
34
|
LIGHTER_PROVIDER_KEY,
|
|
35
|
+
LIGHTER_RH_PROVIDER_KEY,
|
|
32
36
|
LIGHTER_SPOT_CATEGORY_ID,
|
|
33
37
|
} from '../constants.js'
|
|
34
38
|
import type { LighterPerpsProvider } from '../LighterProvider.js'
|
|
@@ -36,6 +40,7 @@ import type {
|
|
|
36
40
|
LtAccountPosition,
|
|
37
41
|
LtOrder,
|
|
38
42
|
LtTrade,
|
|
43
|
+
LtWsAccountAllMessage,
|
|
39
44
|
LtWsAccountAllOrdersMessage,
|
|
40
45
|
LtWsAccountAllPositionsMessage,
|
|
41
46
|
LtWsAccountAllTradesMessage,
|
|
@@ -58,14 +63,18 @@ import {
|
|
|
58
63
|
mapPosition,
|
|
59
64
|
toRequiredBig,
|
|
60
65
|
} from '../utils/index.js'
|
|
66
|
+
import { spotPriceByAssetId, spotValuation } from '../utils/spotPrice.js'
|
|
61
67
|
|
|
62
68
|
// Public channels: `marketsContext` (market_stats/all + spot_market_stats/all),
|
|
63
69
|
// `marketContext` (market_stats/N or spot_market_stats/N), `orderbook`
|
|
64
|
-
// (order_book/N), `trades` (trade/N)
|
|
70
|
+
// (order_book/N), `trades` (trade/N), `accountSummary` (user_stats/N +
|
|
71
|
+
// account_all/N, priced by the `marketsContext` spot marks).
|
|
65
72
|
// Authenticated channels (resolve an auth token per subscribe send):
|
|
66
73
|
// - orderUpdates → account_all_orders/{account_index}
|
|
67
74
|
// - fills → account_all_trades/{account_index}
|
|
68
75
|
// - positions → account_all_positions/{account_index}
|
|
76
|
+
// - accountSummary spot balances → account_all_assets/{account_index}, when
|
|
77
|
+
// a token resolves at subscribe time; else the public account_all/N
|
|
69
78
|
//
|
|
70
79
|
// Auth pattern (per Lighter WS spec): the subscribe payload carries the
|
|
71
80
|
// token directly — `{ type: "subscribe", channel: "...", auth: "<token>" }`.
|
|
@@ -95,6 +104,7 @@ const LIGHTER_AUTH_CHANNEL = {
|
|
|
95
104
|
function channelNeedsMarkets(channel: Subscription['channel']): boolean {
|
|
96
105
|
return (
|
|
97
106
|
channel === 'marketContext' ||
|
|
107
|
+
channel === 'accountSummary' ||
|
|
98
108
|
channel === 'orderUpdates' ||
|
|
99
109
|
channel === 'fills' ||
|
|
100
110
|
channel === 'positions'
|
|
@@ -138,6 +148,17 @@ interface BookLevel {
|
|
|
138
148
|
priceNum: number
|
|
139
149
|
}
|
|
140
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Per-address inputs of the `accountSummary` emit: the latest `user_stats`
|
|
153
|
+
* figures (`perps`, whose `equity` is perps-route only) and the spot-route
|
|
154
|
+
* balance per asset index from `account_all` or `account_all_assets`.
|
|
155
|
+
*/
|
|
156
|
+
interface AccountSummaryInputs {
|
|
157
|
+
perps?: { equity: Big } & Omit<AccountSummary, 'portfolioValue'>
|
|
158
|
+
spotBalances?: Map<number, Big>
|
|
159
|
+
lastPortfolioValue?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
141
162
|
interface OrderbookState {
|
|
142
163
|
bids: Map<string, BookLevel>
|
|
143
164
|
asks: Map<string, BookLevel>
|
|
@@ -185,6 +206,11 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
185
206
|
*/
|
|
186
207
|
private readonly positionsByAddress = new Map<string, Map<number, Position>>()
|
|
187
208
|
|
|
209
|
+
private readonly accountSummaryInputs = new Map<
|
|
210
|
+
string,
|
|
211
|
+
AccountSummaryInputs
|
|
212
|
+
>()
|
|
213
|
+
|
|
188
214
|
private readonly registry: MarketRegistry | undefined
|
|
189
215
|
|
|
190
216
|
private readonly accountIndexCache = new Map<string, number>()
|
|
@@ -279,15 +305,37 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
279
305
|
}
|
|
280
306
|
}
|
|
281
307
|
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
308
|
+
// Acquired before the wire subs register, so a rejected spot-mark open
|
|
309
|
+
// leaves no `user_stats`/`account_all` entry behind for the next replay.
|
|
310
|
+
const releaseSpotMarks =
|
|
311
|
+
sub.channel === 'accountSummary'
|
|
312
|
+
? await this.subscribeSpotMarks(sub)
|
|
313
|
+
: undefined
|
|
314
|
+
if (sub.channel === 'accountSummary') {
|
|
315
|
+
this.accountSummaryInputs.set(sub.address.toLowerCase(), {})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Registry keyed by the wire channel (unique per sub), so replay-failure
|
|
320
|
+
// logs name the channel the venue knows. A logical sub may fan out to
|
|
321
|
+
// several wire channels (e.g. `marketsContext`), each registered independently.
|
|
322
|
+
for (const { channel, needsAuth, address } of wireChannels) {
|
|
323
|
+
await this.registerSub(channel, { channel, needsAuth, address })
|
|
324
|
+
}
|
|
325
|
+
await this.rws.ready()
|
|
326
|
+
} catch (error) {
|
|
327
|
+
releaseSpotMarks?.()
|
|
328
|
+
for (const { channel } of wireChannels) {
|
|
329
|
+
this.unregisterSub(channel)
|
|
330
|
+
}
|
|
331
|
+
if (sub.channel === 'accountSummary') {
|
|
332
|
+
this.accountSummaryInputs.delete(sub.address.toLowerCase())
|
|
333
|
+
}
|
|
334
|
+
throw error
|
|
287
335
|
}
|
|
288
|
-
await this.rws.ready()
|
|
289
336
|
|
|
290
337
|
return () => {
|
|
338
|
+
releaseSpotMarks?.()
|
|
291
339
|
for (const { channel } of wireChannels) {
|
|
292
340
|
this.unregisterSub(channel)
|
|
293
341
|
this.rws.send(JSON.stringify({ type: 'unsubscribe', channel }))
|
|
@@ -301,13 +349,31 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
301
349
|
if (sub.channel === 'positions') {
|
|
302
350
|
this.positionsByAddress.delete(sub.address.toLowerCase())
|
|
303
351
|
}
|
|
352
|
+
if (sub.channel === 'accountSummary') {
|
|
353
|
+
this.accountSummaryInputs.delete(sub.address.toLowerCase())
|
|
354
|
+
}
|
|
304
355
|
}
|
|
305
356
|
}
|
|
306
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Hold a `marketsContext` subscription for the spot marks that price the
|
|
360
|
+
* account's spot balances. The ref-counted base shares the wire channels
|
|
361
|
+
* with any consumer `marketsContext` subscription.
|
|
362
|
+
*/
|
|
363
|
+
private subscribeSpotMarks(
|
|
364
|
+
sub: Extract<Subscription, { channel: 'accountSummary' }>
|
|
365
|
+
): Promise<() => void> {
|
|
366
|
+
const address = sub.address.toLowerCase()
|
|
367
|
+
return this.subscribe({ channel: 'marketsContext', dex: sub.dex }, () =>
|
|
368
|
+
this.emitAccountSummary(address, true)
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
307
372
|
protected override onClose(): void {
|
|
308
373
|
this.orderbooks.clear()
|
|
309
374
|
this.marketsContext = {}
|
|
310
375
|
this.positionsByAddress.clear()
|
|
376
|
+
this.accountSummaryInputs.clear()
|
|
311
377
|
}
|
|
312
378
|
|
|
313
379
|
protected async sendSubscribe({
|
|
@@ -354,6 +420,19 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
354
420
|
return token
|
|
355
421
|
}
|
|
356
422
|
|
|
423
|
+
/** Whether a token resolves for the address; a resolver that throws counts as no token. */
|
|
424
|
+
private async canResolveAuthToken(address: Address): Promise<boolean> {
|
|
425
|
+
const resolve = this.authTokenResolver()
|
|
426
|
+
if (!resolve) {
|
|
427
|
+
return false
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
return Boolean(await resolve(address))
|
|
431
|
+
} catch {
|
|
432
|
+
return false
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
357
436
|
protected toKey(sub: Subscription): string {
|
|
358
437
|
switch (sub.channel) {
|
|
359
438
|
case 'marketsContext':
|
|
@@ -433,14 +512,26 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
433
512
|
return [{ channel: `trade/${id}`, needsAuth: false }]
|
|
434
513
|
}
|
|
435
514
|
if (sub.channel === 'accountSummary') {
|
|
515
|
+
// Rejects the subscribe for a provider key with no settlement asset,
|
|
516
|
+
// instead of every later frame.
|
|
517
|
+
this.settlementAssetIndex()
|
|
436
518
|
const accountIndex = await this.resolveAccountIndex(sub.address)
|
|
437
|
-
// `
|
|
519
|
+
// `account_all_assets` carries only the spot balances but rejects a
|
|
520
|
+
// subscribe without auth; the public `account_all` also carries the
|
|
521
|
+
// account's trades, positions and funding.
|
|
522
|
+
const hasToken = await this.canResolveAuthToken(sub.address)
|
|
523
|
+
const assetsChannel = hasToken ? 'account_all_assets' : 'account_all'
|
|
438
524
|
return [
|
|
439
525
|
{
|
|
440
526
|
channel: `user_stats/${accountIndex}`,
|
|
441
527
|
needsAuth: false,
|
|
442
528
|
address: sub.address,
|
|
443
529
|
},
|
|
530
|
+
{
|
|
531
|
+
channel: `${assetsChannel}/${accountIndex}`,
|
|
532
|
+
needsAuth: hasToken,
|
|
533
|
+
address: sub.address,
|
|
534
|
+
},
|
|
444
535
|
]
|
|
445
536
|
}
|
|
446
537
|
if (
|
|
@@ -585,6 +676,30 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
585
676
|
return
|
|
586
677
|
}
|
|
587
678
|
|
|
679
|
+
if (
|
|
680
|
+
msg.type === 'subscribed/account_all' ||
|
|
681
|
+
msg.type === 'update/account_all'
|
|
682
|
+
) {
|
|
683
|
+
this.handleAccountAll(
|
|
684
|
+
msg as LtWsAccountAllMessage,
|
|
685
|
+
'account_all',
|
|
686
|
+
msg.type === 'subscribed/account_all'
|
|
687
|
+
)
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (
|
|
692
|
+
msg.type === 'subscribed/account_all_assets' ||
|
|
693
|
+
msg.type === 'update/account_all_assets'
|
|
694
|
+
) {
|
|
695
|
+
this.handleAccountAll(
|
|
696
|
+
msg as LtWsAccountAllMessage,
|
|
697
|
+
'account_all_assets',
|
|
698
|
+
msg.type === 'subscribed/account_all_assets'
|
|
699
|
+
)
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
|
|
588
703
|
if (
|
|
589
704
|
msg.type === 'subscribed/account_all_positions' ||
|
|
590
705
|
msg.type === 'update/account_all_positions'
|
|
@@ -657,17 +772,110 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
657
772
|
marginUsed = isolatedMargin.plus(crossMargin)
|
|
658
773
|
}
|
|
659
774
|
|
|
775
|
+
const inputs = this.accountSummaryInputs.get(address)
|
|
776
|
+
if (inputs === undefined) {
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
inputs.perps = {
|
|
780
|
+
equity: portfolio,
|
|
781
|
+
availableMargin: available.toFixed(),
|
|
782
|
+
marginUsed: marginUsed.toFixed(),
|
|
783
|
+
unrealizedPnl: portfolio.minus(collateral).toFixed(),
|
|
784
|
+
}
|
|
785
|
+
this.emitAccountSummary(address)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Replace the spot balances on a `subscribed/` frame and upsert them on an
|
|
790
|
+
* `update/` frame. An update before the first snapshot is dropped, so the
|
|
791
|
+
* map never holds a partial set. Balances parse here, so a non-decimal
|
|
792
|
+
* balance rejects its frame and never reaches the emit path.
|
|
793
|
+
*/
|
|
794
|
+
private handleAccountAll(
|
|
795
|
+
msg: LtWsAccountAllMessage,
|
|
796
|
+
prefix: 'account_all' | 'account_all_assets',
|
|
797
|
+
isSnapshot: boolean
|
|
798
|
+
): void {
|
|
799
|
+
const address = this.addressFromChannel(msg.channel, prefix)
|
|
800
|
+
if (!address) {
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
const inputs = this.accountSummaryInputs.get(address)
|
|
804
|
+
if (inputs === undefined || (!isSnapshot && !msg.assets)) {
|
|
805
|
+
return
|
|
806
|
+
}
|
|
807
|
+
const parsed = Object.values(msg.assets ?? {}).map(
|
|
808
|
+
(asset) =>
|
|
809
|
+
[asset.asset_id, toRequiredBig(asset.balance, 'balance')] as const
|
|
810
|
+
)
|
|
811
|
+
const balances = isSnapshot ? new Map<number, Big>() : inputs.spotBalances
|
|
812
|
+
if (balances === undefined) {
|
|
813
|
+
return
|
|
814
|
+
}
|
|
815
|
+
for (const [assetId, balance] of parsed) {
|
|
816
|
+
balances.set(assetId, balance)
|
|
817
|
+
}
|
|
818
|
+
inputs.spotBalances = balances
|
|
819
|
+
this.emitAccountSummary(address)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Emit the account summary once both the `user_stats` figures and the spot
|
|
824
|
+
* balances are in. `portfolioValue` values the spot balances as REST
|
|
825
|
+
* `getAccount` does. `onlyOnChange` skips an emit whose `portfolioValue`
|
|
826
|
+
* equals the last one, so a mark tick on an unrelated market emits nothing.
|
|
827
|
+
*/
|
|
828
|
+
private emitAccountSummary(address: string, onlyOnChange = false): void {
|
|
829
|
+
const inputs = this.accountSummaryInputs.get(address)
|
|
830
|
+
if (inputs?.perps === undefined || inputs.spotBalances === undefined) {
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
const settlementAssetIndex = this.settlementAssetIndex()
|
|
834
|
+
const held = [...inputs.spotBalances].filter(([, balance]) => balance.gt(0))
|
|
835
|
+
const spotPrices = spotPriceByAssetId(
|
|
836
|
+
this.registry?.markets ?? [],
|
|
837
|
+
LIGHTER_SPOT_CATEGORY_ID,
|
|
838
|
+
Object.values(this.marketsContext),
|
|
839
|
+
new Set(
|
|
840
|
+
held
|
|
841
|
+
.filter(([assetId]) => assetId !== settlementAssetIndex)
|
|
842
|
+
.map(([assetId]) => String(assetId))
|
|
843
|
+
)
|
|
844
|
+
)
|
|
845
|
+
const portfolioValue = lighterPortfolioValue(
|
|
846
|
+
inputs.perps.equity,
|
|
847
|
+
held.map(
|
|
848
|
+
([assetId, balance]) =>
|
|
849
|
+
spotValuation(
|
|
850
|
+
assetId,
|
|
851
|
+
balance.toFixed(),
|
|
852
|
+
settlementAssetIndex,
|
|
853
|
+
spotPrices
|
|
854
|
+
).valueUsd
|
|
855
|
+
)
|
|
856
|
+
).toFixed()
|
|
857
|
+
if (onlyOnChange && portfolioValue === inputs.lastPortfolioValue) {
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
inputs.lastPortfolioValue = portfolioValue
|
|
861
|
+
const { equity: _equity, ...perps } = inputs.perps
|
|
660
862
|
this.emit(`accountSummary:${address}`, {
|
|
661
863
|
channel: 'accountSummary',
|
|
662
|
-
data: {
|
|
663
|
-
portfolioValue: portfolio.toFixed(),
|
|
664
|
-
availableMargin: available.toFixed(),
|
|
665
|
-
marginUsed: marginUsed.toFixed(),
|
|
666
|
-
unrealizedPnl: portfolio.minus(collateral).toFixed(),
|
|
667
|
-
},
|
|
864
|
+
data: { portfolioValue, ...perps },
|
|
668
865
|
})
|
|
669
866
|
}
|
|
670
867
|
|
|
868
|
+
private settlementAssetIndex(): number {
|
|
869
|
+
const key = this.providerKey
|
|
870
|
+
if (key === LIGHTER_PROVIDER_KEY || key === LIGHTER_RH_PROVIDER_KEY) {
|
|
871
|
+
return LIGHTER_COLLATERAL_ASSETS[key].assetIndex
|
|
872
|
+
}
|
|
873
|
+
throw new PerpsError(
|
|
874
|
+
PerpsErrorCode.SDKError,
|
|
875
|
+
`Lighter WS: no settlement asset for provider '${key}'.`
|
|
876
|
+
)
|
|
877
|
+
}
|
|
878
|
+
|
|
671
879
|
private handleAccountTrades(msg: LtWsAccountAllTradesMessage): void {
|
|
672
880
|
const address = this.addressFromChannel(msg.channel, 'account_all_trades')
|
|
673
881
|
if (!address) {
|