@lifi/perps-sdk-provider-lighter 27.0.0 → 29.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 +38 -13
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +14 -7
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/availableToTrade.d.ts +3 -0
- package/dist/cjs/availableToTrade.d.ts.map +1 -0
- package/dist/cjs/availableToTrade.js +38 -0
- package/dist/cjs/availableToTrade.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +2 -2
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +2 -1
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/spotPrice.d.ts +4 -0
- package/dist/cjs/utils/spotPrice.d.ts.map +1 -0
- package/dist/cjs/utils/spotPrice.js +24 -0
- package/dist/cjs/utils/spotPrice.js.map +1 -0
- package/dist/cjs/utils/transferMargin.d.ts +2 -2
- package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
- package/dist/cjs/utils/transferMargin.js +33 -8
- package/dist/cjs/utils/transferMargin.js.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +5 -5
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +42 -17
- 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 +19 -13
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/availableToTrade.d.ts +15 -0
- package/dist/esm/availableToTrade.d.ts.map +1 -0
- package/dist/esm/availableToTrade.js +45 -0
- package/dist/esm/availableToTrade.js.map +1 -0
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +2 -1
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/spotPrice.d.ts +14 -0
- package/dist/esm/utils/spotPrice.d.ts.map +1 -0
- package/dist/esm/utils/spotPrice.js +30 -0
- package/dist/esm/utils/spotPrice.js.map +1 -0
- package/dist/esm/utils/transferMargin.d.ts +11 -5
- package/dist/esm/utils/transferMargin.d.ts.map +1 -1
- package/dist/esm/utils/transferMargin.js +39 -11
- package/dist/esm/utils/transferMargin.js.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +5 -5
- 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/availableToTrade.d.ts +15 -0
- package/dist/types/availableToTrade.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/spotPrice.d.ts +14 -0
- package/dist/types/utils/spotPrice.d.ts.map +1 -0
- package/dist/types/utils/transferMargin.d.ts +11 -5
- package/dist/types/utils/transferMargin.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +70 -27
- package/src/accountSummary.ts +24 -16
- package/src/availableToTrade.ts +61 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/mapOrder.ts +2 -1
- package/src/utils/spotPrice.ts +39 -0
- package/src/utils/transferMargin.ts +52 -14
- package/src/websocket/LighterWsProvider.ts +5 -5
package/src/LighterProvider.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
explorerTxUrlFromBase,
|
|
6
6
|
getAssetRegistry,
|
|
7
7
|
getMarketRegistry,
|
|
8
|
+
getMarketsContext,
|
|
8
9
|
getProviders,
|
|
9
10
|
isActiveOrderStatus,
|
|
10
11
|
localStorageAdapter,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type ProviderAccountExistsParams,
|
|
15
16
|
type ProviderGetAccountParams,
|
|
16
17
|
type ProviderGetActivityParams,
|
|
18
|
+
type ProviderGetAvailableToTradeParams,
|
|
17
19
|
type ProviderGetDepositFlowParams,
|
|
18
20
|
type ProviderGetFillsParams,
|
|
19
21
|
type ProviderGetMarketSettingsParams,
|
|
@@ -40,6 +42,7 @@ import type {
|
|
|
40
42
|
ActionStep,
|
|
41
43
|
ActivitiesResponse,
|
|
42
44
|
ActivityItem,
|
|
45
|
+
AvailableToTrade,
|
|
43
46
|
Balance,
|
|
44
47
|
Fill,
|
|
45
48
|
FillsResponse,
|
|
@@ -63,9 +66,11 @@ import {
|
|
|
63
66
|
MarginMode,
|
|
64
67
|
PerpsErrorCode,
|
|
65
68
|
} from '@lifi/perps-types'
|
|
69
|
+
import Big from 'big.js'
|
|
66
70
|
import type { Address } from 'viem'
|
|
67
71
|
import { projectLighterConfigSettings } from './accountConfig.js'
|
|
68
72
|
import { getAccountSummary } from './accountSummary.js'
|
|
73
|
+
import { lighterAvailableToTrade } from './availableToTrade.js'
|
|
69
74
|
import {
|
|
70
75
|
DEFAULT_TRADES_LIMIT,
|
|
71
76
|
LIGHTER_ALL_MARKETS_WILDCARD,
|
|
@@ -138,7 +143,7 @@ import {
|
|
|
138
143
|
mapOpenPositions,
|
|
139
144
|
mapOrder,
|
|
140
145
|
mapPortfolioHistory,
|
|
141
|
-
|
|
146
|
+
positionRemovableMargin,
|
|
142
147
|
toBigOrNull,
|
|
143
148
|
toIsoFromMs,
|
|
144
149
|
toIsoFromSeconds,
|
|
@@ -148,6 +153,7 @@ import {
|
|
|
148
153
|
fetchRegisteredApiKey,
|
|
149
154
|
normalizeLighterPublicKey,
|
|
150
155
|
} from './utils/registeredApiKey.js'
|
|
156
|
+
import { spotPriceByAssetId } from './utils/spotPrice.js'
|
|
151
157
|
import { isPlaceholderTxHash } from './utils/txHash.js'
|
|
152
158
|
import { wireList } from './utils/wireList.js'
|
|
153
159
|
|
|
@@ -781,7 +787,7 @@ export const createLighterProvider = (
|
|
|
781
787
|
}
|
|
782
788
|
}
|
|
783
789
|
|
|
784
|
-
|
|
790
|
+
const plugin: LighterPerpsProvider = {
|
|
785
791
|
type: providerKey,
|
|
786
792
|
|
|
787
793
|
internalSetupActions: [ActionType.SET_REFERRAL],
|
|
@@ -825,11 +831,12 @@ export const createLighterProvider = (
|
|
|
825
831
|
|
|
826
832
|
const registry = getMarketRegistry(requireClient(), providerKey)
|
|
827
833
|
const assetRegistry = getAssetRegistry(requireClient(), providerKey)
|
|
828
|
-
const [{ providers }, , , limitsResult, storedReadOnlyToken] =
|
|
834
|
+
const [{ providers }, , , { prices }, limitsResult, storedReadOnlyToken] =
|
|
829
835
|
await Promise.all([
|
|
830
836
|
getProviders(requireClient()),
|
|
831
837
|
registry.sync(),
|
|
832
838
|
assetRegistry.sync(),
|
|
839
|
+
getMarketsContext(requireClient(), { provider: providerKey }, opts),
|
|
833
840
|
token === undefined
|
|
834
841
|
? Promise.resolve(undefined)
|
|
835
842
|
: retryOnRevoked(opts, params.address, token, (resolvedToken) =>
|
|
@@ -844,12 +851,12 @@ export const createLighterProvider = (
|
|
|
844
851
|
)
|
|
845
852
|
|
|
846
853
|
const totalMarginUsed = positions.reduce(
|
|
847
|
-
(sum, p) => sum
|
|
848
|
-
0
|
|
854
|
+
(sum, p) => sum.plus(toRequiredBig(p.marginUsed, 'marginUsed')),
|
|
855
|
+
new Big(0)
|
|
849
856
|
)
|
|
850
857
|
const totalUnrealizedPnl = positions.reduce(
|
|
851
|
-
(sum, p) => sum
|
|
852
|
-
0
|
|
858
|
+
(sum, p) => sum.plus(toRequiredBig(p.unrealizedPnl, 'unrealizedPnl')),
|
|
859
|
+
new Big(0)
|
|
853
860
|
)
|
|
854
861
|
|
|
855
862
|
const instanceMeta = providers.find((p) => p.key === providerKey)
|
|
@@ -883,23 +890,39 @@ export const createLighterProvider = (
|
|
|
883
890
|
]
|
|
884
891
|
: []
|
|
885
892
|
// Spot token holdings — non-collateral. The instance's settlement asset
|
|
886
|
-
// is valued 1:1;
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
893
|
+
// is valued 1:1; an asset no spot market prices keeps a zero USD value.
|
|
894
|
+
const heldAssets = account.assets.filter((asset) =>
|
|
895
|
+
toRequiredBig(asset.balance, 'balance').gt(0)
|
|
896
|
+
)
|
|
897
|
+
const spotPrices = spotPriceByAssetId(
|
|
898
|
+
registry.markets,
|
|
899
|
+
spotCategoryId,
|
|
900
|
+
prices,
|
|
901
|
+
new Set(
|
|
902
|
+
heldAssets
|
|
903
|
+
.filter((a) => a.asset_id !== collateral.assetIndex)
|
|
904
|
+
.map((a) => String(a.asset_id))
|
|
905
|
+
)
|
|
906
|
+
)
|
|
907
|
+
const balances: Balance[] = heldAssets.map((a) => {
|
|
908
|
+
const assetId = String(a.asset_id)
|
|
909
|
+
const price =
|
|
910
|
+
a.asset_id === collateral.assetIndex
|
|
911
|
+
? new Big(1)
|
|
912
|
+
: spotPrices.get(assetId)
|
|
913
|
+
return {
|
|
914
|
+
categoryId: spotCategoryId,
|
|
915
|
+
asset:
|
|
916
|
+
assetRegistry.get(assetId) ??
|
|
917
|
+
lighterAsset(assetId, a.symbol, providerKey),
|
|
918
|
+
units: a.balance,
|
|
919
|
+
valueUsd:
|
|
920
|
+
price === undefined
|
|
921
|
+
? '0'
|
|
922
|
+
: toRequiredBig(a.balance, 'balance').times(price).toFixed(),
|
|
923
|
+
...(price === undefined ? {} : { price: price.toFixed() }),
|
|
924
|
+
}
|
|
925
|
+
})
|
|
903
926
|
|
|
904
927
|
const assetCollateral = account.assets.flatMap((a) =>
|
|
905
928
|
a.margin_mode === undefined
|
|
@@ -941,8 +964,8 @@ export const createLighterProvider = (
|
|
|
941
964
|
balances,
|
|
942
965
|
collateralBalances,
|
|
943
966
|
positions,
|
|
944
|
-
marginUsed: totalMarginUsed.
|
|
945
|
-
unrealizedPnl: totalUnrealizedPnl.
|
|
967
|
+
marginUsed: totalMarginUsed.toFixed(),
|
|
968
|
+
unrealizedPnl: totalUnrealizedPnl.toFixed(),
|
|
946
969
|
feeTier:
|
|
947
970
|
limitsResult === undefined
|
|
948
971
|
? ZERO_FEE_TIER
|
|
@@ -1005,6 +1028,25 @@ export const createLighterProvider = (
|
|
|
1005
1028
|
}
|
|
1006
1029
|
},
|
|
1007
1030
|
|
|
1031
|
+
/**
|
|
1032
|
+
* Lighter publishes no per-market figure, so this derives one from the
|
|
1033
|
+
* account's `available_balance` and its open position on the market.
|
|
1034
|
+
*/
|
|
1035
|
+
async getAvailableToTrade(
|
|
1036
|
+
params: ProviderGetAvailableToTradeParams,
|
|
1037
|
+
opts?: SDKRequestOptions
|
|
1038
|
+
): Promise<AvailableToTrade | undefined> {
|
|
1039
|
+
const registry = getMarketRegistry(requireClient(), providerKey)
|
|
1040
|
+
await registry.sync()
|
|
1041
|
+
const market = registry.require(params.marketId)
|
|
1042
|
+
if (market.categoryId === LIGHTER_SPOT_CATEGORY_ID) {
|
|
1043
|
+
return undefined
|
|
1044
|
+
}
|
|
1045
|
+
const account = await plugin.getAccount({ address: params.address }, opts)
|
|
1046
|
+
const { availableMargin } = getAccountSummary(account, account.positions)
|
|
1047
|
+
return lighterAvailableToTrade(market, availableMargin, account.positions)
|
|
1048
|
+
},
|
|
1049
|
+
|
|
1008
1050
|
/**
|
|
1009
1051
|
* Lighter reports a market's margin mode and leverage only on the
|
|
1010
1052
|
* account's position row, so a market the account never touched (or a
|
|
@@ -1603,7 +1645,7 @@ export const createLighterProvider = (
|
|
|
1603
1645
|
|
|
1604
1646
|
estimateLiquidationPrice,
|
|
1605
1647
|
|
|
1606
|
-
|
|
1648
|
+
positionRemovableMargin,
|
|
1607
1649
|
|
|
1608
1650
|
projectConfig(
|
|
1609
1651
|
config: AccountConfig,
|
|
@@ -1675,6 +1717,7 @@ export const createLighterProvider = (
|
|
|
1675
1717
|
)
|
|
1676
1718
|
},
|
|
1677
1719
|
}
|
|
1720
|
+
return plugin
|
|
1678
1721
|
}
|
|
1679
1722
|
|
|
1680
1723
|
/**
|
package/src/accountSummary.ts
CHANGED
|
@@ -7,23 +7,29 @@ import type {
|
|
|
7
7
|
} from '@lifi/perps-types'
|
|
8
8
|
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
9
9
|
import Big from 'big.js'
|
|
10
|
+
import { LIGHTER_PROVIDER_KEY, LIGHTER_RH_PROVIDER_KEY } from './constants.js'
|
|
10
11
|
import { toRequiredBig } from './utils/decimal.js'
|
|
11
12
|
|
|
12
13
|
const lighterConfig = (account: AccountResponse): LighterAccountConfig => {
|
|
13
|
-
|
|
14
|
+
const { config } = account
|
|
15
|
+
if (
|
|
16
|
+
config.provider !== LIGHTER_PROVIDER_KEY &&
|
|
17
|
+
config.provider !== LIGHTER_RH_PROVIDER_KEY
|
|
18
|
+
) {
|
|
14
19
|
throw new PerpsError(
|
|
15
20
|
PerpsErrorCode.SDKError,
|
|
16
|
-
`Lighter account summary received a '${
|
|
21
|
+
`Lighter account summary received a '${config.provider}' account config`
|
|
17
22
|
)
|
|
18
23
|
}
|
|
19
|
-
return
|
|
24
|
+
return config
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/**
|
|
23
|
-
* Roll a Lighter account up into an {@link AccountSummary}
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
28
|
+
* Roll a Lighter account up into an {@link AccountSummary}. `totalAssetValue`
|
|
29
|
+
* is perps-route equity and excludes the spot route, so `portfolioValue` adds
|
|
30
|
+
* every spot `balances` row, settlement included; the `collateralBalances` row
|
|
31
|
+
* is buying power already inside `totalAssetValue`. The positions supply only
|
|
32
|
+
* the margin and PnL breakdown.
|
|
27
33
|
*
|
|
28
34
|
* @throws {PerpsError} `SDKError` when the account is not a Lighter one.
|
|
29
35
|
* @public
|
|
@@ -41,18 +47,20 @@ export function getAccountSummary(
|
|
|
41
47
|
unrealizedPnl = unrealizedPnl.plus(position.unrealizedPnl)
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
let portfolioValue = toRequiredBig(config.totalAssetValue, 'totalAssetValue')
|
|
51
|
+
for (const balance of account.balances) {
|
|
52
|
+
portfolioValue = portfolioValue.plus(
|
|
53
|
+
toRequiredBig(balance.valueUsd, 'valueUsd')
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
return {
|
|
45
|
-
|
|
46
|
-
// spot `balances` rows must not be added on top of it.
|
|
47
|
-
portfolioValue: toRequiredBig(
|
|
48
|
-
config.totalAssetValue,
|
|
49
|
-
'totalAssetValue'
|
|
50
|
-
).toString(),
|
|
58
|
+
portfolioValue: portfolioValue.toFixed(),
|
|
51
59
|
availableMargin: toRequiredBig(
|
|
52
60
|
config.availableBalance,
|
|
53
61
|
'availableBalance'
|
|
54
|
-
).
|
|
55
|
-
marginUsed: marginUsed.
|
|
56
|
-
unrealizedPnl: unrealizedPnl.
|
|
62
|
+
).toFixed(),
|
|
63
|
+
marginUsed: marginUsed.toFixed(),
|
|
64
|
+
unrealizedPnl: unrealizedPnl.toFixed(),
|
|
57
65
|
}
|
|
58
66
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { toAssetDisplay } from '@lifi/perps-sdk'
|
|
2
|
+
import type { AvailableToTrade, Market, Position } from '@lifi/perps-types'
|
|
3
|
+
import { MarginMode, PositionSide } from '@lifi/perps-types'
|
|
4
|
+
import Big from 'big.js'
|
|
5
|
+
import { toRequiredBig } from './utils/decimal.js'
|
|
6
|
+
|
|
7
|
+
const atLeastZero = (value: Big): Big => (value.lt(0) ? new Big(0) : value)
|
|
8
|
+
|
|
9
|
+
// Lighter's `available_balance` already nets cross unrealized PnL and counts
|
|
10
|
+
// an isolated position's equity above its IMR, so only the rest is released.
|
|
11
|
+
const releasedOnClose = (position: Position): Big => {
|
|
12
|
+
const marginUsed = toRequiredBig(position.marginUsed, 'marginUsed')
|
|
13
|
+
if (position.marginMode !== MarginMode.ISOLATED) {
|
|
14
|
+
return marginUsed
|
|
15
|
+
}
|
|
16
|
+
const equity = marginUsed.plus(
|
|
17
|
+
toRequiredBig(position.unrealizedPnl, 'unrealizedPnl')
|
|
18
|
+
)
|
|
19
|
+
const imr = toRequiredBig(
|
|
20
|
+
position.initialMarginRequirement,
|
|
21
|
+
'initialMarginRequirement'
|
|
22
|
+
)
|
|
23
|
+
return equity.lt(imr) ? equity : imr
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The amounts the account can still buy and sell on one Lighter perps
|
|
28
|
+
* market. The side that adds to an open position on `market` gets
|
|
29
|
+
* `availableMargin`. The side that reduces or flips it gets the position's
|
|
30
|
+
* initial margin requirement, which the closing part of the order consumes,
|
|
31
|
+
* plus `availableMargin` and the margin that closing releases, floored at 0.
|
|
32
|
+
* A deficit therefore never cuts the closing part. Both amounts are at least 0.
|
|
33
|
+
*
|
|
34
|
+
* @param availableMargin - The account's {@link AccountSummary.availableMargin}.
|
|
35
|
+
* @throws {PerpsError} `SDKError` when a decimal the formula reads is malformed.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export const lighterAvailableToTrade = (
|
|
39
|
+
market: Market,
|
|
40
|
+
availableMargin: string,
|
|
41
|
+
positions: readonly Position[]
|
|
42
|
+
): AvailableToTrade => {
|
|
43
|
+
const available = toRequiredBig(availableMargin, 'availableMargin')
|
|
44
|
+
const adding = atLeastZero(available)
|
|
45
|
+
const position = positions.find((p) => p.market.id === market.id)
|
|
46
|
+
const reducing =
|
|
47
|
+
position === undefined
|
|
48
|
+
? adding
|
|
49
|
+
: toRequiredBig(
|
|
50
|
+
position.initialMarginRequirement,
|
|
51
|
+
'initialMarginRequirement'
|
|
52
|
+
).plus(atLeastZero(available.plus(releasedOnClose(position))))
|
|
53
|
+
const isShort = position?.side === PositionSide.SHORT
|
|
54
|
+
return {
|
|
55
|
+
providerId: market.providerId,
|
|
56
|
+
marketId: market.id,
|
|
57
|
+
asset: toAssetDisplay(market.quoteAsset),
|
|
58
|
+
buy: (isShort ? reducing : adding).toFixed(),
|
|
59
|
+
sell: (isShort ? adding : reducing).toFixed(),
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { mapMarketOrderLimits } from './marketLimits.js'
|
|
|
23
23
|
export { mapInterval } from './ohlcvInterval.js'
|
|
24
24
|
export { formatOrderPrice, formatOrderSize } from './orderFormatting.js'
|
|
25
25
|
export { toIsoFromMs, toIsoFromSeconds } from './time.js'
|
|
26
|
-
export {
|
|
26
|
+
export { positionRemovableMargin } from './transferMargin.js'
|
|
27
27
|
export {
|
|
28
28
|
leverageToFraction,
|
|
29
29
|
mapOrderTypeToInt,
|
package/src/utils/mapOrder.ts
CHANGED
|
@@ -61,9 +61,10 @@ const mapOrderStatus = (
|
|
|
61
61
|
filledSize = new Big(order.filled_base_amount)
|
|
62
62
|
): OrderStatus => {
|
|
63
63
|
switch (order.status) {
|
|
64
|
-
case 'pending':
|
|
65
64
|
case 'in-progress':
|
|
66
65
|
return OrderStatus.ACCEPTED
|
|
66
|
+
case 'pending':
|
|
67
|
+
return OrderStatus.OPEN
|
|
67
68
|
case 'open':
|
|
68
69
|
if (order.trigger_status === 'parent-order') {
|
|
69
70
|
return OrderStatus.PENDING
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Market, MarketContext } from '@lifi/perps-types'
|
|
2
|
+
import type Big from 'big.js'
|
|
3
|
+
import { toRequiredBig } from './decimal.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unit prices keyed by spot base `Asset.id` (the venue `asset_id`), read from
|
|
7
|
+
* each spot market's `markPrice` for the base assets in `assetIds` only. A
|
|
8
|
+
* market without a context row or with a non-positive mark adds no entry; when
|
|
9
|
+
* several spot markets share a base asset, the first priced one in `markets`
|
|
10
|
+
* order wins. The mark is read as a USD price; `quoteAsset` is not checked.
|
|
11
|
+
*
|
|
12
|
+
* @throws {PerpsError} `SDKError` when a context row for a requested asset
|
|
13
|
+
* carries a non-decimal mark.
|
|
14
|
+
*/
|
|
15
|
+
export const spotPriceByAssetId = (
|
|
16
|
+
markets: readonly Market[],
|
|
17
|
+
spotCategoryId: string,
|
|
18
|
+
contexts: readonly MarketContext[],
|
|
19
|
+
assetIds: ReadonlySet<string>
|
|
20
|
+
): Map<string, Big> => {
|
|
21
|
+
const markByMarketId = new Map(contexts.map((c) => [c.marketId, c.markPrice]))
|
|
22
|
+
const priceByAssetId = new Map<string, Big>()
|
|
23
|
+
for (const market of markets) {
|
|
24
|
+
const mark = markByMarketId.get(market.id)
|
|
25
|
+
if (
|
|
26
|
+
market.categoryId !== spotCategoryId ||
|
|
27
|
+
mark === undefined ||
|
|
28
|
+
!assetIds.has(market.baseAsset.id) ||
|
|
29
|
+
priceByAssetId.has(market.baseAsset.id)
|
|
30
|
+
) {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
const price = toRequiredBig(mark, 'markPrice')
|
|
34
|
+
if (price.gt(0)) {
|
|
35
|
+
priceByAssetId.set(market.baseAsset.id, price)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return priceByAssetId
|
|
39
|
+
}
|
|
@@ -1,31 +1,69 @@
|
|
|
1
|
+
import { PerpsError } from '@lifi/perps-sdk'
|
|
1
2
|
import {
|
|
3
|
+
PerpsErrorCode,
|
|
2
4
|
type Position,
|
|
3
|
-
type PositionMarginConstraints,
|
|
4
5
|
positionSupportsMarginAdjustment,
|
|
6
|
+
positionSupportsMarginRemoval,
|
|
5
7
|
} from '@lifi/perps-types'
|
|
6
|
-
import
|
|
8
|
+
import Big from 'big.js'
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const AMOUNT_DECIMALS = 6
|
|
11
|
+
|
|
12
|
+
function positionAmount(value: string, field: string): Big {
|
|
13
|
+
try {
|
|
14
|
+
return new Big(value)
|
|
15
|
+
} catch {
|
|
16
|
+
throw new PerpsError(
|
|
17
|
+
PerpsErrorCode.ValidationError,
|
|
18
|
+
`Invalid decimal string on Position.${field}: '${value}'`
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function positivePositionAmount(value: string, field: string): Big {
|
|
24
|
+
const amount = positionAmount(value, field)
|
|
25
|
+
if (amount.lte(0)) {
|
|
26
|
+
throw new PerpsError(
|
|
27
|
+
PerpsErrorCode.ValidationError,
|
|
28
|
+
`Position.${field} must be greater than zero.`
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
return amount
|
|
32
|
+
}
|
|
9
33
|
|
|
10
34
|
/**
|
|
11
|
-
* Lighter
|
|
12
|
-
*
|
|
13
|
-
* the
|
|
35
|
+
* Lighter removable isolated margin:
|
|
36
|
+
* `allocated_margin + unrealized PnL − initial margin requirement`. Lighter
|
|
37
|
+
* `allocated_margin` (`Position.marginUsed`) excludes the unrealized PnL, and
|
|
38
|
+
* Lighter publishes no separate notional floor.
|
|
14
39
|
*
|
|
40
|
+
* @returns `undefined` for cross positions and markets without individual
|
|
41
|
+
* margin adjustment; `'0'` for add-only markets and when nothing is
|
|
42
|
+
* removable.
|
|
43
|
+
* @throws {PerpsError} `ValidationError` when a required `Position` decimal
|
|
44
|
+
* is malformed or not positive.
|
|
15
45
|
* @see https://docs.lighter.xyz/trading/liquidations-and-llp-insurance-fund
|
|
16
46
|
* @public
|
|
17
47
|
*/
|
|
18
|
-
export function
|
|
48
|
+
export function positionRemovableMargin(
|
|
19
49
|
position: Position
|
|
20
|
-
):
|
|
50
|
+
): string | undefined {
|
|
21
51
|
if (!positionSupportsMarginAdjustment(position)) {
|
|
22
52
|
return undefined
|
|
23
53
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
54
|
+
if (!positionSupportsMarginRemoval(position)) {
|
|
55
|
+
return '0'
|
|
56
|
+
}
|
|
57
|
+
const removable = positivePositionAmount(position.marginUsed, 'marginUsed')
|
|
58
|
+
.plus(positionAmount(position.unrealizedPnl, 'unrealizedPnl'))
|
|
59
|
+
.minus(
|
|
60
|
+
positivePositionAmount(
|
|
61
|
+
position.initialMarginRequirement,
|
|
62
|
+
'initialMarginRequirement'
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
if (removable.lte(0)) {
|
|
66
|
+
return '0'
|
|
30
67
|
}
|
|
68
|
+
return removable.round(AMOUNT_DECIMALS, Big.roundDown).toFixed()
|
|
31
69
|
}
|
|
@@ -660,10 +660,10 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
660
660
|
this.emit(`accountSummary:${address}`, {
|
|
661
661
|
channel: 'accountSummary',
|
|
662
662
|
data: {
|
|
663
|
-
portfolioValue: portfolio.
|
|
664
|
-
availableMargin: available.
|
|
665
|
-
marginUsed: marginUsed.
|
|
666
|
-
unrealizedPnl: portfolio.minus(collateral).
|
|
663
|
+
portfolioValue: portfolio.toFixed(),
|
|
664
|
+
availableMargin: available.toFixed(),
|
|
665
|
+
marginUsed: marginUsed.toFixed(),
|
|
666
|
+
unrealizedPnl: portfolio.minus(collateral).toFixed(),
|
|
667
667
|
},
|
|
668
668
|
})
|
|
669
669
|
}
|
|
@@ -1008,5 +1008,5 @@ export const lighterWsProvider = (
|
|
|
1008
1008
|
Object.assign(
|
|
1009
1009
|
({ provider, wsUrl, client }: WsProviderFactoryParams) =>
|
|
1010
1010
|
new LighterWsProvider(wsUrl, provider, options, client),
|
|
1011
|
-
{ streamsCandles: false }
|
|
1011
|
+
{ streamsCandles: false, streamsAvailableToTrade: false }
|
|
1012
1012
|
)
|