@lifi/perps-sdk-provider-lighter 25.0.0 → 27.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 +13 -11
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +11 -18
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/constants.d.ts +2 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +3 -0
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/utils/mapFill.d.ts.map +1 -1
- package/dist/cjs/utils/mapFill.js +4 -1
- package/dist/cjs/utils/mapFill.js.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +3 -0
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/txHash.d.ts +2 -0
- package/dist/cjs/utils/txHash.d.ts.map +1 -0
- package/dist/cjs/utils/txHash.js +10 -0
- package/dist/cjs/utils/txHash.js.map +1 -0
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +11 -8
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +16 -17
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountSummary.d.ts +5 -6
- package/dist/esm/accountSummary.d.ts.map +1 -1
- package/dist/esm/accountSummary.js +19 -25
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/constants.d.ts +9 -3
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +6 -3
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/types/ws.d.ts +1 -1
- package/dist/esm/utils/mapFill.d.ts.map +1 -1
- package/dist/esm/utils/mapFill.js +4 -1
- package/dist/esm/utils/mapFill.js.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +3 -0
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/txHash.d.ts +8 -0
- package/dist/esm/utils/txHash.d.ts.map +1 -0
- package/dist/esm/utils/txHash.js +18 -0
- package/dist/esm/utils/txHash.js.map +1 -0
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +19 -14
- 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 -6
- package/dist/types/accountSummary.d.ts.map +1 -1
- package/dist/types/constants.d.ts +9 -3
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +1 -1
- package/dist/types/utils/mapFill.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/txHash.d.ts +8 -0
- package/dist/types/utils/txHash.d.ts.map +1 -0
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +18 -23
- package/src/accountSummary.ts +30 -26
- package/src/constants.ts +11 -3
- package/src/types/ws.ts +1 -1
- package/src/utils/mapFill.ts +4 -1
- package/src/utils/mapOrder.ts +3 -0
- package/src/utils/txHash.ts +20 -0
- package/src/websocket/LighterWsProvider.ts +21 -15
package/src/accountSummary.ts
CHANGED
|
@@ -1,53 +1,57 @@
|
|
|
1
|
+
import { PerpsError } from '@lifi/perps-sdk'
|
|
1
2
|
import type {
|
|
2
3
|
AccountResponse,
|
|
3
4
|
AccountSummary,
|
|
5
|
+
LighterAccountConfig,
|
|
4
6
|
Position,
|
|
5
7
|
} from '@lifi/perps-types'
|
|
6
|
-
import {
|
|
8
|
+
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
7
9
|
import Big from 'big.js'
|
|
10
|
+
import { toRequiredBig } from './utils/decimal.js'
|
|
11
|
+
|
|
12
|
+
const lighterConfig = (account: AccountResponse): LighterAccountConfig => {
|
|
13
|
+
if (account.config.provider !== 'lighter') {
|
|
14
|
+
throw new PerpsError(
|
|
15
|
+
PerpsErrorCode.SDKError,
|
|
16
|
+
`Lighter account summary received a '${account.config.provider}' account config`
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return account.config
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
/**
|
|
10
|
-
* Roll a Lighter
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* sub-account. Reconciliation therefore adds every position's locked margin,
|
|
15
|
-
* but adds unrealized PnL only for isolated positions.
|
|
23
|
+
* Roll a Lighter account up into an {@link AccountSummary}, from the venue
|
|
24
|
+
* figures the response carries. `totalAssetValue` is total equity and
|
|
25
|
+
* `availableBalance` is buying power, so neither needs reconciling against the
|
|
26
|
+
* positions; the positions supply only the margin and PnL breakdown.
|
|
16
27
|
*
|
|
28
|
+
* @throws {PerpsError} `SDKError` when the account is not a Lighter one.
|
|
17
29
|
* @public
|
|
18
30
|
*/
|
|
19
31
|
export function getAccountSummary(
|
|
20
32
|
account: AccountResponse,
|
|
21
33
|
positions: Position[]
|
|
22
34
|
): AccountSummary {
|
|
23
|
-
|
|
24
|
-
for (const balance of account.collateralBalances) {
|
|
25
|
-
availableMargin = availableMargin.plus(balance.valueUsd)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let nonCollateralValue = new Big(0)
|
|
29
|
-
for (const balance of account.balances) {
|
|
30
|
-
nonCollateralValue = nonCollateralValue.plus(balance.valueUsd)
|
|
31
|
-
}
|
|
35
|
+
const config = lighterConfig(account)
|
|
32
36
|
|
|
33
37
|
let marginUsed = new Big(0)
|
|
34
38
|
let unrealizedPnl = new Big(0)
|
|
35
|
-
let isolatedUnrealizedPnl = new Big(0)
|
|
36
39
|
for (const position of positions) {
|
|
37
40
|
marginUsed = marginUsed.plus(position.marginUsed)
|
|
38
41
|
unrealizedPnl = unrealizedPnl.plus(position.unrealizedPnl)
|
|
39
|
-
if (position.marginMode === MarginMode.ISOLATED) {
|
|
40
|
-
isolatedUnrealizedPnl = isolatedUnrealizedPnl.plus(position.unrealizedPnl)
|
|
41
|
-
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
// `total_asset_value` already covers every asset the venue prices, so the
|
|
46
|
+
// spot `balances` rows must not be added on top of it.
|
|
47
|
+
portfolioValue: toRequiredBig(
|
|
48
|
+
config.totalAssetValue,
|
|
49
|
+
'totalAssetValue'
|
|
50
|
+
).toString(),
|
|
51
|
+
availableMargin: toRequiredBig(
|
|
52
|
+
config.availableBalance,
|
|
53
|
+
'availableBalance'
|
|
54
|
+
).toString(),
|
|
51
55
|
marginUsed: marginUsed.toString(),
|
|
52
56
|
unrealizedPnl: unrealizedPnl.toString(),
|
|
53
57
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExplorerChainId } from '@lifi/perps-sdk'
|
|
1
2
|
import type { FeeTier, LighterProviderKey } from '@lifi/perps-types'
|
|
2
3
|
import { LT_ASSET_ID_USDC } from './types/action.js'
|
|
3
4
|
|
|
@@ -114,6 +115,11 @@ export interface LighterDeployment {
|
|
|
114
115
|
* When omitted, transfer links are not emitted for this deployment.
|
|
115
116
|
*/
|
|
116
117
|
explorerTxBaseUrl?: string
|
|
118
|
+
/**
|
|
119
|
+
* L1 the deployment's bridge settles on. Deposit and withdrawal rows report
|
|
120
|
+
* an `l1_tx_hash` on this chain, not on the zkLighter L2.
|
|
121
|
+
*/
|
|
122
|
+
bridgeChainId: ExplorerChainId
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
/**
|
|
@@ -128,6 +134,7 @@ export const LIGHTER_MAINNET_DEPLOYMENT: LighterDeployment = Object.freeze({
|
|
|
128
134
|
signerChainId: LIGHTER_MAINNET_SIGNER_CHAIN_ID,
|
|
129
135
|
collateral: LIGHTER_COLLATERAL_ASSETS[LIGHTER_PROVIDER_KEY],
|
|
130
136
|
explorerTxBaseUrl: DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL,
|
|
137
|
+
bridgeChainId: ExplorerChainId.ETHEREUM,
|
|
131
138
|
})
|
|
132
139
|
|
|
133
140
|
/**
|
|
@@ -155,9 +162,9 @@ export const LIGHTER_RH_WS_URL = 'wss://api.rh.lighter.xyz/stream'
|
|
|
155
162
|
export const LIGHTER_RH_SIGNER_CHAIN_ID = 466324
|
|
156
163
|
|
|
157
164
|
/**
|
|
158
|
-
* Lighter on Robinhood chain, as served by `lighterRhProvider()`. No
|
|
159
|
-
* base URL: the RH zkLighter explorer is unpublished, so transfer
|
|
160
|
-
* left unset rather than pointed at the mainnet explorer.
|
|
165
|
+
* Lighter on Robinhood chain, as served by `lighterRhProvider()`. No L2
|
|
166
|
+
* explorer base URL: the RH zkLighter explorer is unpublished, so transfer
|
|
167
|
+
* links are left unset rather than pointed at the mainnet explorer.
|
|
161
168
|
*
|
|
162
169
|
* @public
|
|
163
170
|
*/
|
|
@@ -168,6 +175,7 @@ export const LIGHTER_RH_DEPLOYMENT: LighterDeployment = Object.freeze({
|
|
|
168
175
|
signerChainId: LIGHTER_RH_SIGNER_CHAIN_ID,
|
|
169
176
|
collateral: LIGHTER_COLLATERAL_ASSETS[LIGHTER_RH_PROVIDER_KEY],
|
|
170
177
|
explorerTxBaseUrl: undefined,
|
|
178
|
+
bridgeChainId: ExplorerChainId.ROBINHOOD,
|
|
171
179
|
})
|
|
172
180
|
|
|
173
181
|
/** @internal */
|
package/src/types/ws.ts
CHANGED
|
@@ -65,7 +65,7 @@ export type LtWsMarketStatsAllMessage = LtWsMessage & {
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Spot market stats carry no funding/open-interest fields and address
|
|
68
|
-
* markets by their
|
|
68
|
+
* markets by their `market_id`.
|
|
69
69
|
* @public
|
|
70
70
|
*/
|
|
71
71
|
export type LtWsSpotMarketStats = {
|
package/src/utils/mapFill.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../constants.js'
|
|
13
13
|
import type { LtTrade } from '../types/index.js'
|
|
14
14
|
import { leverageFromImf } from './mapPosition.js'
|
|
15
|
+
import { isPlaceholderTxHash } from './txHash.js'
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Fee charged on a fill, in the market's quote asset. Lighter publishes the
|
|
@@ -156,6 +157,8 @@ export const mapFill = (
|
|
|
156
157
|
trade.size
|
|
157
158
|
),
|
|
158
159
|
createdAt: new Date(trade.timestamp).toISOString(),
|
|
159
|
-
explorerLink:
|
|
160
|
+
explorerLink: isPlaceholderTxHash(trade.tx_hash)
|
|
161
|
+
? undefined
|
|
162
|
+
: explorerTxUrl(ExplorerChainId.LIGHTER, trade.tx_hash),
|
|
160
163
|
}
|
|
161
164
|
}
|
package/src/utils/mapOrder.ts
CHANGED
|
@@ -61,6 +61,9 @@ const mapOrderStatus = (
|
|
|
61
61
|
filledSize = new Big(order.filled_base_amount)
|
|
62
62
|
): OrderStatus => {
|
|
63
63
|
switch (order.status) {
|
|
64
|
+
case 'pending':
|
|
65
|
+
case 'in-progress':
|
|
66
|
+
return OrderStatus.ACCEPTED
|
|
64
67
|
case 'open':
|
|
65
68
|
if (order.trigger_status === 'parent-order') {
|
|
66
69
|
return OrderStatus.PENDING
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trailing hex characters a Lighter placeholder `tx_hash` leaves zero. Lighter
|
|
3
|
+
* reports a 40-byte hash on every fill and transfer row, and a row that settled
|
|
4
|
+
* no transaction carries a sequence pair in the leading 16 bytes and zero in
|
|
5
|
+
* the remaining 24. A settled row fills all 40 bytes.
|
|
6
|
+
*/
|
|
7
|
+
const PLACEHOLDER_ZERO_SUFFIX_LENGTH = 48
|
|
8
|
+
|
|
9
|
+
const ALL_ZERO = /^0+$/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Report whether a Lighter `tx_hash` is the placeholder the venue sends for a
|
|
13
|
+
* row that settled no transaction. Such a row resolves to no explorer link.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export const isPlaceholderTxHash = (txHash: string | undefined): boolean =>
|
|
18
|
+
txHash !== undefined &&
|
|
19
|
+
txHash.length > PLACEHOLDER_ZERO_SUFFIX_LENGTH &&
|
|
20
|
+
ALL_ZERO.test(txHash.slice(-PLACEHOLDER_ZERO_SUFFIX_LENGTH))
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
DEFAULT_LIGHTER_WS_URL,
|
|
30
30
|
LIGHTER_BASE_FEE_TIER,
|
|
31
31
|
LIGHTER_PROVIDER_KEY,
|
|
32
|
+
LIGHTER_SPOT_CATEGORY_ID,
|
|
32
33
|
} from '../constants.js'
|
|
33
34
|
import type { LighterPerpsProvider } from '../LighterProvider.js'
|
|
34
35
|
import type {
|
|
@@ -84,18 +85,19 @@ import {
|
|
|
84
85
|
// Orderbook is stateful: the first message is a full snapshot, subsequent
|
|
85
86
|
// messages are deltas where size=0 deletes a level.
|
|
86
87
|
|
|
87
|
-
const LIGHTER_SPOT_MARKET_ID_OFFSET = 2048
|
|
88
|
-
|
|
89
88
|
const LIGHTER_AUTH_CHANNEL = {
|
|
90
89
|
orderUpdates: 'account_all_orders',
|
|
91
90
|
fills: 'account_all_trades',
|
|
92
91
|
positions: 'account_all_positions',
|
|
93
92
|
} as const
|
|
94
93
|
|
|
95
|
-
/** Channels whose
|
|
94
|
+
/** Channels whose wire name or handler reads market identity from the registry. */
|
|
96
95
|
function channelNeedsMarkets(channel: Subscription['channel']): boolean {
|
|
97
96
|
return (
|
|
98
|
-
channel === '
|
|
97
|
+
channel === 'marketContext' ||
|
|
98
|
+
channel === 'orderUpdates' ||
|
|
99
|
+
channel === 'fills' ||
|
|
100
|
+
channel === 'positions'
|
|
99
101
|
)
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -258,10 +260,10 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
protected async openChannel(sub: Subscription): Promise<() => void> {
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// gating them on the
|
|
264
|
-
//
|
|
263
|
+
// `marketsContext` resolves to two fixed `/all` channels and `orderbook` is
|
|
264
|
+
// keyed purely by `String(market_id)`, so neither needs the registry;
|
|
265
|
+
// gating them on the sync would let a failed `/markets` fetch kill live
|
|
266
|
+
// price ticks.
|
|
265
267
|
if (channelNeedsMarkets(sub.channel)) {
|
|
266
268
|
await this.registry?.sync()
|
|
267
269
|
}
|
|
@@ -372,6 +374,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
372
374
|
return `accountSummary:${sub.address.toLowerCase()}`
|
|
373
375
|
case 'candle':
|
|
374
376
|
case 'spotBalances':
|
|
377
|
+
case 'availableToTrade':
|
|
375
378
|
throw new Error(`Lighter WS does not support channel: ${sub.channel}.`)
|
|
376
379
|
}
|
|
377
380
|
}
|
|
@@ -393,18 +396,21 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
393
396
|
]
|
|
394
397
|
}
|
|
395
398
|
if (sub.channel === 'marketContext') {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
// Lighter market ids carry no type information, so the wire channel comes
|
|
400
|
+
// from the registry row's category, not from the numeric id.
|
|
401
|
+
if (this.registry === undefined) {
|
|
402
|
+
throw new PerpsError(
|
|
403
|
+
PerpsErrorCode.MarketNotFound,
|
|
404
|
+
`Lighter WS: no market registry for marketId '${sub.marketId}'. ` +
|
|
405
|
+
'Construct via `lighterWsProvider({...})` and register with PerpsWsClient.'
|
|
401
406
|
)
|
|
402
407
|
}
|
|
408
|
+
const market = this.registry.require(sub.marketId)
|
|
403
409
|
const prefix =
|
|
404
|
-
|
|
410
|
+
market.categoryId === LIGHTER_SPOT_CATEGORY_ID
|
|
405
411
|
? 'spot_market_stats'
|
|
406
412
|
: 'market_stats'
|
|
407
|
-
return [{ channel: `${prefix}/${id}`, needsAuth: false }]
|
|
413
|
+
return [{ channel: `${prefix}/${market.id}`, needsAuth: false }]
|
|
408
414
|
}
|
|
409
415
|
if (sub.channel === 'orderbook') {
|
|
410
416
|
const id = Number(sub.marketId)
|