@lifi/perps-sdk-provider-lighter 7.0.0 → 8.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 +60 -27
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +33 -2
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/types/account.d.ts +1 -0
- package/dist/cjs/types/account.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/decimal.d.ts +5 -0
- package/dist/cjs/utils/decimal.d.ts.map +1 -0
- package/dist/cjs/utils/decimal.js +38 -0
- package/dist/cjs/utils/decimal.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +3 -1
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +7 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/mapOpenPositions.d.ts +2 -2
- package/dist/cjs/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/cjs/utils/mapOpenPositions.js.map +1 -1
- package/dist/cjs/utils/mapPosition.d.ts +3 -2
- package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
- package/dist/cjs/utils/mapPosition.js +30 -9
- package/dist/cjs/utils/mapPosition.js.map +1 -1
- package/dist/cjs/utils/transferMargin.d.ts +3 -0
- package/dist/cjs/utils/transferMargin.d.ts.map +1 -0
- package/dist/cjs/utils/transferMargin.js +17 -0
- package/dist/cjs/utils/transferMargin.js.map +1 -0
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +20 -9
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +49 -11
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountSummary.d.ts +5 -4
- package/dist/esm/accountSummary.d.ts.map +1 -1
- package/dist/esm/accountSummary.js +35 -6
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/types/account.d.ts +3 -0
- package/dist/esm/types/account.d.ts.map +1 -1
- package/dist/esm/types/ws.d.ts +9 -1
- package/dist/esm/types/ws.d.ts.map +1 -1
- package/dist/esm/utils/decimal.d.ts +15 -0
- package/dist/esm/utils/decimal.d.ts.map +1 -0
- package/dist/esm/utils/decimal.js +39 -0
- package/dist/esm/utils/decimal.js.map +1 -0
- package/dist/esm/utils/index.d.ts +3 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +3 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/mapOpenPositions.d.ts +2 -2
- package/dist/esm/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/esm/utils/mapOpenPositions.js.map +1 -1
- package/dist/esm/utils/mapPosition.d.ts +10 -2
- package/dist/esm/utils/mapPosition.d.ts.map +1 -1
- package/dist/esm/utils/mapPosition.js +32 -11
- package/dist/esm/utils/mapPosition.js.map +1 -1
- package/dist/esm/utils/transferMargin.d.ts +11 -0
- package/dist/esm/utils/transferMargin.d.ts.map +1 -0
- package/dist/esm/utils/transferMargin.js +22 -0
- package/dist/esm/utils/transferMargin.js.map +1 -0
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +24 -13
- package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountSummary.d.ts +5 -4
- package/dist/types/accountSummary.d.ts.map +1 -1
- package/dist/types/types/account.d.ts +3 -0
- package/dist/types/types/account.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +9 -1
- package/dist/types/types/ws.d.ts.map +1 -1
- package/dist/types/utils/decimal.d.ts +15 -0
- package/dist/types/utils/decimal.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/mapOpenPositions.d.ts +2 -2
- package/dist/types/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/types/utils/mapPosition.d.ts +10 -2
- package/dist/types/utils/mapPosition.d.ts.map +1 -1
- package/dist/types/utils/transferMargin.d.ts +11 -0
- package/dist/types/utils/transferMargin.d.ts.map +1 -0
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +75 -9
- package/src/accountSummary.ts +38 -6
- package/src/types/account.ts +3 -0
- package/src/types/ws.ts +9 -1
- package/src/utils/decimal.ts +52 -0
- package/src/utils/index.ts +3 -1
- package/src/utils/mapOpenPositions.ts +2 -2
- package/src/utils/mapPosition.ts +40 -14
- package/src/utils/transferMargin.ts +35 -0
- package/src/websocket/LighterWsProvider.ts +41 -11
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PerpsError } from '@lifi/perps-sdk'
|
|
2
|
+
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
3
|
+
import Big from 'big.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Exact-decimal parse of a venue decimal string; null when absent or
|
|
7
|
+
* unparsable, so callers can gate on presence before deriving.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export const toBigOrNull = (value: string | undefined): Big | null => {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return new Big(value)
|
|
16
|
+
} catch {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a required venue decimal and identify the bad field on failure.
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export const toRequiredBig = (
|
|
26
|
+
value: string | undefined,
|
|
27
|
+
field: string
|
|
28
|
+
): Big => {
|
|
29
|
+
const parsed = toBigOrNull(value)
|
|
30
|
+
if (parsed !== null) {
|
|
31
|
+
return parsed
|
|
32
|
+
}
|
|
33
|
+
throw new PerpsError(
|
|
34
|
+
PerpsErrorCode.SDKError,
|
|
35
|
+
`Lighter field \`${field}\` is not a valid decimal.`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Parse a required venue decimal that must be greater than zero. */
|
|
40
|
+
export const toPositiveRequiredBig = (
|
|
41
|
+
value: string | undefined,
|
|
42
|
+
field: string
|
|
43
|
+
): Big => {
|
|
44
|
+
const parsed = toRequiredBig(value, field)
|
|
45
|
+
if (parsed.lte(0)) {
|
|
46
|
+
throw new PerpsError(
|
|
47
|
+
PerpsErrorCode.SDKError,
|
|
48
|
+
`Lighter field \`${field}\` must be greater than zero.`
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return parsed
|
|
52
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
} from './activityCursor.js'
|
|
10
10
|
export { LighterApiClient } from './apiClient.js'
|
|
11
11
|
export { assetMarginModeInt, isAssetMarginEnabled } from './assetCollateral.js'
|
|
12
|
+
export { toBigOrNull, toRequiredBig } from './decimal.js'
|
|
12
13
|
export { fetchDetailedAccount } from './fetchDetailedAccount.js'
|
|
13
14
|
export { lighterAsset } from './lighterAsset.js'
|
|
14
15
|
export { estimateLiquidationPrice } from './liquidation.js'
|
|
@@ -24,10 +25,11 @@ export {
|
|
|
24
25
|
mapStatusReason,
|
|
25
26
|
mapTriggerOrder,
|
|
26
27
|
} from './mapOrder.js'
|
|
27
|
-
export { mapPosition } from './mapPosition.js'
|
|
28
|
+
export { leverageFromImf, mapPosition } from './mapPosition.js'
|
|
28
29
|
export { mapInterval } from './ohlcvInterval.js'
|
|
29
30
|
export { formatOrderPrice, formatOrderSize } from './orderFormatting.js'
|
|
30
31
|
export { toIsoFromMs, toIsoFromSeconds } from './time.js'
|
|
32
|
+
export { positionMarginConstraints } from './transferMargin.js'
|
|
31
33
|
export {
|
|
32
34
|
leverageToFraction,
|
|
33
35
|
mapOrderTypeToInt,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
|
|
2
2
|
import type { LtAccountPosition } from '../types/index.js'
|
|
3
3
|
import { mapPosition } from './mapPosition.js'
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ import { mapPosition } from './mapPosition.js'
|
|
|
11
11
|
*/
|
|
12
12
|
export const mapOpenPositions = (
|
|
13
13
|
positions: LtAccountPosition[],
|
|
14
|
-
resolveMarket: (marketId: number) =>
|
|
14
|
+
resolveMarket: (marketId: number) => PerpsMarketDisplay
|
|
15
15
|
): Position[] =>
|
|
16
16
|
positions
|
|
17
17
|
.filter((p) => Number.parseFloat(p.position) !== 0)
|
package/src/utils/mapPosition.ts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
|
|
2
2
|
import { MarginMode, PositionSide } from '@lifi/perps-types'
|
|
3
|
+
import Big from 'big.js'
|
|
3
4
|
import type { LtAccountPosition } from '../types/index.js'
|
|
4
5
|
import { LT_MARGIN_MODE_ISOLATED } from '../types/index.js'
|
|
6
|
+
import { toPositiveRequiredBig, toRequiredBig } from './decimal.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Display leverage from an IMF percent string: `100 / IMF` in exact decimal
|
|
10
|
+
* arithmetic before conversion to `number`. This value is for displaying the
|
|
11
|
+
* venue setting; provider risk calculations consume the original decimal IMF
|
|
12
|
+
* instead. `undefined` for a non-positive or unparsable IMF.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export const leverageFromImf = (imf: string): number | undefined => {
|
|
16
|
+
let parsed: Big
|
|
17
|
+
try {
|
|
18
|
+
parsed = new Big(imf)
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
if (parsed.lte(0)) {
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
return new Big(100).div(parsed).toNumber()
|
|
26
|
+
}
|
|
5
27
|
|
|
6
28
|
/**
|
|
7
29
|
* Map a raw Lighter account position to the generic Position type.
|
|
@@ -10,33 +32,37 @@ import { LT_MARGIN_MODE_ISOLATED } from '../types/index.js'
|
|
|
10
32
|
*/
|
|
11
33
|
export const mapPosition = (
|
|
12
34
|
pos: LtAccountPosition,
|
|
13
|
-
market:
|
|
35
|
+
market: PerpsMarketDisplay
|
|
14
36
|
): Position => {
|
|
15
|
-
const size =
|
|
37
|
+
const size = toRequiredBig(pos.position, 'position')
|
|
16
38
|
const isIsolated = pos.margin_mode === LT_MARGIN_MODE_ISOLATED
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
39
|
+
const positionValue = toRequiredBig(
|
|
40
|
+
pos.position_value,
|
|
41
|
+
'position_value'
|
|
42
|
+
).abs()
|
|
43
|
+
const imf = toPositiveRequiredBig(
|
|
44
|
+
pos.initial_margin_fraction,
|
|
45
|
+
'initial_margin_fraction'
|
|
46
|
+
)
|
|
47
|
+
const initialMarginRequirement = positionValue.times(imf).div(100)
|
|
23
48
|
const marginUsed = isIsolated
|
|
24
49
|
? pos.allocated_margin
|
|
25
|
-
:
|
|
50
|
+
: initialMarginRequirement.toFixed()
|
|
26
51
|
|
|
27
52
|
return {
|
|
28
53
|
market,
|
|
29
54
|
side: pos.sign >= 0 ? PositionSide.LONG : PositionSide.SHORT,
|
|
30
|
-
size:
|
|
55
|
+
size: size.abs().toFixed(),
|
|
31
56
|
entryPrice: pos.avg_entry_price,
|
|
32
57
|
markPrice:
|
|
33
|
-
|
|
58
|
+
positionValue.eq(0) || size.eq(0)
|
|
34
59
|
? '0'
|
|
35
|
-
: (
|
|
60
|
+
: positionValue.div(size.abs()).toFixed(),
|
|
36
61
|
liquidationPrice: pos.liquidation_price,
|
|
37
62
|
unrealizedPnl: pos.unrealized_pnl,
|
|
38
|
-
leverage:
|
|
63
|
+
leverage: leverageFromImf(pos.initial_margin_fraction) ?? 1,
|
|
39
64
|
marginUsed,
|
|
65
|
+
initialMarginRequirement: initialMarginRequirement.toFixed(),
|
|
40
66
|
marginMode: isIsolated ? MarginMode.ISOLATED : MarginMode.CROSS,
|
|
41
67
|
}
|
|
42
68
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MarginMode,
|
|
3
|
+
type Position,
|
|
4
|
+
PositionMarginAdjustment,
|
|
5
|
+
type PositionMarginConstraints,
|
|
6
|
+
} from '@lifi/perps-types'
|
|
7
|
+
import { toPositiveRequiredBig } from './decimal.js'
|
|
8
|
+
|
|
9
|
+
const AMOUNT_INCREMENT = '0.000001'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lighter's exact isolated-margin requirement. Lighter publishes no separate
|
|
13
|
+
* notional floor, so the provider-normalized initial-margin requirement is
|
|
14
|
+
* the retained amount.
|
|
15
|
+
*
|
|
16
|
+
* @see https://docs.lighter.xyz/trading/liquidations-and-llp-insurance-fund
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export function positionMarginConstraints(
|
|
20
|
+
position: Position
|
|
21
|
+
): PositionMarginConstraints | undefined {
|
|
22
|
+
if (
|
|
23
|
+
position.marginMode !== MarginMode.ISOLATED ||
|
|
24
|
+
position.market.positionMarginAdjustment === PositionMarginAdjustment.NONE
|
|
25
|
+
) {
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
minimumMarginRequirement: toPositiveRequiredBig(
|
|
30
|
+
position.initialMarginRequirement,
|
|
31
|
+
'initialMarginRequirement'
|
|
32
|
+
).toFixed(),
|
|
33
|
+
amountIncrement: AMOUNT_INCREMENT,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ReconnectingWebSocket,
|
|
10
10
|
resolveRetryPolicy,
|
|
11
11
|
resolveSubscribeQuote,
|
|
12
|
+
toPerpsMarketDisplay,
|
|
12
13
|
WsProviderBase,
|
|
13
14
|
type WsProviderFactory,
|
|
14
15
|
wsLog,
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
mapFill,
|
|
52
53
|
mapMarketContext,
|
|
53
54
|
mapPosition,
|
|
55
|
+
toRequiredBig,
|
|
54
56
|
} from '../utils/index.js'
|
|
55
57
|
|
|
56
58
|
// Public channels: `marketsContext` (market_stats/all + spot_market_stats/all),
|
|
@@ -588,23 +590,51 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
588
590
|
return
|
|
589
591
|
}
|
|
590
592
|
const stats = msg.stats
|
|
591
|
-
|
|
592
|
-
const available = Number.parseFloat(stats?.available_balance ?? '')
|
|
593
|
-
if (!Number.isFinite(portfolio) || !Number.isFinite(available)) {
|
|
593
|
+
if (stats === undefined) {
|
|
594
594
|
return
|
|
595
595
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
596
|
+
|
|
597
|
+
const portfolio = toRequiredBig(
|
|
598
|
+
stats.portfolio_value,
|
|
599
|
+
'stats.portfolio_value'
|
|
600
|
+
)
|
|
601
|
+
const collateral = toRequiredBig(stats.collateral, 'stats.collateral')
|
|
602
|
+
|
|
603
|
+
let available: Big
|
|
604
|
+
let marginUsed: Big
|
|
605
|
+
if (stats.cross_stats === undefined) {
|
|
606
|
+
// Compatibility with legacy gateways that omitted the entire
|
|
607
|
+
// cross_stats object.
|
|
608
|
+
available = toRequiredBig(
|
|
609
|
+
stats.available_balance,
|
|
610
|
+
'stats.available_balance'
|
|
611
|
+
)
|
|
612
|
+
marginUsed = portfolio.minus(available)
|
|
613
|
+
} else {
|
|
614
|
+
const crossCollateral = toRequiredBig(
|
|
615
|
+
stats.cross_stats.collateral,
|
|
616
|
+
'stats.cross_stats.collateral'
|
|
617
|
+
)
|
|
618
|
+
const crossPortfolio = toRequiredBig(
|
|
619
|
+
stats.cross_stats.portfolio_value,
|
|
620
|
+
'stats.cross_stats.portfolio_value'
|
|
621
|
+
)
|
|
622
|
+
available = toRequiredBig(
|
|
623
|
+
stats.cross_stats.available_balance,
|
|
624
|
+
'stats.cross_stats.available_balance'
|
|
625
|
+
)
|
|
626
|
+
const isolatedMargin = collateral.minus(crossCollateral)
|
|
627
|
+
const crossMargin = crossPortfolio.minus(available)
|
|
628
|
+
marginUsed = isolatedMargin.plus(crossMargin)
|
|
629
|
+
}
|
|
630
|
+
|
|
599
631
|
this.emit(`accountSummary:${address}`, {
|
|
600
632
|
channel: 'accountSummary',
|
|
601
633
|
data: {
|
|
602
634
|
portfolioValue: portfolio.toString(),
|
|
603
635
|
availableMargin: available.toString(),
|
|
604
|
-
marginUsed:
|
|
605
|
-
unrealizedPnl:
|
|
606
|
-
? (portfolio - collateral).toString()
|
|
607
|
-
: '0',
|
|
636
|
+
marginUsed: marginUsed.toString(),
|
|
637
|
+
unrealizedPnl: portfolio.minus(collateral).toString(),
|
|
608
638
|
},
|
|
609
639
|
})
|
|
610
640
|
}
|
|
@@ -649,7 +679,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
649
679
|
} else {
|
|
650
680
|
const market = this.registry?.get(String(p.market_id))
|
|
651
681
|
if (market) {
|
|
652
|
-
state.set(p.market_id, mapPosition(p, market))
|
|
682
|
+
state.set(p.market_id, mapPosition(p, toPerpsMarketDisplay(market)))
|
|
653
683
|
}
|
|
654
684
|
}
|
|
655
685
|
}
|