@lifi/perps-sdk 14.0.0 → 16.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/README.md +27 -0
- package/dist/cjs/client/PerpsClient.d.ts +6 -1
- package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
- package/dist/cjs/client/PerpsClient.js +43 -8
- package/dist/cjs/client/PerpsClient.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +6 -5
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registry/assetRegistry.d.ts +2 -1
- package/dist/cjs/registry/assetRegistry.d.ts.map +1 -1
- package/dist/cjs/registry/assetRegistry.js +11 -1
- package/dist/cjs/registry/assetRegistry.js.map +1 -1
- package/dist/cjs/types/provider.d.ts +6 -1
- package/dist/cjs/types/provider.d.ts.map +1 -1
- package/dist/cjs/utils/explorer.d.ts +3 -0
- package/dist/cjs/utils/explorer.d.ts.map +1 -1
- package/dist/cjs/utils/explorer.js +6 -0
- package/dist/cjs/utils/explorer.js.map +1 -1
- package/dist/cjs/utils/orderClassification.d.ts.map +1 -1
- package/dist/cjs/utils/orderClassification.js +1 -0
- package/dist/cjs/utils/orderClassification.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/websocket/decodeChain.d.ts +11 -0
- package/dist/cjs/websocket/decodeChain.d.ts.map +1 -0
- package/dist/cjs/websocket/decodeChain.js +37 -0
- package/dist/cjs/websocket/decodeChain.js.map +1 -0
- package/dist/esm/client/PerpsClient.d.ts +30 -5
- package/dist/esm/client/PerpsClient.d.ts.map +1 -1
- package/dist/esm/client/PerpsClient.js +77 -13
- package/dist/esm/client/PerpsClient.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/registry/assetRegistry.d.ts +7 -1
- package/dist/esm/registry/assetRegistry.d.ts.map +1 -1
- package/dist/esm/registry/assetRegistry.js +14 -0
- package/dist/esm/registry/assetRegistry.js.map +1 -1
- package/dist/esm/types/provider.d.ts +20 -1
- package/dist/esm/types/provider.d.ts.map +1 -1
- package/dist/esm/utils/explorer.d.ts +3 -0
- package/dist/esm/utils/explorer.d.ts.map +1 -1
- package/dist/esm/utils/explorer.js +6 -0
- package/dist/esm/utils/explorer.js.map +1 -1
- package/dist/esm/utils/orderClassification.d.ts.map +1 -1
- package/dist/esm/utils/orderClassification.js +1 -0
- package/dist/esm/utils/orderClassification.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/websocket/decodeChain.d.ts +30 -0
- package/dist/esm/websocket/decodeChain.d.ts.map +1 -0
- package/dist/esm/websocket/decodeChain.js +44 -0
- package/dist/esm/websocket/decodeChain.js.map +1 -0
- package/dist/types/client/PerpsClient.d.ts +30 -5
- package/dist/types/client/PerpsClient.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/registry/assetRegistry.d.ts +7 -1
- package/dist/types/registry/assetRegistry.d.ts.map +1 -1
- package/dist/types/types/provider.d.ts +20 -1
- package/dist/types/types/provider.d.ts.map +1 -1
- package/dist/types/utils/explorer.d.ts +3 -0
- package/dist/types/utils/explorer.d.ts.map +1 -1
- package/dist/types/utils/orderClassification.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/websocket/decodeChain.d.ts +30 -0
- package/dist/types/websocket/decodeChain.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/client/PerpsClient.ts +111 -18
- package/src/index.ts +8 -5
- package/src/registry/assetRegistry.ts +16 -1
- package/src/types/provider.ts +25 -0
- package/src/utils/explorer.ts +6 -0
- package/src/utils/orderClassification.ts +1 -0
- package/src/version.ts +1 -1
- package/src/websocket/decodeChain.ts +54 -0
- package/dist/cjs/utils/accountSummary.d.ts +0 -4
- package/dist/cjs/utils/accountSummary.d.ts.map +0 -1
- package/dist/cjs/utils/accountSummary.js +0 -35
- package/dist/cjs/utils/accountSummary.js.map +0 -1
- package/dist/esm/utils/accountSummary.d.ts +0 -32
- package/dist/esm/utils/accountSummary.d.ts.map +0 -1
- package/dist/esm/utils/accountSummary.js +0 -46
- package/dist/esm/utils/accountSummary.js.map +0 -1
- package/dist/types/utils/accountSummary.d.ts +0 -32
- package/dist/types/utils/accountSummary.d.ts.map +0 -1
- package/src/utils/accountSummary.ts +0 -90
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ActionParamsMap,
|
|
5
5
|
ActionResult,
|
|
6
6
|
ActionStep,
|
|
7
|
+
AvailableToTrade,
|
|
7
8
|
CreateActionResponse,
|
|
8
9
|
ExecuteActionResponse,
|
|
9
10
|
MarketRef,
|
|
@@ -28,7 +29,8 @@ import {
|
|
|
28
29
|
import Big from 'big.js'
|
|
29
30
|
import type { Address } from 'viem'
|
|
30
31
|
import { PerpsError } from '../errors/PerpsError.js'
|
|
31
|
-
import { getAssetRegistry } from '../registry/assetRegistry.js'
|
|
32
|
+
import { getAssetRegistry, toAssetDisplay } from '../registry/assetRegistry.js'
|
|
33
|
+
import { getMarketRegistry } from '../registry/marketRegistry.js'
|
|
32
34
|
import { createAction } from '../services/createAction.js'
|
|
33
35
|
import { executeAction } from '../services/executeAction.js'
|
|
34
36
|
import { getAccount as fetchAccount } from '../services/getAccount.js'
|
|
@@ -109,6 +111,32 @@ function findActionDescriptor(
|
|
|
109
111
|
return descriptor
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Split an ordered `createAction` batch into consecutive runs of steps that
|
|
116
|
+
* share one descriptor. A batch is a chain and may mix descriptors — a
|
|
117
|
+
* Hyperliquid `placeOrder` can arrive with a USER-signed builder-fee approval
|
|
118
|
+
* ahead of the SDK-signed order — and each run must be signed by its own
|
|
119
|
+
* descriptor's signer.
|
|
120
|
+
*
|
|
121
|
+
* @throws {PerpsError} When a step's action is not declared by the provider.
|
|
122
|
+
*/
|
|
123
|
+
function groupStepsByDescriptor(
|
|
124
|
+
metadata: Provider,
|
|
125
|
+
steps: ActionStep[]
|
|
126
|
+
): { descriptor: ProviderAction; steps: ActionStep[] }[] {
|
|
127
|
+
const groups: { descriptor: ProviderAction; steps: ActionStep[] }[] = []
|
|
128
|
+
for (const step of steps) {
|
|
129
|
+
const descriptor = findActionDescriptor(metadata, step.action)
|
|
130
|
+
const current = groups.at(-1)
|
|
131
|
+
if (current !== undefined && current.descriptor.type === descriptor.type) {
|
|
132
|
+
current.steps.push(step)
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
groups.push({ descriptor, steps: [step] })
|
|
136
|
+
}
|
|
137
|
+
return groups
|
|
138
|
+
}
|
|
139
|
+
|
|
112
140
|
/**
|
|
113
141
|
* The primary high-level perps API: wraps a {@link PerpsSDKClient} and owns the
|
|
114
142
|
* end-to-end signing pipeline for provider setup, orders, and account-level
|
|
@@ -241,15 +269,21 @@ export class PerpsClient {
|
|
|
241
269
|
}
|
|
242
270
|
|
|
243
271
|
/**
|
|
244
|
-
* Delegate signing of `actions` to the provider plugin
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
272
|
+
* Delegate signing of `actions` to the provider plugin, one call per
|
|
273
|
+
* consecutive run of steps sharing a descriptor. Each call carries that
|
|
274
|
+
* descriptor's `signingMethod` and `signers`, so a mixed batch signs every
|
|
275
|
+
* step with the signer its own descriptor declares; the signed steps come
|
|
276
|
+
* back in the batch's original order. The plugin owns every signing arm and
|
|
277
|
+
* branches on `signers` internally, reading the end-user's wallet from the
|
|
278
|
+
* {@link SignActionsContext} when an arm signs as the user.
|
|
279
|
+
*
|
|
280
|
+
* @throws {PerpsError} When a step's action is not declared by the provider,
|
|
281
|
+
* or the plugin implements no `signActions`.
|
|
248
282
|
*/
|
|
249
283
|
private async delegateSignActions(
|
|
250
284
|
provider: string,
|
|
251
285
|
address: Address,
|
|
252
|
-
|
|
286
|
+
metadata: Provider,
|
|
253
287
|
actions: ActionStep[],
|
|
254
288
|
onProgress?: (progress: SignActionProgress) => void
|
|
255
289
|
): Promise<SignedActionStep[]> {
|
|
@@ -257,17 +291,25 @@ export class PerpsClient {
|
|
|
257
291
|
if (typeof plugin.signActions !== 'function') {
|
|
258
292
|
throw new PerpsError(
|
|
259
293
|
PerpsErrorCode.SDKError,
|
|
260
|
-
`Provider '${provider}' does not implement signActions
|
|
261
|
-
`signingMethod '${descriptor.signingMethod}'.`
|
|
294
|
+
`Provider '${provider}' does not implement signActions.`
|
|
262
295
|
)
|
|
263
296
|
}
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
const signed: SignedActionStep[] = []
|
|
298
|
+
for (const group of groupStepsByDescriptor(metadata, actions)) {
|
|
299
|
+
const userWallet = await this.resolveSigningWallet(
|
|
300
|
+
group.descriptor,
|
|
301
|
+
group.steps
|
|
302
|
+
)
|
|
303
|
+
signed.push(
|
|
304
|
+
...(await plugin.signActions(
|
|
305
|
+
group.descriptor.signingMethod,
|
|
306
|
+
group.steps,
|
|
307
|
+
address,
|
|
308
|
+
this.buildSignActionsContext(group.descriptor, userWallet, onProgress)
|
|
309
|
+
))
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
return signed
|
|
271
313
|
}
|
|
272
314
|
|
|
273
315
|
/**
|
|
@@ -409,11 +451,10 @@ export class PerpsClient {
|
|
|
409
451
|
step: ActionStep
|
|
410
452
|
): Promise<SignedActionStep | undefined> {
|
|
411
453
|
const metadata = await this.getProviderMetadata(provider)
|
|
412
|
-
const descriptor = findActionDescriptor(metadata, step.action)
|
|
413
454
|
const [signed] = await this.delegateSignActions(
|
|
414
455
|
provider,
|
|
415
456
|
address,
|
|
416
|
-
|
|
457
|
+
metadata,
|
|
417
458
|
[step]
|
|
418
459
|
)
|
|
419
460
|
return signed
|
|
@@ -498,6 +539,58 @@ export class PerpsClient {
|
|
|
498
539
|
})
|
|
499
540
|
}
|
|
500
541
|
|
|
542
|
+
/**
|
|
543
|
+
* The amounts `params.address` can still buy and sell on one market, in
|
|
544
|
+
* that market's margin asset. The order panel reads this per-market figure;
|
|
545
|
+
* account displays read the account-scoped
|
|
546
|
+
* {@link AccountSummary.availableMargin} instead.
|
|
547
|
+
*
|
|
548
|
+
* Providers that read a per-market figure answer it directly. For every
|
|
549
|
+
* other provider this falls back to the account summary, so both sides
|
|
550
|
+
* equal `availableMargin` and the asset is the market's quote asset.
|
|
551
|
+
*
|
|
552
|
+
* @throws {PerpsError} When the provider plugin is not registered, or the
|
|
553
|
+
* market is unknown to the provider's market registry.
|
|
554
|
+
* @public
|
|
555
|
+
*/
|
|
556
|
+
async getAvailableToTrade(
|
|
557
|
+
params: {
|
|
558
|
+
provider: string
|
|
559
|
+
address: Address
|
|
560
|
+
marketId: string
|
|
561
|
+
},
|
|
562
|
+
options?: SDKRequestOptions
|
|
563
|
+
): Promise<AvailableToTrade> {
|
|
564
|
+
const plugin = this.requireProvider(params.provider)
|
|
565
|
+
const perMarket = await plugin.getAvailableToTrade?.(
|
|
566
|
+
{ address: params.address, marketId: params.marketId },
|
|
567
|
+
options
|
|
568
|
+
)
|
|
569
|
+
if (perMarket !== undefined) {
|
|
570
|
+
return perMarket
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const registry = getMarketRegistry(this.sdkClient, params.provider)
|
|
574
|
+
await registry.sync()
|
|
575
|
+
const market = registry.require(params.marketId)
|
|
576
|
+
const account = await fetchAccount(
|
|
577
|
+
this.sdkClient,
|
|
578
|
+
{ provider: params.provider, address: params.address },
|
|
579
|
+
options
|
|
580
|
+
)
|
|
581
|
+
const { availableMargin } = plugin.getAccountSummary(
|
|
582
|
+
account,
|
|
583
|
+
account.positions
|
|
584
|
+
)
|
|
585
|
+
return {
|
|
586
|
+
providerId: market.providerId,
|
|
587
|
+
marketId: market.id,
|
|
588
|
+
asset: toAssetDisplay(market.quoteAsset),
|
|
589
|
+
buy: availableMargin,
|
|
590
|
+
sell: availableMargin,
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
501
594
|
/**
|
|
502
595
|
* Resolve the exact venue-owned margin requirements for `position`.
|
|
503
596
|
* Returns `undefined` when the position has no individual margin adjustment.
|
|
@@ -1218,7 +1311,7 @@ export class PerpsClient {
|
|
|
1218
1311
|
const signedActions = await this.delegateSignActions(
|
|
1219
1312
|
provider,
|
|
1220
1313
|
address,
|
|
1221
|
-
|
|
1314
|
+
metadata,
|
|
1222
1315
|
actions,
|
|
1223
1316
|
onProgress
|
|
1224
1317
|
)
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,11 @@ export { PerpsClient } from './client/PerpsClient.js'
|
|
|
19
19
|
export { PerpsErrorMessage } from './errors/constants.js'
|
|
20
20
|
export { PerpsError } from './errors/PerpsError.js'
|
|
21
21
|
// Registries
|
|
22
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
AssetRegistry,
|
|
24
|
+
getAssetRegistry,
|
|
25
|
+
toAssetDisplay,
|
|
26
|
+
} from './registry/assetRegistry.js'
|
|
23
27
|
export type { DefaultMarketProvider } from './registry/marketRegistry.js'
|
|
24
28
|
export {
|
|
25
29
|
DEFAULT_MARKET_ID,
|
|
@@ -152,6 +156,7 @@ export type {
|
|
|
152
156
|
ProviderAccountExistsParams,
|
|
153
157
|
ProviderGetAccountParams,
|
|
154
158
|
ProviderGetActivityParams,
|
|
159
|
+
ProviderGetAvailableToTradeParams,
|
|
155
160
|
ProviderGetDepositFlowParams,
|
|
156
161
|
ProviderGetFillsParams,
|
|
157
162
|
ProviderGetMarketSettingsParams,
|
|
@@ -170,10 +175,6 @@ export type {
|
|
|
170
175
|
WithdrawableBalance,
|
|
171
176
|
WithdrawalRoute,
|
|
172
177
|
} from './types/withdrawal.js'
|
|
173
|
-
export {
|
|
174
|
-
type CollateralSemantics,
|
|
175
|
-
summarizeAccount,
|
|
176
|
-
} from './utils/accountSummary.js'
|
|
177
178
|
export { paginateActivity } from './utils/activityPaging.js'
|
|
178
179
|
export type { ExpectedPnl } from './utils/calculations.js'
|
|
179
180
|
export {
|
|
@@ -259,6 +260,8 @@ export { createWarnOnce } from './utils/warnOnce.js'
|
|
|
259
260
|
// Version
|
|
260
261
|
export { name, version } from './version.js'
|
|
261
262
|
export { cachePromise } from './websocket/cachePromise.js'
|
|
263
|
+
export type { DecodeChainMode } from './websocket/decodeChain.js'
|
|
264
|
+
export { DecodeChain } from './websocket/decodeChain.js'
|
|
262
265
|
export type {
|
|
263
266
|
PerpsWsClientOptions,
|
|
264
267
|
WsProviderFactory,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Asset, AssetsResponse } from '@lifi/perps-types'
|
|
1
|
+
import type { Asset, AssetDisplay, AssetsResponse } from '@lifi/perps-types'
|
|
2
2
|
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
3
3
|
import { PerpsError } from '../errors/PerpsError.js'
|
|
4
4
|
import { buildUrl, request } from '../transport/request.js'
|
|
@@ -59,6 +59,21 @@ export class AssetRegistry extends ReferenceDataRegistry<Asset> {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Project an {@link Asset} to the identity and labels a display needs.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export const toAssetDisplay = (asset: Asset): AssetDisplay => ({
|
|
68
|
+
providerId: asset.providerId,
|
|
69
|
+
id: asset.id,
|
|
70
|
+
displaySymbol: asset.displaySymbol,
|
|
71
|
+
logoURI: asset.logoURI,
|
|
72
|
+
...(asset.displayName === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: { displayName: asset.displayName }),
|
|
75
|
+
})
|
|
76
|
+
|
|
62
77
|
const registries = new WeakMap<PerpsSDKClient, Map<string, AssetRegistry>>()
|
|
63
78
|
|
|
64
79
|
/**
|
package/src/types/provider.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ActionType,
|
|
9
9
|
ActivitiesResponse,
|
|
10
10
|
ActivityType,
|
|
11
|
+
AvailableToTrade,
|
|
11
12
|
FillsResponse,
|
|
12
13
|
Market,
|
|
13
14
|
MarketRef,
|
|
@@ -216,6 +217,17 @@ export interface ProviderGetMarketSettingsParams {
|
|
|
216
217
|
market: MarketRef
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Read params for {@link PerpsProviderPlugin.getAvailableToTrade}.
|
|
222
|
+
*
|
|
223
|
+
* @public
|
|
224
|
+
*/
|
|
225
|
+
export interface ProviderGetAvailableToTradeParams {
|
|
226
|
+
address: Address
|
|
227
|
+
/** The market's opaque `Market.id` (not a display symbol). */
|
|
228
|
+
marketId: string
|
|
229
|
+
}
|
|
230
|
+
|
|
219
231
|
/**
|
|
220
232
|
* Read params for {@link PerpsProvider.getOrders}.
|
|
221
233
|
*
|
|
@@ -442,6 +454,19 @@ export interface PerpsProviderPlugin {
|
|
|
442
454
|
options?: SDKRequestOptions
|
|
443
455
|
): Promise<MarketSettings | undefined>
|
|
444
456
|
|
|
457
|
+
/**
|
|
458
|
+
* The amounts the account can still buy and sell on one market, in that
|
|
459
|
+
* market's margin asset. Optional because venues expose this unevenly:
|
|
460
|
+
* Hyperliquid reads it directly (`activeAssetData`), other venues publish
|
|
461
|
+
* only an account-scoped figure. `undefined` means the venue has nothing
|
|
462
|
+
* to read for this market, and the caller falls back to the account
|
|
463
|
+
* summary.
|
|
464
|
+
*/
|
|
465
|
+
getAvailableToTrade?(
|
|
466
|
+
params: ProviderGetAvailableToTradeParams,
|
|
467
|
+
options?: SDKRequestOptions
|
|
468
|
+
): Promise<AvailableToTrade | undefined>
|
|
469
|
+
|
|
445
470
|
getOrders(
|
|
446
471
|
params: ProviderGetOrdersParams,
|
|
447
472
|
options?: SDKRequestOptions
|
package/src/utils/explorer.ts
CHANGED
|
@@ -15,7 +15,10 @@ export const ExplorerChainId = {
|
|
|
15
15
|
ETHEREUM: 1,
|
|
16
16
|
ARBITRUM_ONE: 42161,
|
|
17
17
|
LIGHTER: 304,
|
|
18
|
+
ROBINHOOD: 4663,
|
|
18
19
|
HYPERLIQUID: 999,
|
|
20
|
+
AVALANCHE_C_CHAIN: 43114,
|
|
21
|
+
BNB_SMART_CHAIN: 56,
|
|
19
22
|
} as const
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -30,7 +33,10 @@ const TX_BASE_URL_BY_CHAIN: Record<ExplorerChainId, string> = {
|
|
|
30
33
|
[ExplorerChainId.ETHEREUM]: 'https://etherscan.io/tx/',
|
|
31
34
|
[ExplorerChainId.ARBITRUM_ONE]: 'https://arbiscan.io/tx/',
|
|
32
35
|
[ExplorerChainId.LIGHTER]: 'https://app.lighter.xyz/explorer/logs/',
|
|
36
|
+
[ExplorerChainId.ROBINHOOD]: 'https://robin.etherscan.io/tx/',
|
|
33
37
|
[ExplorerChainId.HYPERLIQUID]: 'https://app.hyperliquid.xyz/explorer/tx/',
|
|
38
|
+
[ExplorerChainId.AVALANCHE_C_CHAIN]: 'https://snowtrace.io/tx/',
|
|
39
|
+
[ExplorerChainId.BNB_SMART_CHAIN]: 'https://bscscan.com/tx/',
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
package/src/version.ts
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame semantics of a {@link DecodeChain}:
|
|
3
|
+
* - `'every'`: every frame decodes and applies in arrival order — required for
|
|
4
|
+
* delta-carrying channels, where dropping a frame corrupts downstream state.
|
|
5
|
+
* - `'latest'`: frames queued behind an in-flight decode coalesce to the
|
|
6
|
+
* newest — for snapshot-like channels, where a newer frame supersedes
|
|
7
|
+
* anything still waiting.
|
|
8
|
+
*/
|
|
9
|
+
export type DecodeChainMode = 'every' | 'latest'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Serializes async WS frame decodes so results apply in arrival order.
|
|
13
|
+
* In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
|
|
14
|
+
* collapses to its newest frame instead of replaying every stale decode.
|
|
15
|
+
* Decode failures are reported to `onError` and never break the chain.
|
|
16
|
+
*/
|
|
17
|
+
export class DecodeChain {
|
|
18
|
+
private tail: Promise<void> = Promise.resolve()
|
|
19
|
+
private queued: (() => Promise<void>) | undefined
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly mode: DecodeChainMode,
|
|
23
|
+
private readonly onError: (error: unknown) => void
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
push(decode: () => Promise<void>): void {
|
|
27
|
+
if (this.mode === 'every') {
|
|
28
|
+
this.tail = this.tail.then(decode).catch(this.onError)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
const drainScheduled = this.queued !== undefined
|
|
32
|
+
this.queued = decode
|
|
33
|
+
if (drainScheduled) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
this.tail = this.tail
|
|
37
|
+
.then(() => {
|
|
38
|
+
const next = this.queued
|
|
39
|
+
this.queued = undefined
|
|
40
|
+
return next?.()
|
|
41
|
+
})
|
|
42
|
+
.catch(this.onError)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detach on connection teardown: drops the queued latest-mode decode and
|
|
47
|
+
* starts subsequent pushes on a fresh chain. A decode already in flight
|
|
48
|
+
* still settles.
|
|
49
|
+
*/
|
|
50
|
+
reset(): void {
|
|
51
|
+
this.tail = Promise.resolve()
|
|
52
|
+
this.queued = undefined
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { AccountResponse, AccountSummary, Position } from '@lifi/perps-types';
|
|
2
|
-
export type CollateralSemantics = 'free' | 'net' | 'gross' | 'equity';
|
|
3
|
-
export declare function summarizeAccount(account: AccountResponse, positions: Position[], semantics: CollateralSemantics): AccountSummary;
|
|
4
|
-
//# sourceMappingURL=accountSummary.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accountSummary.d.ts","sourceRoot":"","sources":["../../../src/utils/accountSummary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,QAAQ,EACT,MAAM,mBAAmB,CAAA;AAgC1B,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAUrE,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,SAAS,EAAE,mBAAmB,GAC7B,cAAc,CAuChB"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.summarizeAccount = summarizeAccount;
|
|
4
|
-
const parse_js_1 = require("./parse.js");
|
|
5
|
-
const sumValueUsd = (balances) => balances.reduce((sum, b) => sum + (0, parse_js_1.stringToFloat)(b.valueUsd), 0);
|
|
6
|
-
const sumMarginUsd = (balances) => balances.reduce((sum, b) => sum + (0, parse_js_1.stringToFloat)(b.valueUsd) * (b.collateralWeight ?? 1), 0);
|
|
7
|
-
function summarizeAccount(account, positions, semantics) {
|
|
8
|
-
let marginUsed = 0;
|
|
9
|
-
let unrealizedPnl = 0;
|
|
10
|
-
for (const p of positions) {
|
|
11
|
-
marginUsed += (0, parse_js_1.stringToFloat)(p.marginUsed);
|
|
12
|
-
unrealizedPnl += (0, parse_js_1.stringToFloat)(p.unrealizedPnl);
|
|
13
|
-
}
|
|
14
|
-
const collateral = sumValueUsd(account.collateralBalances);
|
|
15
|
-
const marginCollateral = sumMarginUsd(account.collateralBalances);
|
|
16
|
-
const balances = sumValueUsd(account.balances);
|
|
17
|
-
const withLocked = semantics === 'free' || semantics === 'net';
|
|
18
|
-
const grossCollateral = withLocked ? collateral + marginUsed : collateral;
|
|
19
|
-
const grossMarginCollateral = withLocked
|
|
20
|
-
? marginCollateral + marginUsed
|
|
21
|
-
: marginCollateral;
|
|
22
|
-
const equity = semantics === 'equity' || semantics === 'net'
|
|
23
|
-
? grossCollateral
|
|
24
|
-
: grossCollateral + unrealizedPnl;
|
|
25
|
-
const availableMargin = grossMarginCollateral +
|
|
26
|
-
(semantics === 'gross' ? unrealizedPnl : 0) -
|
|
27
|
-
marginUsed;
|
|
28
|
-
return {
|
|
29
|
-
portfolioValue: (balances + equity).toString(),
|
|
30
|
-
availableMargin: availableMargin.toString(),
|
|
31
|
-
marginUsed: marginUsed.toString(),
|
|
32
|
-
unrealizedPnl: unrealizedPnl.toString(),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=accountSummary.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accountSummary.js","sourceRoot":"","sources":["../../../src/utils/accountSummary.ts"],"names":[],"mappings":";;AA8CA,4CA2CC;AApFD,yCAA0C;AAE1C,MAAM,WAAW,GAAG,CAAC,QAAqC,EAAU,EAAE,CACpE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAA,wBAAa,EAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AAEjE,MAAM,YAAY,GAAG,CAAC,QAAqC,EAAU,EAAE,CACrE,QAAQ,CAAC,MAAM,CACb,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAA,wBAAa,EAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EACvE,CAAC,CACF,CAAA;AAgCH,SAAgB,gBAAgB,CAC9B,OAAwB,EACxB,SAAqB,EACrB,SAA8B;IAE9B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,UAAU,IAAI,IAAA,wBAAa,EAAC,CAAC,CAAC,UAAU,CAAC,CAAA;QACzC,aAAa,IAAI,IAAA,wBAAa,EAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IACjD,CAAC;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAI1D,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE9C,MAAM,UAAU,GAAG,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,KAAK,CAAA;IAC9D,MAAM,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;IACzE,MAAM,qBAAqB,GAAG,UAAU;QACtC,CAAC,CAAC,gBAAgB,GAAG,UAAU;QAC/B,CAAC,CAAC,gBAAgB,CAAA;IACpB,MAAM,MAAM,GACV,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,KAAK;QAC3C,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,eAAe,GAAG,aAAa,CAAA;IAKrC,MAAM,eAAe,GACnB,qBAAqB;QACrB,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,UAAU,CAAA;IAEZ,OAAO;QACL,cAAc,EAAE,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC,QAAQ,EAAE;QAC9C,eAAe,EAAE,eAAe,CAAC,QAAQ,EAAE;QAC3C,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;QACjC,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE;KACxC,CAAA;AACH,CAAC"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { AccountResponse, AccountSummary, Position } from '@lifi/perps-types';
|
|
2
|
-
/**
|
|
3
|
-
* What the summed `collateralBalances` rows already contain, relative to the
|
|
4
|
-
* positions' locked margin and unrealized PnL:
|
|
5
|
-
*
|
|
6
|
-
* - `'free'` — free collateral only; locked margin and unrealized PnL are
|
|
7
|
-
* both carried by the positions. Available margin is the free collateral
|
|
8
|
-
* as-is; unrealized PnL is not counted toward it.
|
|
9
|
-
* - `'net'` — free collateral with unrealized PnL already marked in; only
|
|
10
|
-
* the locked margin is carried by the positions (e.g. a venue-reported
|
|
11
|
-
* available balance).
|
|
12
|
-
* - `'gross'` — locked margin included, unrealized PnL carried by the
|
|
13
|
-
* positions (e.g. spot holdings backing a unified account). The venue
|
|
14
|
-
* counts unrealized PnL toward buying power, so it is added to available
|
|
15
|
-
* margin on top of the gross collateral.
|
|
16
|
-
* - `'equity'` — total equity: locked margin AND unrealized PnL included
|
|
17
|
-
* (e.g. a venue-reported account value). Unrealized PnL is thus already
|
|
18
|
-
* counted toward available margin.
|
|
19
|
-
*
|
|
20
|
-
* @public
|
|
21
|
-
*/
|
|
22
|
-
export type CollateralSemantics = 'free' | 'net' | 'gross' | 'equity';
|
|
23
|
-
/**
|
|
24
|
-
* Roll an {@link AccountResponse} and its open positions up into an
|
|
25
|
-
* {@link AccountSummary}.
|
|
26
|
-
*
|
|
27
|
-
* @param semantics - How the collateral rows relate to the positions'
|
|
28
|
-
* locked margin and unrealized PnL; see {@link CollateralSemantics}.
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
export declare function summarizeAccount(account: AccountResponse, positions: Position[], semantics: CollateralSemantics): AccountSummary;
|
|
32
|
-
//# sourceMappingURL=accountSummary.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accountSummary.d.ts","sourceRoot":"","sources":["../../../src/utils/accountSummary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,QAAQ,EACT,MAAM,mBAAmB,CAAA;AAY1B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErE;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,SAAS,EAAE,mBAAmB,GAC7B,cAAc,CAuChB"}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { stringToFloat } from './parse.js';
|
|
2
|
-
const sumValueUsd = (balances) => balances.reduce((sum, b) => sum + stringToFloat(b.valueUsd), 0);
|
|
3
|
-
const sumMarginUsd = (balances) => balances.reduce((sum, b) => sum + stringToFloat(b.valueUsd) * (b.collateralWeight ?? 1), 0);
|
|
4
|
-
/**
|
|
5
|
-
* Roll an {@link AccountResponse} and its open positions up into an
|
|
6
|
-
* {@link AccountSummary}.
|
|
7
|
-
*
|
|
8
|
-
* @param semantics - How the collateral rows relate to the positions'
|
|
9
|
-
* locked margin and unrealized PnL; see {@link CollateralSemantics}.
|
|
10
|
-
* @public
|
|
11
|
-
*/
|
|
12
|
-
export function summarizeAccount(account, positions, semantics) {
|
|
13
|
-
let marginUsed = 0;
|
|
14
|
-
let unrealizedPnl = 0;
|
|
15
|
-
for (const p of positions) {
|
|
16
|
-
marginUsed += stringToFloat(p.marginUsed);
|
|
17
|
-
unrealizedPnl += stringToFloat(p.unrealizedPnl);
|
|
18
|
-
}
|
|
19
|
-
const collateral = sumValueUsd(account.collateralBalances);
|
|
20
|
-
// Margin-eligible collateral: each row's value scaled by its loan-to-value
|
|
21
|
-
// weight (1 when unset), so a haircut asset backs buying power at less than
|
|
22
|
-
// face while `portfolioValue` still reflects its full value.
|
|
23
|
-
const marginCollateral = sumMarginUsd(account.collateralBalances);
|
|
24
|
-
const balances = sumValueUsd(account.balances);
|
|
25
|
-
const withLocked = semantics === 'free' || semantics === 'net';
|
|
26
|
-
const grossCollateral = withLocked ? collateral + marginUsed : collateral;
|
|
27
|
-
const grossMarginCollateral = withLocked
|
|
28
|
-
? marginCollateral + marginUsed
|
|
29
|
-
: marginCollateral;
|
|
30
|
-
const equity = semantics === 'equity' || semantics === 'net'
|
|
31
|
-
? grossCollateral
|
|
32
|
-
: grossCollateral + unrealizedPnl;
|
|
33
|
-
// Buying power is equity net of locked margin, so unrealized PnL counts
|
|
34
|
-
// toward it. The `'free'` rows exclude uPnL from available margin, holding
|
|
35
|
-
// it purely as free collateral.
|
|
36
|
-
const availableMargin = grossMarginCollateral +
|
|
37
|
-
(semantics === 'gross' ? unrealizedPnl : 0) -
|
|
38
|
-
marginUsed;
|
|
39
|
-
return {
|
|
40
|
-
portfolioValue: (balances + equity).toString(),
|
|
41
|
-
availableMargin: availableMargin.toString(),
|
|
42
|
-
marginUsed: marginUsed.toString(),
|
|
43
|
-
unrealizedPnl: unrealizedPnl.toString(),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=accountSummary.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accountSummary.js","sourceRoot":"","sources":["../../../src/utils/accountSummary.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,WAAW,GAAG,CAAC,QAAqC,EAAU,EAAE,CACpE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AAEjE,MAAM,YAAY,GAAG,CAAC,QAAqC,EAAU,EAAE,CACrE,QAAQ,CAAC,MAAM,CACb,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,EACvE,CAAC,CACF,CAAA;AAwBH;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAwB,EACxB,SAAqB,EACrB,SAA8B;IAE9B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,UAAU,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;QACzC,aAAa,IAAI,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IACjD,CAAC;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC1D,2EAA2E;IAC3E,4EAA4E;IAC5E,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE9C,MAAM,UAAU,GAAG,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,KAAK,CAAA;IAC9D,MAAM,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;IACzE,MAAM,qBAAqB,GAAG,UAAU;QACtC,CAAC,CAAC,gBAAgB,GAAG,UAAU;QAC/B,CAAC,CAAC,gBAAgB,CAAA;IACpB,MAAM,MAAM,GACV,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,KAAK;QAC3C,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,eAAe,GAAG,aAAa,CAAA;IAErC,wEAAwE;IACxE,2EAA2E;IAC3E,gCAAgC;IAChC,MAAM,eAAe,GACnB,qBAAqB;QACrB,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,UAAU,CAAA;IAEZ,OAAO;QACL,cAAc,EAAE,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC,QAAQ,EAAE;QAC9C,eAAe,EAAE,eAAe,CAAC,QAAQ,EAAE;QAC3C,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;QACjC,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE;KACxC,CAAA;AACH,CAAC"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { AccountResponse, AccountSummary, Position } from '@lifi/perps-types';
|
|
2
|
-
/**
|
|
3
|
-
* What the summed `collateralBalances` rows already contain, relative to the
|
|
4
|
-
* positions' locked margin and unrealized PnL:
|
|
5
|
-
*
|
|
6
|
-
* - `'free'` — free collateral only; locked margin and unrealized PnL are
|
|
7
|
-
* both carried by the positions. Available margin is the free collateral
|
|
8
|
-
* as-is; unrealized PnL is not counted toward it.
|
|
9
|
-
* - `'net'` — free collateral with unrealized PnL already marked in; only
|
|
10
|
-
* the locked margin is carried by the positions (e.g. a venue-reported
|
|
11
|
-
* available balance).
|
|
12
|
-
* - `'gross'` — locked margin included, unrealized PnL carried by the
|
|
13
|
-
* positions (e.g. spot holdings backing a unified account). The venue
|
|
14
|
-
* counts unrealized PnL toward buying power, so it is added to available
|
|
15
|
-
* margin on top of the gross collateral.
|
|
16
|
-
* - `'equity'` — total equity: locked margin AND unrealized PnL included
|
|
17
|
-
* (e.g. a venue-reported account value). Unrealized PnL is thus already
|
|
18
|
-
* counted toward available margin.
|
|
19
|
-
*
|
|
20
|
-
* @public
|
|
21
|
-
*/
|
|
22
|
-
export type CollateralSemantics = 'free' | 'net' | 'gross' | 'equity';
|
|
23
|
-
/**
|
|
24
|
-
* Roll an {@link AccountResponse} and its open positions up into an
|
|
25
|
-
* {@link AccountSummary}.
|
|
26
|
-
*
|
|
27
|
-
* @param semantics - How the collateral rows relate to the positions'
|
|
28
|
-
* locked margin and unrealized PnL; see {@link CollateralSemantics}.
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
export declare function summarizeAccount(account: AccountResponse, positions: Position[], semantics: CollateralSemantics): AccountSummary;
|
|
32
|
-
//# sourceMappingURL=accountSummary.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accountSummary.d.ts","sourceRoot":"","sources":["../../../src/utils/accountSummary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,QAAQ,EACT,MAAM,mBAAmB,CAAA;AAY1B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErE;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,SAAS,EAAE,mBAAmB,GAC7B,cAAc,CAuChB"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AccountResponse,
|
|
3
|
-
AccountSummary,
|
|
4
|
-
Position,
|
|
5
|
-
} from '@lifi/perps-types'
|
|
6
|
-
import { stringToFloat } from './parse.js'
|
|
7
|
-
|
|
8
|
-
const sumValueUsd = (balances: AccountResponse['balances']): number =>
|
|
9
|
-
balances.reduce((sum, b) => sum + stringToFloat(b.valueUsd), 0)
|
|
10
|
-
|
|
11
|
-
const sumMarginUsd = (balances: AccountResponse['balances']): number =>
|
|
12
|
-
balances.reduce(
|
|
13
|
-
(sum, b) => sum + stringToFloat(b.valueUsd) * (b.collateralWeight ?? 1),
|
|
14
|
-
0
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* What the summed `collateralBalances` rows already contain, relative to the
|
|
19
|
-
* positions' locked margin and unrealized PnL:
|
|
20
|
-
*
|
|
21
|
-
* - `'free'` — free collateral only; locked margin and unrealized PnL are
|
|
22
|
-
* both carried by the positions. Available margin is the free collateral
|
|
23
|
-
* as-is; unrealized PnL is not counted toward it.
|
|
24
|
-
* - `'net'` — free collateral with unrealized PnL already marked in; only
|
|
25
|
-
* the locked margin is carried by the positions (e.g. a venue-reported
|
|
26
|
-
* available balance).
|
|
27
|
-
* - `'gross'` — locked margin included, unrealized PnL carried by the
|
|
28
|
-
* positions (e.g. spot holdings backing a unified account). The venue
|
|
29
|
-
* counts unrealized PnL toward buying power, so it is added to available
|
|
30
|
-
* margin on top of the gross collateral.
|
|
31
|
-
* - `'equity'` — total equity: locked margin AND unrealized PnL included
|
|
32
|
-
* (e.g. a venue-reported account value). Unrealized PnL is thus already
|
|
33
|
-
* counted toward available margin.
|
|
34
|
-
*
|
|
35
|
-
* @public
|
|
36
|
-
*/
|
|
37
|
-
export type CollateralSemantics = 'free' | 'net' | 'gross' | 'equity'
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Roll an {@link AccountResponse} and its open positions up into an
|
|
41
|
-
* {@link AccountSummary}.
|
|
42
|
-
*
|
|
43
|
-
* @param semantics - How the collateral rows relate to the positions'
|
|
44
|
-
* locked margin and unrealized PnL; see {@link CollateralSemantics}.
|
|
45
|
-
* @public
|
|
46
|
-
*/
|
|
47
|
-
export function summarizeAccount(
|
|
48
|
-
account: AccountResponse,
|
|
49
|
-
positions: Position[],
|
|
50
|
-
semantics: CollateralSemantics
|
|
51
|
-
): AccountSummary {
|
|
52
|
-
let marginUsed = 0
|
|
53
|
-
let unrealizedPnl = 0
|
|
54
|
-
for (const p of positions) {
|
|
55
|
-
marginUsed += stringToFloat(p.marginUsed)
|
|
56
|
-
unrealizedPnl += stringToFloat(p.unrealizedPnl)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const collateral = sumValueUsd(account.collateralBalances)
|
|
60
|
-
// Margin-eligible collateral: each row's value scaled by its loan-to-value
|
|
61
|
-
// weight (1 when unset), so a haircut asset backs buying power at less than
|
|
62
|
-
// face while `portfolioValue` still reflects its full value.
|
|
63
|
-
const marginCollateral = sumMarginUsd(account.collateralBalances)
|
|
64
|
-
const balances = sumValueUsd(account.balances)
|
|
65
|
-
|
|
66
|
-
const withLocked = semantics === 'free' || semantics === 'net'
|
|
67
|
-
const grossCollateral = withLocked ? collateral + marginUsed : collateral
|
|
68
|
-
const grossMarginCollateral = withLocked
|
|
69
|
-
? marginCollateral + marginUsed
|
|
70
|
-
: marginCollateral
|
|
71
|
-
const equity =
|
|
72
|
-
semantics === 'equity' || semantics === 'net'
|
|
73
|
-
? grossCollateral
|
|
74
|
-
: grossCollateral + unrealizedPnl
|
|
75
|
-
|
|
76
|
-
// Buying power is equity net of locked margin, so unrealized PnL counts
|
|
77
|
-
// toward it. The `'free'` rows exclude uPnL from available margin, holding
|
|
78
|
-
// it purely as free collateral.
|
|
79
|
-
const availableMargin =
|
|
80
|
-
grossMarginCollateral +
|
|
81
|
-
(semantics === 'gross' ? unrealizedPnl : 0) -
|
|
82
|
-
marginUsed
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
portfolioValue: (balances + equity).toString(),
|
|
86
|
-
availableMargin: availableMargin.toString(),
|
|
87
|
-
marginUsed: marginUsed.toString(),
|
|
88
|
-
unrealizedPnl: unrealizedPnl.toString(),
|
|
89
|
-
}
|
|
90
|
-
}
|