@lifi/perps-sdk-provider-lighter 31.0.0 → 32.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 +6 -0
- package/dist/cjs/LighterProvider.d.ts.map +1 -1
- package/dist/cjs/LighterProvider.js +13 -9
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts +1 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +8 -2
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/signers/LighterSigner.js +1 -1
- package/dist/cjs/signers/LighterSigner.js.map +1 -1
- package/dist/cjs/types/ws.d.ts +1 -1
- package/dist/cjs/types/ws.d.ts.map +1 -1
- 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 +3 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/liquidation.js +1 -1
- package/dist/cjs/utils/liquidation.js.map +1 -1
- package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/cjs/utils/mapMarketContext.js +12 -4
- package/dist/cjs/utils/mapMarketContext.js.map +1 -1
- package/dist/cjs/utils/orderFormatting.d.ts +3 -3
- package/dist/cjs/utils/orderFormatting.d.ts.map +1 -1
- package/dist/cjs/utils/orderFormatting.js +5 -5
- package/dist/cjs/utils/orderFormatting.js.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/cjs/utils/withdrawableBalances.js +16 -5
- package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +27 -7
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +18 -14
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountSummary.d.ts +4 -10
- package/dist/esm/accountSummary.d.ts.map +1 -1
- package/dist/esm/accountSummary.js +12 -12
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/availableToTrade.d.ts +1 -1
- package/dist/esm/availableToTrade.js +1 -1
- package/dist/esm/constants.d.ts +4 -2
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +4 -2
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/signers/LighterSigner.js +2 -2
- package/dist/esm/signers/LighterSigner.js.map +1 -1
- package/dist/esm/types/ws.d.ts +2 -3
- package/dist/esm/types/ws.d.ts.map +1 -1
- 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/liquidation.js +1 -1
- package/dist/esm/utils/liquidation.js.map +1 -1
- package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/esm/utils/mapMarketContext.js +18 -4
- package/dist/esm/utils/mapMarketContext.js.map +1 -1
- package/dist/esm/utils/orderFormatting.d.ts +11 -11
- package/dist/esm/utils/orderFormatting.d.ts.map +1 -1
- package/dist/esm/utils/orderFormatting.js +12 -12
- package/dist/esm/utils/orderFormatting.js.map +1 -1
- package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/esm/utils/withdrawableBalances.js +17 -6
- package/dist/esm/utils/withdrawableBalances.js.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.d.ts +2 -12
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +27 -22
- package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountSummary.d.ts +4 -10
- package/dist/types/accountSummary.d.ts.map +1 -1
- package/dist/types/availableToTrade.d.ts +1 -1
- package/dist/types/constants.d.ts +4 -2
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +2 -3
- package/dist/types/types/ws.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/types/utils/orderFormatting.d.ts +11 -11
- package/dist/types/utils/orderFormatting.d.ts.map +1 -1
- package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
- package/dist/types/websocket/LighterWsProvider.d.ts +2 -12
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +36 -23
- package/src/accountSummary.ts +20 -13
- package/src/availableToTrade.ts +1 -1
- package/src/constants.ts +4 -2
- package/src/signers/LighterSigner.ts +2 -2
- package/src/types/ws.ts +4 -4
- package/src/utils/index.ts +1 -1
- package/src/utils/liquidation.ts +1 -1
- package/src/utils/mapMarketContext.ts +24 -4
- package/src/utils/orderFormatting.ts +22 -12
- package/src/utils/withdrawableBalances.ts +26 -11
- package/src/websocket/LighterWsProvider.ts +33 -31
package/src/LighterProvider.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTIVE_ORDER_STATUSES,
|
|
3
|
+
calculateTransferable,
|
|
3
4
|
type DepositFlow,
|
|
4
5
|
explorerTxUrl,
|
|
5
6
|
explorerTxUrlFromBase,
|
|
@@ -9,8 +10,6 @@ import {
|
|
|
9
10
|
getProviders,
|
|
10
11
|
isActiveOrderStatus,
|
|
11
12
|
localStorageAdapter,
|
|
12
|
-
maxOf,
|
|
13
|
-
minOf,
|
|
14
13
|
PerpsError,
|
|
15
14
|
type PerpsProviderPlugin,
|
|
16
15
|
type PerpsSDKClient,
|
|
@@ -66,6 +65,7 @@ import type {
|
|
|
66
65
|
import {
|
|
67
66
|
ActionType,
|
|
68
67
|
ActivityType,
|
|
68
|
+
isDecimalString,
|
|
69
69
|
MarginMode,
|
|
70
70
|
PerpsErrorCode,
|
|
71
71
|
} from '@lifi/perps-types'
|
|
@@ -142,8 +142,6 @@ import { isAssetMarginEnabled } from './utils/assetCollateral.js'
|
|
|
142
142
|
import {
|
|
143
143
|
estimateLiquidationPrice,
|
|
144
144
|
fetchDetailedAccount,
|
|
145
|
-
formatOrderPrice,
|
|
146
|
-
formatOrderSize,
|
|
147
145
|
leverageFromImf,
|
|
148
146
|
lighterAsset,
|
|
149
147
|
lighterWithdrawableBalances,
|
|
@@ -152,6 +150,8 @@ import {
|
|
|
152
150
|
mapOrder,
|
|
153
151
|
mapPortfolioHistory,
|
|
154
152
|
positionRemovableMargin,
|
|
153
|
+
snapOrderPrice,
|
|
154
|
+
snapOrderSize,
|
|
155
155
|
toBigOrNull,
|
|
156
156
|
toIsoFromMs,
|
|
157
157
|
toIsoFromSeconds,
|
|
@@ -1042,37 +1042,37 @@ export const createLighterProvider = (
|
|
|
1042
1042
|
// account's free margin; every other perps-route asset releases none.
|
|
1043
1043
|
const collateralBalances: Balance[] = marginAssets.map((a) => {
|
|
1044
1044
|
const isSettlement = a.asset_id === collateral.assetIndex
|
|
1045
|
+
const marginBalance = toRequiredBig(
|
|
1046
|
+
a.margin_balance,
|
|
1047
|
+
'margin_balance'
|
|
1048
|
+
).toFixed()
|
|
1045
1049
|
return {
|
|
1046
1050
|
...toBalance(
|
|
1047
1051
|
a,
|
|
1048
1052
|
perpsCategory?.id ?? providerKey,
|
|
1049
1053
|
isSettlement ? settlementAsset : registryAsset(a),
|
|
1050
|
-
a.margin_balance
|
|
1054
|
+
isDecimalString(a.margin_balance) ? a.margin_balance : marginBalance
|
|
1051
1055
|
),
|
|
1052
1056
|
transferable: isSettlement
|
|
1053
|
-
?
|
|
1054
|
-
maxOf(availableBalance, new Big(0)),
|
|
1055
|
-
new Big(a.margin_balance)
|
|
1056
|
-
).toFixed()
|
|
1057
|
+
? calculateTransferable(availableBalance.toFixed(), marginBalance)
|
|
1057
1058
|
: '0',
|
|
1058
1059
|
}
|
|
1059
1060
|
})
|
|
1060
1061
|
const balances: Balance[] = heldAssets.map((a) => {
|
|
1061
|
-
const balance =
|
|
1062
|
+
const balance = toRequiredBig(a.balance, 'balance')
|
|
1062
1063
|
return {
|
|
1063
1064
|
...toBalance(
|
|
1064
1065
|
a,
|
|
1065
1066
|
LIGHTER_SPOT_CATEGORY_ID,
|
|
1066
1067
|
registryAsset(a),
|
|
1067
|
-
a.balance
|
|
1068
|
+
isDecimalString(a.balance) ? a.balance : balance.toFixed()
|
|
1068
1069
|
),
|
|
1069
|
-
transferable:
|
|
1070
|
-
maxOf(
|
|
1071
|
-
balance.minus(toRequiredBig(a.locked_balance, 'locked_balance')),
|
|
1072
|
-
new Big(0)
|
|
1073
|
-
),
|
|
1070
|
+
transferable: calculateTransferable(
|
|
1074
1071
|
balance
|
|
1075
|
-
|
|
1072
|
+
.minus(toRequiredBig(a.locked_balance, 'locked_balance'))
|
|
1073
|
+
.toFixed(),
|
|
1074
|
+
balance.toFixed()
|
|
1075
|
+
),
|
|
1076
1076
|
}
|
|
1077
1077
|
})
|
|
1078
1078
|
|
|
@@ -1192,8 +1192,8 @@ export const createLighterProvider = (
|
|
|
1192
1192
|
},
|
|
1193
1193
|
|
|
1194
1194
|
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1195
|
+
* Derive per-market capacity from signed cross free collateral and the
|
|
1196
|
+
* margin released by closing the market's open position.
|
|
1197
1197
|
*/
|
|
1198
1198
|
async getAvailableToTrade(
|
|
1199
1199
|
params: ProviderGetAvailableToTradeParams,
|
|
@@ -1206,8 +1206,21 @@ export const createLighterProvider = (
|
|
|
1206
1206
|
return undefined
|
|
1207
1207
|
}
|
|
1208
1208
|
const account = await plugin.getAccount({ address: params.address }, opts)
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1209
|
+
const config = lighterConfig(account)
|
|
1210
|
+
const crossFreeCollateral = toRequiredBig(
|
|
1211
|
+
config.crossAssetValue,
|
|
1212
|
+
'crossAssetValue'
|
|
1213
|
+
).minus(
|
|
1214
|
+
toRequiredBig(
|
|
1215
|
+
config.crossInitialMarginRequirement,
|
|
1216
|
+
'crossInitialMarginRequirement'
|
|
1217
|
+
)
|
|
1218
|
+
)
|
|
1219
|
+
return lighterAvailableToTrade(
|
|
1220
|
+
market,
|
|
1221
|
+
crossFreeCollateral.toFixed(),
|
|
1222
|
+
account.positions
|
|
1223
|
+
)
|
|
1211
1224
|
},
|
|
1212
1225
|
|
|
1213
1226
|
/**
|
|
@@ -1801,9 +1814,9 @@ export const createLighterProvider = (
|
|
|
1801
1814
|
return getAccountSummary(account, positions)
|
|
1802
1815
|
},
|
|
1803
1816
|
|
|
1804
|
-
|
|
1817
|
+
snapOrderPrice,
|
|
1805
1818
|
|
|
1806
|
-
|
|
1819
|
+
snapOrderSize,
|
|
1807
1820
|
|
|
1808
1821
|
estimateLiquidationPrice,
|
|
1809
1822
|
|
package/src/accountSummary.ts
CHANGED
|
@@ -8,7 +8,11 @@ import type {
|
|
|
8
8
|
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
9
9
|
import Big from 'big.js'
|
|
10
10
|
import { atLeastZero } from './availableToTrade.js'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
LIGHTER_COLLATERAL_ASSETS,
|
|
13
|
+
LIGHTER_PROVIDER_KEY,
|
|
14
|
+
LIGHTER_RH_PROVIDER_KEY,
|
|
15
|
+
} from './constants.js'
|
|
12
16
|
import { toRequiredBig } from './utils/decimal.js'
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -33,26 +37,20 @@ export const lighterConfig = (
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
37
|
-
* the USD value of every spot-route holding, settlement asset included.
|
|
40
|
+
* Add marked holdings excluded from Lighter's settlement equity.
|
|
38
41
|
*/
|
|
39
42
|
export const lighterPortfolioValue = (
|
|
40
43
|
perpsEquity: Big,
|
|
41
|
-
|
|
44
|
+
holdingValuesUsd: readonly string[]
|
|
42
45
|
): Big =>
|
|
43
|
-
|
|
46
|
+
holdingValuesUsd.reduce(
|
|
44
47
|
(sum, valueUsd) => sum.plus(toRequiredBig(valueUsd, 'valueUsd')),
|
|
45
48
|
perpsEquity
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
|
-
* Roll
|
|
50
|
-
*
|
|
51
|
-
* every spot `balances` row, settlement included; the `collateralBalances` rows
|
|
52
|
-
* are the perps-route holding already inside `totalAssetValue`.
|
|
53
|
-
* `availableMargin` is the cross free collateral, floored at 0: an isolated
|
|
54
|
-
* position's equity stays with that position until the user removes it. The
|
|
55
|
-
* positions supply only the margin and PnL breakdown.
|
|
52
|
+
* Roll up settlement equity, spot holdings and non-settlement margin holdings.
|
|
53
|
+
* Settlement margin is already included in `totalAssetValue`.
|
|
56
54
|
*
|
|
57
55
|
* @throws {PerpsError} `SDKError` when the account is not a Lighter one.
|
|
58
56
|
* @public
|
|
@@ -62,6 +60,15 @@ export function getAccountSummary(
|
|
|
62
60
|
positions: Position[]
|
|
63
61
|
): AccountSummary {
|
|
64
62
|
const config = lighterConfig(account)
|
|
63
|
+
const settlement = LIGHTER_COLLATERAL_ASSETS[config.provider]
|
|
64
|
+
const holdings = [
|
|
65
|
+
...account.balances,
|
|
66
|
+
...account.collateralBalances.filter(
|
|
67
|
+
({ asset }) =>
|
|
68
|
+
asset.id !== String(settlement.assetIndex) &&
|
|
69
|
+
asset.id !== settlement.displaySymbol
|
|
70
|
+
),
|
|
71
|
+
]
|
|
65
72
|
|
|
66
73
|
let marginUsed = new Big(0)
|
|
67
74
|
let unrealizedPnl = new Big(0)
|
|
@@ -73,7 +80,7 @@ export function getAccountSummary(
|
|
|
73
80
|
return {
|
|
74
81
|
portfolioValue: lighterPortfolioValue(
|
|
75
82
|
toRequiredBig(config.totalAssetValue, 'totalAssetValue'),
|
|
76
|
-
|
|
83
|
+
holdings.map((balance) => balance.valueUsd)
|
|
77
84
|
).toFixed(),
|
|
78
85
|
availableMargin: atLeastZero(
|
|
79
86
|
toRequiredBig(config.crossAssetValue, 'crossAssetValue').minus(
|
package/src/availableToTrade.ts
CHANGED
|
@@ -27,7 +27,7 @@ const releasedOnClose = (position: Position): Big => {
|
|
|
27
27
|
* plus `availableMargin` and the margin that closing releases, floored at 0.
|
|
28
28
|
* A deficit therefore never cuts the closing part. Both amounts are at least 0.
|
|
29
29
|
*
|
|
30
|
-
* @param availableMargin -
|
|
30
|
+
* @param availableMargin - Signed cross free collateral, before summary clamping.
|
|
31
31
|
* @throws {PerpsError} `SDKError` when a decimal the formula reads is malformed.
|
|
32
32
|
* @internal
|
|
33
33
|
*/
|
package/src/constants.ts
CHANGED
|
@@ -18,8 +18,10 @@ export const LIGHTER_RH_PROVIDER_KEY = 'lighter-rh'
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Category id for Lighter spot token holdings, matching the backend's Lighter
|
|
21
|
-
* spot asset category.
|
|
22
|
-
*
|
|
21
|
+
* spot asset category. Every Lighter call site, REST and WebSocket, uses this
|
|
22
|
+
* constant for the spot category. The perps category id is not a constant:
|
|
23
|
+
* `LighterProvider.getAccount` reads it from the `/providers` category whose
|
|
24
|
+
* `quoteAsset` is not null.
|
|
23
25
|
*
|
|
24
26
|
* @public
|
|
25
27
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decimalToBaseUnits, PerpsError } from '@lifi/perps-sdk'
|
|
2
2
|
import { ActionType, PerpsErrorCode } from '@lifi/perps-types'
|
|
3
3
|
import Big from 'big.js'
|
|
4
4
|
import { LT_ROUTE_PERP, LT_ROUTE_SPOT } from '../types/action.js'
|
|
@@ -632,7 +632,7 @@ export class LighterSigner {
|
|
|
632
632
|
return wasm.SignWithdraw(
|
|
633
633
|
numberField(p, 'asset_index'),
|
|
634
634
|
routeType,
|
|
635
|
-
|
|
635
|
+
decimalToBaseUnits(amount, numberField(p, 'decimals'), 'truncate'),
|
|
636
636
|
SKIP_NONCE_DISABLED,
|
|
637
637
|
nonce,
|
|
638
638
|
ctx.apiKeyIndex,
|
package/src/types/ws.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { LtAccountAsset } from './account.js'
|
|
|
11
11
|
// - spot_market_stats/all → context across all spot markets
|
|
12
12
|
// - spot_market_stats/{id} → context for one spot market
|
|
13
13
|
// - order_book/{market_id} → bids/asks per market (stateful with deltas)
|
|
14
|
-
// - account_all/{account} → per-asset spot-route balances
|
|
14
|
+
// - account_all/{account} → per-asset spot and margin-route balances
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Common envelope fields on every Lighter WebSocket frame.
|
|
@@ -218,8 +218,7 @@ export type LtWsUserStatsMessage = LtWsMessage & {
|
|
|
218
218
|
* `account_all/{account_index}` or `account_all_assets/{account_index}` frame,
|
|
219
219
|
* read for its `assets` map only. The `subscribed/` frame carries every asset
|
|
220
220
|
* keyed by asset index; an `update/` frame carries the changed assets, or
|
|
221
|
-
* `null` when no balance changed.
|
|
222
|
-
* units.
|
|
221
|
+
* `null` when no balance changed. Route balances are in asset units.
|
|
223
222
|
* @public
|
|
224
223
|
*/
|
|
225
224
|
export type LtWsAccountAllMessage = LtWsMessage & {
|
|
@@ -230,7 +229,8 @@ export type LtWsAccountAllMessage = LtWsMessage & {
|
|
|
230
229
|
| 'update/account_all_assets'
|
|
231
230
|
assets?: Record<
|
|
232
231
|
string,
|
|
233
|
-
Pick<LtAccountAsset, 'symbol' | 'asset_id' | 'balance' | 'locked_balance'>
|
|
232
|
+
Pick<LtAccountAsset, 'symbol' | 'asset_id' | 'balance' | 'locked_balance'> &
|
|
233
|
+
Partial<Pick<LtAccountAsset, 'margin_balance'>>
|
|
234
234
|
> | null
|
|
235
235
|
}
|
|
236
236
|
|
package/src/utils/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { mapPortfolioHistory } from './mapPortfolioHistory.js'
|
|
|
21
21
|
export { leverageFromImf, mapPosition } from './mapPosition.js'
|
|
22
22
|
export { mapMarketOrderLimits } from './marketLimits.js'
|
|
23
23
|
export { mapInterval } from './ohlcvInterval.js'
|
|
24
|
-
export {
|
|
24
|
+
export { snapOrderPrice, snapOrderSize } from './orderFormatting.js'
|
|
25
25
|
export { toIsoFromMs, toIsoFromSeconds } from './time.js'
|
|
26
26
|
export { positionRemovableMargin } from './transferMargin.js'
|
|
27
27
|
export {
|
package/src/utils/liquidation.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numberToDecimalString } from '@lifi/perps-sdk'
|
|
1
2
|
import type { MarketContext } from '@lifi/perps-types'
|
|
2
3
|
import type { LtWsMarketStats, LtWsSpotMarketStats } from '../types/index.js'
|
|
3
4
|
|
|
@@ -7,6 +8,27 @@ const isPerpStats = (
|
|
|
7
8
|
stats: LtWsMarketStats | LtWsSpotMarketStats
|
|
8
9
|
): stats is LtWsMarketStats => 'mark_price' in stats
|
|
9
10
|
|
|
11
|
+
const isFiniteFigure = (value: number | null | undefined): value is number =>
|
|
12
|
+
typeof value === 'number' && Number.isFinite(value)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The 24h figures of one record, omitted where the frame carries no finite
|
|
16
|
+
* number. The venue publishes them as unvalidated JSON, and `market_stats/all`
|
|
17
|
+
* maps every market in one pass, so a single absent figure must not cost the
|
|
18
|
+
* whole frame.
|
|
19
|
+
*/
|
|
20
|
+
const dailyFigures = (stats: {
|
|
21
|
+
daily_price_change?: number | null
|
|
22
|
+
daily_quote_token_volume?: number | null
|
|
23
|
+
}): Pick<MarketContext, 'priceChange24h' | 'volume24h'> => ({
|
|
24
|
+
...(isFiniteFigure(stats.daily_price_change)
|
|
25
|
+
? { priceChange24h: numberToDecimalString(stats.daily_price_change) }
|
|
26
|
+
: {}),
|
|
27
|
+
...(isFiniteFigure(stats.daily_quote_token_volume)
|
|
28
|
+
? { volume24h: numberToDecimalString(stats.daily_quote_token_volume) }
|
|
29
|
+
: {}),
|
|
30
|
+
})
|
|
31
|
+
|
|
10
32
|
/**
|
|
11
33
|
* Map a Lighter market-stats record to a {@link MarketContext}: `index_price`
|
|
12
34
|
* is the oracle, `mid_price` the mid. Perp records carry the venue
|
|
@@ -24,8 +46,7 @@ export const mapMarketContext = (
|
|
|
24
46
|
midPrice: stats.mid_price,
|
|
25
47
|
markPrice: stats.mark_price,
|
|
26
48
|
oraclePrice: stats.index_price,
|
|
27
|
-
|
|
28
|
-
volume24h: String(stats.daily_quote_token_volume),
|
|
49
|
+
...dailyFigures(stats),
|
|
29
50
|
openInterest: stats.open_interest,
|
|
30
51
|
funding: {
|
|
31
52
|
rate: stats.current_funding_rate,
|
|
@@ -38,7 +59,6 @@ export const mapMarketContext = (
|
|
|
38
59
|
midPrice: stats.mid_price,
|
|
39
60
|
markPrice: stats.mid_price,
|
|
40
61
|
oraclePrice: stats.index_price,
|
|
41
|
-
|
|
42
|
-
volume24h: String(stats.daily_quote_token_volume),
|
|
62
|
+
...dailyFigures(stats),
|
|
43
63
|
}
|
|
44
64
|
}
|
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lighter order size and price
|
|
2
|
+
* Lighter order size and price snapping for exchange submission.
|
|
3
3
|
*
|
|
4
4
|
* Lighter publishes a flat per-market decimal budget on its order books
|
|
5
5
|
* (`supported_price_decimals` / `supported_size_decimals`), surfaced on the
|
|
6
|
-
* generic {@link Market} as `priceDecimals` / `szDecimals`. Prices round
|
|
7
|
-
* the tick grid; sizes truncate
|
|
6
|
+
* generic {@link Market} as `priceDecimals` / `szDecimals`. Prices round onto
|
|
7
|
+
* the tick grid; sizes truncate onto the lot grid.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { PerpsError } from '@lifi/perps-sdk'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type DecimalString,
|
|
13
|
+
type Market,
|
|
14
|
+
PerpsErrorCode,
|
|
15
|
+
} from '@lifi/perps-types'
|
|
12
16
|
import Big from 'big.js'
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
|
-
*
|
|
16
|
-
* `priceDecimals` (Lighter's `supported_price_decimals`), trailing
|
|
17
|
-
* stripped.
|
|
19
|
+
* Snap a price onto a Lighter market's tick grid: rounded half-up to the
|
|
20
|
+
* market's `priceDecimals` (Lighter's `supported_price_decimals`), trailing
|
|
21
|
+
* zeros stripped.
|
|
18
22
|
*
|
|
19
23
|
* @throws {PerpsError} `ValidationError` when `market.priceDecimals` is
|
|
20
24
|
* absent — without the venue's tick grid no correct price can be produced.
|
|
21
25
|
* @public
|
|
22
26
|
*/
|
|
23
|
-
export function
|
|
27
|
+
export function snapOrderPrice(
|
|
28
|
+
market: Market,
|
|
29
|
+
price: DecimalString
|
|
30
|
+
): DecimalString {
|
|
24
31
|
if (market.priceDecimals === undefined) {
|
|
25
32
|
throw new PerpsError(
|
|
26
33
|
PerpsErrorCode.ValidationError,
|
|
27
34
|
`Market '${market.id}' carries no priceDecimals; Lighter order prices ` +
|
|
28
|
-
`cannot be
|
|
35
|
+
`cannot be snapped without the market's tick grid.`
|
|
29
36
|
)
|
|
30
37
|
}
|
|
31
38
|
const rounded = new Big(price).round(market.priceDecimals, Big.roundHalfUp)
|
|
@@ -33,14 +40,17 @@ export function formatOrderPrice(market: Market, price: number): string {
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
37
|
-
* up, so the size cannot exceed the user's balance) to the market's
|
|
43
|
+
* Snap a size onto a Lighter market's lot grid: truncated toward zero (never
|
|
44
|
+
* rounded up, so the size cannot exceed the user's balance) to the market's
|
|
38
45
|
* `szDecimals` (Lighter's `supported_size_decimals`), trailing zeros stripped.
|
|
39
46
|
*
|
|
40
47
|
* @param size - Size in base-asset units as a non-negative magnitude.
|
|
41
48
|
* @public
|
|
42
49
|
*/
|
|
43
|
-
export function
|
|
50
|
+
export function snapOrderSize(
|
|
51
|
+
market: Market,
|
|
52
|
+
size: DecimalString
|
|
53
|
+
): DecimalString {
|
|
44
54
|
const truncated = new Big(size).round(market.szDecimals, Big.roundDown)
|
|
45
55
|
return truncated.eq(0) ? '0' : truncated.toFixed()
|
|
46
56
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
calculateTransferable,
|
|
3
|
+
calculateWithdrawMax,
|
|
4
|
+
type ProviderWithdrawableBalance,
|
|
5
|
+
} from '@lifi/perps-sdk'
|
|
2
6
|
import Big from 'big.js'
|
|
3
7
|
import type { LtAccount } from '../types/account.js'
|
|
4
8
|
import { toRequiredBig } from './decimal.js'
|
|
@@ -29,21 +33,32 @@ export const lighterWithdrawableBalances = (
|
|
|
29
33
|
toRequiredBig(asset.locked_balance, 'locked_balance')
|
|
30
34
|
)
|
|
31
35
|
if (spot.gt(0)) {
|
|
32
|
-
|
|
36
|
+
const available = spot.toFixed()
|
|
37
|
+
rows.push({
|
|
38
|
+
assetId,
|
|
39
|
+
route: 'spot',
|
|
40
|
+
available,
|
|
41
|
+
max: calculateWithdrawMax({ available }),
|
|
42
|
+
})
|
|
33
43
|
}
|
|
34
44
|
const marginBalance = toRequiredBig(asset.margin_balance, 'margin_balance')
|
|
35
45
|
const perps =
|
|
36
46
|
asset.asset_id === settlementAssetIndex
|
|
37
|
-
?
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
),
|
|
42
|
-
marginBalance
|
|
47
|
+
? calculateTransferable(
|
|
48
|
+
toRequiredBig(
|
|
49
|
+
account.available_balance,
|
|
50
|
+
'available_balance'
|
|
51
|
+
).toFixed(),
|
|
52
|
+
marginBalance.toFixed()
|
|
43
53
|
)
|
|
44
|
-
: marginBalance
|
|
45
|
-
if (perps.gt(0)) {
|
|
46
|
-
rows.push({
|
|
54
|
+
: marginBalance.toFixed()
|
|
55
|
+
if (new Big(perps).gt(0)) {
|
|
56
|
+
rows.push({
|
|
57
|
+
assetId,
|
|
58
|
+
route: 'perps',
|
|
59
|
+
available: perps,
|
|
60
|
+
max: calculateWithdrawMax({ available: perps }),
|
|
61
|
+
})
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
return rows
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type Position,
|
|
25
25
|
type Subscription,
|
|
26
26
|
} from '@lifi/perps-types'
|
|
27
|
+
import Big from 'big.js'
|
|
27
28
|
import type { Address } from 'viem'
|
|
28
29
|
import { lighterPortfolioValue } from '../accountSummary.js'
|
|
29
30
|
import {
|
|
@@ -148,14 +149,10 @@ interface BookLevel {
|
|
|
148
149
|
priceNum: number
|
|
149
150
|
}
|
|
150
151
|
|
|
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
|
-
*/
|
|
152
|
+
/** Settlement equity and per-asset route quantities for account valuation. */
|
|
156
153
|
interface AccountSummaryInputs {
|
|
157
154
|
perps?: { equity: Big } & Omit<AccountSummary, 'portfolioValue'>
|
|
158
|
-
|
|
155
|
+
balances?: Map<number, { spot: Big; margin: Big }>
|
|
159
156
|
lastPortfolioValue?: string
|
|
160
157
|
}
|
|
161
158
|
|
|
@@ -516,9 +513,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
516
513
|
// instead of every later frame.
|
|
517
514
|
this.settlementAssetIndex()
|
|
518
515
|
const accountIndex = await this.resolveAccountIndex(sub.address)
|
|
519
|
-
// `account_all_assets`
|
|
520
|
-
// subscribe without auth; the public `account_all` also carries the
|
|
521
|
-
// account's trades, positions and funding.
|
|
516
|
+
// `account_all_assets` requires auth; `account_all` is publicly readable.
|
|
522
517
|
const hasToken = await this.canResolveAuthToken(sub.address)
|
|
523
518
|
const assetsChannel = hasToken ? 'account_all_assets' : 'account_all'
|
|
524
519
|
return [
|
|
@@ -785,12 +780,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
785
780
|
this.emitAccountSummary(address)
|
|
786
781
|
}
|
|
787
782
|
|
|
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
|
-
*/
|
|
783
|
+
/** Snapshots replace all route quantities; deltas upsert changed assets. */
|
|
794
784
|
private handleAccountAll(
|
|
795
785
|
msg: LtWsAccountAllMessage,
|
|
796
786
|
prefix: 'account_all' | 'account_all_assets',
|
|
@@ -804,34 +794,46 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
804
794
|
if (inputs === undefined || (!isSnapshot && !msg.assets)) {
|
|
805
795
|
return
|
|
806
796
|
}
|
|
807
|
-
const parsed = Object.values(msg.assets ?? {}).map(
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
797
|
+
const parsed = Object.values(msg.assets ?? {}).map((asset) => ({
|
|
798
|
+
assetId: asset.asset_id,
|
|
799
|
+
spot: toRequiredBig(asset.balance, 'balance'),
|
|
800
|
+
margin:
|
|
801
|
+
asset.margin_balance === undefined
|
|
802
|
+
? undefined
|
|
803
|
+
: toRequiredBig(asset.margin_balance, 'margin_balance'),
|
|
804
|
+
}))
|
|
805
|
+
const balances = isSnapshot
|
|
806
|
+
? new Map<number, { spot: Big; margin: Big }>()
|
|
807
|
+
: inputs.balances
|
|
812
808
|
if (balances === undefined) {
|
|
813
809
|
return
|
|
814
810
|
}
|
|
815
|
-
for (const
|
|
816
|
-
balances.set(assetId,
|
|
811
|
+
for (const { assetId, spot, margin } of parsed) {
|
|
812
|
+
balances.set(assetId, {
|
|
813
|
+
spot,
|
|
814
|
+
margin: margin ?? balances.get(assetId)?.margin ?? new Big(0),
|
|
815
|
+
})
|
|
817
816
|
}
|
|
818
|
-
inputs.
|
|
817
|
+
inputs.balances = balances
|
|
819
818
|
this.emitAccountSummary(address)
|
|
820
819
|
}
|
|
821
820
|
|
|
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
|
-
*/
|
|
821
|
+
/** Unrelated mark ticks do not emit an unchanged portfolio value. */
|
|
828
822
|
private emitAccountSummary(address: string, onlyOnChange = false): void {
|
|
829
823
|
const inputs = this.accountSummaryInputs.get(address)
|
|
830
|
-
if (inputs?.perps === undefined || inputs.
|
|
824
|
+
if (inputs?.perps === undefined || inputs.balances === undefined) {
|
|
831
825
|
return
|
|
832
826
|
}
|
|
833
827
|
const settlementAssetIndex = this.settlementAssetIndex()
|
|
834
|
-
const held = [...inputs.
|
|
828
|
+
const held = [...inputs.balances]
|
|
829
|
+
.map(
|
|
830
|
+
([assetId, { spot, margin }]) =>
|
|
831
|
+
[
|
|
832
|
+
assetId,
|
|
833
|
+
assetId === settlementAssetIndex ? spot : spot.plus(margin),
|
|
834
|
+
] as const
|
|
835
|
+
)
|
|
836
|
+
.filter(([, balance]) => balance.gt(0))
|
|
835
837
|
const spotPrices = spotPriceByAssetId(
|
|
836
838
|
this.registry?.markets ?? [],
|
|
837
839
|
LIGHTER_SPOT_CATEGORY_ID,
|