@lifi/perps-sdk-provider-lighter 36.0.0 → 37.0.1
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 +10 -10
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountSummary.d.ts.map +1 -1
- package/dist/cjs/accountSummary.js +13 -6
- package/dist/cjs/accountSummary.js.map +1 -1
- package/dist/cjs/types/activity.d.ts.map +1 -1
- package/dist/cjs/types/fill.d.ts +5 -0
- package/dist/cjs/types/fill.d.ts.map +1 -1
- package/dist/cjs/types/ws.d.ts.map +1 -1
- package/dist/cjs/utils/fundingRate.d.ts +2 -0
- package/dist/cjs/utils/fundingRate.d.ts.map +1 -0
- package/dist/cjs/utils/fundingRate.js +8 -0
- package/dist/cjs/utils/fundingRate.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 +1 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/mapFill.js +3 -3
- package/dist/cjs/utils/mapFill.js.map +1 -1
- package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/cjs/utils/mapMarketContext.js +13 -4
- package/dist/cjs/utils/mapMarketContext.js.map +1 -1
- package/dist/cjs/utils/mapOpenPositions.d.ts +1 -2
- package/dist/cjs/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/cjs/utils/mapOpenPositions.js +1 -13
- package/dist/cjs/utils/mapOpenPositions.js.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +31 -47
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
- package/dist/cjs/utils/mapPosition.js +34 -16
- package/dist/cjs/utils/mapPosition.js.map +1 -1
- package/dist/cjs/utils/transferMargin.js +1 -1
- package/dist/cjs/utils/transferMargin.js.map +1 -1
- package/dist/cjs/utils/wireEncoding.d.ts +0 -2
- package/dist/cjs/utils/wireEncoding.d.ts.map +1 -1
- package/dist/cjs/utils/wireEncoding.js +1 -15
- package/dist/cjs/utils/wireEncoding.js.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +46 -26
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/cjs/websocket/orderBookSide.d.ts +2 -1
- package/dist/cjs/websocket/orderBookSide.d.ts.map +1 -1
- package/dist/cjs/websocket/orderBookSide.js +5 -3
- package/dist/cjs/websocket/orderBookSide.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +13 -13
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountSummary.d.ts.map +1 -1
- package/dist/esm/accountSummary.js +14 -7
- package/dist/esm/accountSummary.js.map +1 -1
- package/dist/esm/types/activity.d.ts +1 -0
- package/dist/esm/types/activity.d.ts.map +1 -1
- package/dist/esm/types/fill.d.ts +5 -0
- package/dist/esm/types/fill.d.ts.map +1 -1
- package/dist/esm/types/ws.d.ts +2 -0
- package/dist/esm/types/ws.d.ts.map +1 -1
- package/dist/esm/utils/fundingRate.d.ts +8 -0
- package/dist/esm/utils/fundingRate.d.ts.map +1 -0
- package/dist/esm/utils/fundingRate.js +10 -0
- package/dist/esm/utils/fundingRate.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/mapFill.js +3 -3
- package/dist/esm/utils/mapFill.js.map +1 -1
- package/dist/esm/utils/mapMarketContext.d.ts +2 -1
- package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/esm/utils/mapMarketContext.js +15 -5
- package/dist/esm/utils/mapMarketContext.js.map +1 -1
- package/dist/esm/utils/mapOpenPositions.d.ts +1 -8
- package/dist/esm/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/esm/utils/mapOpenPositions.js +1 -18
- package/dist/esm/utils/mapOpenPositions.js.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts +6 -3
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +39 -51
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/mapPortfolioHistory.d.ts +2 -2
- package/dist/esm/utils/mapPortfolioHistory.js +2 -2
- package/dist/esm/utils/mapPosition.d.ts +4 -3
- package/dist/esm/utils/mapPosition.d.ts.map +1 -1
- package/dist/esm/utils/mapPosition.js +39 -20
- package/dist/esm/utils/mapPosition.js.map +1 -1
- package/dist/esm/utils/transferMargin.js +1 -1
- package/dist/esm/utils/transferMargin.js.map +1 -1
- package/dist/esm/utils/wireEncoding.d.ts +2 -28
- package/dist/esm/utils/wireEncoding.d.ts.map +1 -1
- package/dist/esm/utils/wireEncoding.js +2 -38
- package/dist/esm/utils/wireEncoding.js.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +56 -27
- package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/websocket/orderBookSide.d.ts +3 -2
- package/dist/esm/websocket/orderBookSide.d.ts.map +1 -1
- package/dist/esm/websocket/orderBookSide.js +5 -3
- package/dist/esm/websocket/orderBookSide.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountSummary.d.ts.map +1 -1
- package/dist/types/types/activity.d.ts +1 -0
- package/dist/types/types/activity.d.ts.map +1 -1
- package/dist/types/types/fill.d.ts +5 -0
- package/dist/types/types/fill.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +2 -0
- package/dist/types/types/ws.d.ts.map +1 -1
- package/dist/types/utils/fundingRate.d.ts +8 -0
- package/dist/types/utils/fundingRate.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/mapMarketContext.d.ts +2 -1
- package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
- package/dist/types/utils/mapOpenPositions.d.ts +1 -8
- package/dist/types/utils/mapOpenPositions.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts +6 -3
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/mapPortfolioHistory.d.ts +2 -2
- package/dist/types/utils/mapPosition.d.ts +4 -3
- package/dist/types/utils/mapPosition.d.ts.map +1 -1
- package/dist/types/utils/wireEncoding.d.ts +2 -28
- package/dist/types/utils/wireEncoding.d.ts.map +1 -1
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/types/websocket/orderBookSide.d.ts +3 -2
- package/dist/types/websocket/orderBookSide.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +22 -22
- package/src/accountSummary.ts +27 -6
- package/src/types/activity.ts +1 -0
- package/src/types/fill.ts +5 -0
- package/src/types/ws.ts +2 -0
- package/src/utils/fundingRate.ts +12 -0
- package/src/utils/index.ts +0 -2
- package/src/utils/mapFill.ts +3 -3
- package/src/utils/mapMarketContext.ts +18 -5
- package/src/utils/mapOpenPositions.ts +2 -30
- package/src/utils/mapOrder.ts +53 -71
- package/src/utils/mapPortfolioHistory.ts +2 -2
- package/src/utils/mapPosition.ts +50 -23
- package/src/utils/transferMargin.ts +1 -1
- package/src/utils/wireEncoding.ts +2 -43
- package/src/websocket/LighterWsProvider.ts +100 -60
- package/src/websocket/orderBookSide.ts +17 -4
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
PerpsErrorCode,
|
|
4
|
-
type PerpsMarketDisplay,
|
|
5
|
-
type Position,
|
|
6
|
-
} from '@lifi/perps-types'
|
|
7
|
-
import { LIGHTER_PROVIDER_KEY } from '../constants.js'
|
|
1
|
+
import { safeIsDecimalStringZero } from '@lifi/perps-sdk'
|
|
2
|
+
import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
|
|
8
3
|
import type { LtAccountPosition } from '../types/index.js'
|
|
9
4
|
import { mapPosition } from './mapPosition.js'
|
|
10
5
|
|
|
@@ -32,26 +27,3 @@ export const mapOpenPositions = (
|
|
|
32
27
|
const position = mapPosition(p, resolveMarket(p.market_id))
|
|
33
28
|
return position === undefined ? [] : [position]
|
|
34
29
|
})
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Map every open Lighter position for account totals, which must not leave a
|
|
38
|
-
* position out.
|
|
39
|
-
*
|
|
40
|
-
* @throws {PerpsError} `SDKError` when `mapPosition` skips an open row.
|
|
41
|
-
*/
|
|
42
|
-
export const requireOpenPositions = (
|
|
43
|
-
positions: LtAccountPosition[],
|
|
44
|
-
resolveMarket: (marketId: number) => PerpsMarketDisplay
|
|
45
|
-
): Position[] =>
|
|
46
|
-
positions.filter(isOpenPosition).map((p) => {
|
|
47
|
-
const position = mapPosition(p, resolveMarket(p.market_id))
|
|
48
|
-
if (position !== undefined) {
|
|
49
|
-
return position
|
|
50
|
-
}
|
|
51
|
-
const error = new PerpsError(
|
|
52
|
-
PerpsErrorCode.SDKError,
|
|
53
|
-
`${LIGHTER_PROVIDER_KEY} position for market ${p.market_id} is not valid, so the account totals are unknown.`
|
|
54
|
-
)
|
|
55
|
-
error.tool = LIGHTER_PROVIDER_KEY
|
|
56
|
-
throw error
|
|
57
|
-
})
|
package/src/utils/mapOrder.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isActiveOrderStatus,
|
|
3
|
-
isDecimalStringGreaterThan,
|
|
4
3
|
PerpsError,
|
|
5
4
|
safeDivideDecimalString,
|
|
5
|
+
safeIsDecimalStringGreaterThan,
|
|
6
6
|
safeTimestampToIsoString,
|
|
7
7
|
triggerConditionFor,
|
|
8
|
-
unknownToDecimalString,
|
|
9
8
|
warnSkippedVenueRow,
|
|
9
|
+
wsLog,
|
|
10
10
|
} from '@lifi/perps-sdk'
|
|
11
11
|
import type { MarketDisplay, Order, OrderBase } from '@lifi/perps-types'
|
|
12
12
|
import {
|
|
@@ -20,6 +20,9 @@ import { LIGHTER_PROVIDER_KEY } from '../constants.js'
|
|
|
20
20
|
import type { LtOrder } from '../types/index.js'
|
|
21
21
|
import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
|
|
22
22
|
|
|
23
|
+
// Lighter sends client index 0 as the string "0" for an order placed without one.
|
|
24
|
+
const NO_CLIENT_ORDER_ID = '0'
|
|
25
|
+
|
|
23
26
|
const mapOrderType = (type: string): Order['type'] => {
|
|
24
27
|
switch (type.replace(/-/g, '_')) {
|
|
25
28
|
case 'market':
|
|
@@ -62,18 +65,8 @@ const mapTimeInForce = (tif: string): TimeInForce => {
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
return unknownToDecimalString(value, field, LIGHTER_PROVIDER_KEY)
|
|
68
|
-
} catch {
|
|
69
|
-
return undefined
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const hasFill = (order: LtOrder): boolean => {
|
|
74
|
-
const filled = venueDecimal(order.filled_base_amount, 'filled_base_amount')
|
|
75
|
-
return filled !== undefined && isDecimalStringGreaterThan(filled, '0')
|
|
76
|
-
}
|
|
68
|
+
const hasFill = (order: LtOrder): boolean =>
|
|
69
|
+
safeIsDecimalStringGreaterThan(order.filled_base_amount, '0') === true
|
|
77
70
|
|
|
78
71
|
const mapOrderStatus = (order: LtOrder): OrderStatus => {
|
|
79
72
|
switch (order.status) {
|
|
@@ -113,84 +106,56 @@ const mapOrderStatus = (order: LtOrder): OrderStatus => {
|
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
const skipOrder = (
|
|
117
|
-
|
|
109
|
+
const skipOrder = (
|
|
110
|
+
marketId: string,
|
|
111
|
+
field: string,
|
|
112
|
+
value: unknown
|
|
113
|
+
): undefined => {
|
|
114
|
+
warnSkippedVenueRow(LIGHTER_PROVIDER_KEY, 'order', field, value, { marketId })
|
|
118
115
|
return undefined
|
|
119
116
|
}
|
|
120
117
|
|
|
121
|
-
const orderSizeOrSkip = (
|
|
122
|
-
field: 'initial_base_amount' | 'remaining_base_amount' | 'filled_base_amount',
|
|
123
|
-
value: string
|
|
124
|
-
): string | undefined =>
|
|
125
|
-
venueDecimal(value, field) === undefined ? skipOrder(field, value) : value
|
|
126
|
-
|
|
127
118
|
/**
|
|
128
119
|
* Map a Lighter order with its venue identity, lifecycle, and execution fields.
|
|
129
|
-
* A row with an invalid
|
|
130
|
-
*
|
|
120
|
+
* Venue sizes and prices pass verbatim. A row with an invalid time gives
|
|
121
|
+
* `undefined`. An average price that cannot be derived is omitted.
|
|
131
122
|
*/
|
|
132
123
|
export const mapOrder = (
|
|
133
124
|
order: LtOrder,
|
|
134
125
|
market: MarketDisplay
|
|
135
126
|
): Order | undefined => {
|
|
136
127
|
const type = mapOrderType(order.type)
|
|
137
|
-
const originalSize = orderSizeOrSkip(
|
|
138
|
-
'initial_base_amount',
|
|
139
|
-
order.initial_base_amount
|
|
140
|
-
)
|
|
141
|
-
if (originalSize === undefined) {
|
|
142
|
-
return undefined
|
|
143
|
-
}
|
|
144
|
-
const remainingSize = orderSizeOrSkip(
|
|
145
|
-
'remaining_base_amount',
|
|
146
|
-
order.remaining_base_amount
|
|
147
|
-
)
|
|
148
|
-
if (remainingSize === undefined) {
|
|
149
|
-
return undefined
|
|
150
|
-
}
|
|
151
|
-
const filledSize = orderSizeOrSkip(
|
|
152
|
-
'filled_base_amount',
|
|
153
|
-
order.filled_base_amount
|
|
154
|
-
)
|
|
155
|
-
if (filledSize === undefined) {
|
|
156
|
-
return undefined
|
|
157
|
-
}
|
|
158
128
|
const createdAt = rowTimestampToIsoStringOrUndefined(order.created_at * 1000)
|
|
159
129
|
if (createdAt === undefined) {
|
|
160
|
-
return skipOrder('created_at', order.created_at)
|
|
130
|
+
return skipOrder(market.id, 'created_at', order.created_at)
|
|
161
131
|
}
|
|
162
132
|
const updatedAt = rowTimestampToIsoStringOrUndefined(order.updated_at * 1000)
|
|
163
133
|
if (updatedAt === undefined) {
|
|
164
|
-
return skipOrder('updated_at', order.updated_at)
|
|
134
|
+
return skipOrder(market.id, 'updated_at', order.updated_at)
|
|
165
135
|
}
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
filledQuote !== undefined &&
|
|
173
|
-
filledAmount !== undefined &&
|
|
174
|
-
isDecimalStringGreaterThan(filledAmount, '0')
|
|
175
|
-
? safeDivideDecimalString(filledQuote, filledAmount)
|
|
176
|
-
: undefined
|
|
136
|
+
const averagePrice = hasFill(order)
|
|
137
|
+
? safeDivideDecimalString(
|
|
138
|
+
order.filled_quote_amount,
|
|
139
|
+
order.filled_base_amount
|
|
140
|
+
)
|
|
141
|
+
: undefined
|
|
177
142
|
const expiresAt =
|
|
178
143
|
order.order_expiry > 0
|
|
179
144
|
? safeTimestampToIsoString(order.order_expiry)
|
|
180
145
|
: undefined
|
|
181
146
|
const status = mapOrderStatus(order)
|
|
182
147
|
const base: OrderBase = {
|
|
183
|
-
orderId:
|
|
184
|
-
...(order.
|
|
148
|
+
orderId: order.order_id,
|
|
149
|
+
...(order.client_order_id === NO_CLIENT_ORDER_ID
|
|
185
150
|
? {}
|
|
186
|
-
: { clientOrderId:
|
|
151
|
+
: { clientOrderId: order.client_order_id }),
|
|
187
152
|
market,
|
|
188
153
|
side: order.is_ask ? OrderSide.SELL : OrderSide.BUY,
|
|
189
154
|
status,
|
|
190
155
|
...(status === OrderStatus.CANCELLED ? { statusReason: order.status } : {}),
|
|
191
|
-
originalSize,
|
|
192
|
-
remainingSize,
|
|
193
|
-
filledSize,
|
|
156
|
+
originalSize: order.initial_base_amount,
|
|
157
|
+
remainingSize: order.remaining_base_amount,
|
|
158
|
+
filledSize: order.filled_base_amount,
|
|
194
159
|
...(averagePrice === undefined ? {} : { averagePrice }),
|
|
195
160
|
reduceOnly: order.reduce_only,
|
|
196
161
|
...(order.parent_order_id ? { parentOrderId: order.parent_order_id } : {}),
|
|
@@ -214,9 +179,8 @@ export const mapOrder = (
|
|
|
214
179
|
type,
|
|
215
180
|
triggerPrice: order.trigger_price,
|
|
216
181
|
triggerCondition: triggerConditionFor(type, base.side),
|
|
217
|
-
...(
|
|
218
|
-
|
|
219
|
-
venueDecimal(order.price, 'price') !== undefined
|
|
182
|
+
...(type === OrderType.STOP_LIMIT ||
|
|
183
|
+
type === OrderType.TAKE_PROFIT_LIMIT
|
|
220
184
|
? { limitPrice: order.price }
|
|
221
185
|
: {}),
|
|
222
186
|
}
|
|
@@ -233,7 +197,10 @@ export const mapOrder = (
|
|
|
233
197
|
}
|
|
234
198
|
}
|
|
235
199
|
|
|
236
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Include terminal rows and their ids so consumers can update history and
|
|
202
|
+
* active caches. A row the mapper rejects is dropped and warns once.
|
|
203
|
+
*/
|
|
237
204
|
export const mapOrderUpdates = (
|
|
238
205
|
rawOrders: LtOrder[],
|
|
239
206
|
resolveMarket: (marketIndex: number) => MarketDisplay | undefined
|
|
@@ -242,9 +209,24 @@ export const mapOrderUpdates = (
|
|
|
242
209
|
const terminated: string[] = []
|
|
243
210
|
for (const raw of rawOrders) {
|
|
244
211
|
const market = resolveMarket(raw.market_index)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
212
|
+
let order: Order | undefined
|
|
213
|
+
let status: OrderStatus
|
|
214
|
+
try {
|
|
215
|
+
order = market === undefined ? undefined : mapOrder(raw, market)
|
|
216
|
+
status = order?.status ?? mapOrderStatus(raw)
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (!(error instanceof PerpsError)) {
|
|
219
|
+
throw error
|
|
220
|
+
}
|
|
221
|
+
wsLog.droppedRow(
|
|
222
|
+
LIGHTER_PROVIDER_KEY,
|
|
223
|
+
'order',
|
|
224
|
+
`${raw.order_id}: ${error.message}`
|
|
225
|
+
)
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
if (!isActiveOrderStatus(status)) {
|
|
229
|
+
terminated.push(raw.order_id)
|
|
248
230
|
}
|
|
249
231
|
if (order !== undefined) {
|
|
250
232
|
orders.push(order)
|
|
@@ -69,8 +69,8 @@ function getCumulativeNetFlow(snapshot: LtPnLEntry): string {
|
|
|
69
69
|
* Historical account values are anchored to the current account value. A
|
|
70
70
|
* snapshot with a non-finite value is skipped.
|
|
71
71
|
*
|
|
72
|
-
* @throws {PerpsError} `ValidationError` when `currentValue`
|
|
73
|
-
*
|
|
72
|
+
* @throws {PerpsError} `ValidationError` when `currentValue` does not match the
|
|
73
|
+
* decimal pattern.
|
|
74
74
|
*/
|
|
75
75
|
export const mapPortfolioHistory = (
|
|
76
76
|
range: PortfolioHistoryRange,
|
package/src/utils/mapPosition.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
absDecimalString,
|
|
3
|
+
createWarnOnce,
|
|
3
4
|
divideDecimalString,
|
|
4
5
|
isDecimalString,
|
|
5
6
|
isDecimalStringZero,
|
|
@@ -53,16 +54,32 @@ export const leverageFromScaledImf = (imf: number): string | undefined => {
|
|
|
53
54
|
return percent === undefined ? undefined : leverageFromImf(percent)
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
const skipPosition = (
|
|
57
|
-
|
|
57
|
+
const skipPosition = (
|
|
58
|
+
marketId: string,
|
|
59
|
+
field: string,
|
|
60
|
+
value: unknown
|
|
61
|
+
): undefined => {
|
|
62
|
+
warnSkippedVenueRow(LIGHTER_PROVIDER_KEY, 'position', field, value, {
|
|
63
|
+
marketId,
|
|
64
|
+
})
|
|
58
65
|
return undefined
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
const warnUnreadableFieldOnce = createWarnOnce()
|
|
69
|
+
|
|
70
|
+
const warnUnreadableField = (field: string, value: unknown): void => {
|
|
71
|
+
warnUnreadableFieldOnce(
|
|
72
|
+
`${LIGHTER_PROVIDER_KEY}|position.${field}`,
|
|
73
|
+
`[${LIGHTER_PROVIDER_KEY}] position \`${field}\` is not readable, so the derived fields are absent: '${String(value).slice(0, 64)}'`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
/**
|
|
62
78
|
* Map a raw Lighter account position to the generic Position type. A row with
|
|
63
|
-
* an invalid size
|
|
64
|
-
*
|
|
65
|
-
*
|
|
79
|
+
* an invalid size gives `undefined`. An invalid position value or a
|
|
80
|
+
* non-positive initial margin fraction keeps the row, leaves `leverage` and
|
|
81
|
+
* `initialMarginRequirement` absent, sets a cross `marginUsed` to `'0'` and
|
|
82
|
+
* warns once. Prices, PnL, funding and isolated margin pass through raw.
|
|
66
83
|
* @param market - Backend-resolved market identity for `pos.market_id`.
|
|
67
84
|
* @public
|
|
68
85
|
*/
|
|
@@ -71,39 +88,49 @@ export const mapPosition = (
|
|
|
71
88
|
market: PerpsMarketDisplay
|
|
72
89
|
): Position | undefined => {
|
|
73
90
|
if (!isDecimalString(pos.position)) {
|
|
74
|
-
return skipPosition('position', pos.position)
|
|
91
|
+
return skipPosition(market.id, 'position', pos.position)
|
|
75
92
|
}
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
const isIsolated = pos.margin_mode === LT_MARGIN_MODE_ISOLATED
|
|
94
|
+
const size = absDecimalString(pos.position)
|
|
95
|
+
const positionValue = isDecimalString(pos.position_value)
|
|
96
|
+
? absDecimalString(pos.position_value)
|
|
97
|
+
: undefined
|
|
98
|
+
if (positionValue === undefined) {
|
|
99
|
+
warnUnreadableField('position_value', pos.position_value)
|
|
78
100
|
}
|
|
79
101
|
const imf = pos.initial_margin_fraction
|
|
80
102
|
const leverage = isDecimalString(imf) ? leverageFromImf(imf) : undefined
|
|
81
103
|
if (leverage === undefined) {
|
|
82
|
-
|
|
104
|
+
warnUnreadableField('initial_margin_fraction', imf)
|
|
83
105
|
}
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
multiplyDecimalString(positionValue, imf),
|
|
89
|
-
'100'
|
|
90
|
-
)
|
|
106
|
+
const initialMarginRequirement =
|
|
107
|
+
positionValue === undefined || leverage === undefined
|
|
108
|
+
? undefined
|
|
109
|
+
: divideDecimalString(multiplyDecimalString(positionValue, imf), '100')
|
|
91
110
|
|
|
92
111
|
return {
|
|
93
112
|
market,
|
|
94
113
|
side: pos.sign >= 0 ? PositionSide.LONG : PositionSide.SHORT,
|
|
95
114
|
size,
|
|
96
115
|
entryPrice: pos.avg_entry_price,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
...(positionValue === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: {
|
|
119
|
+
markPrice:
|
|
120
|
+
isDecimalStringZero(positionValue) || isDecimalStringZero(size)
|
|
121
|
+
? '0'
|
|
122
|
+
: divideDecimalString(positionValue, size),
|
|
123
|
+
}),
|
|
101
124
|
liquidationPrice: pos.liquidation_price,
|
|
102
125
|
unrealizedPnl: pos.unrealized_pnl,
|
|
103
126
|
accruedFunding: pos.total_funding_paid_out ?? '0',
|
|
104
|
-
leverage,
|
|
105
|
-
marginUsed: isIsolated
|
|
106
|
-
|
|
127
|
+
...(leverage === undefined ? {} : { leverage }),
|
|
128
|
+
marginUsed: isIsolated
|
|
129
|
+
? pos.allocated_margin
|
|
130
|
+
: (initialMarginRequirement ?? '0'),
|
|
131
|
+
...(initialMarginRequirement === undefined
|
|
132
|
+
? {}
|
|
133
|
+
: { initialMarginRequirement }),
|
|
107
134
|
marginMode: isIsolated ? MarginMode.ISOLATED : MarginMode.CROSS,
|
|
108
135
|
}
|
|
109
136
|
}
|
|
@@ -23,7 +23,7 @@ function positionAmount(
|
|
|
23
23
|
if (!isDecimalString(value)) {
|
|
24
24
|
throw new PerpsError(
|
|
25
25
|
PerpsErrorCode.ValidationError,
|
|
26
|
-
`Invalid \`Position.${field}\`: '${value}'
|
|
26
|
+
`Invalid \`Position.${field}\`: '${value}' does not match the decimal pattern.`
|
|
27
27
|
)
|
|
28
28
|
}
|
|
29
29
|
return value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lighter wire encoding for the WASM signer:
|
|
3
|
-
*
|
|
2
|
+
* Lighter wire encoding for the WASM signer: order-type/time-in-force/expiry
|
|
3
|
+
* resolution mirroring lighter-go's rules.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { PerpsError } from '@lifi/perps-sdk'
|
|
@@ -19,47 +19,6 @@ import {
|
|
|
19
19
|
LT_TIME_IN_FORCE_POST_ONLY,
|
|
20
20
|
} from '../types/action.js'
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Convert Lighter's initial-margin fraction to the largest whole-number
|
|
24
|
-
* leverage exposed by the provider.
|
|
25
|
-
*
|
|
26
|
-
* Lighter expresses margin fractions in basis points (`10_000` = 100%), so
|
|
27
|
-
* the conversion is `floor(10_000 / fraction)`. Non-finite or non-positive
|
|
28
|
-
* input uses the safe `1x` fallback.
|
|
29
|
-
*
|
|
30
|
-
* @param fraction - Lighter's basis-point initial-margin fraction.
|
|
31
|
-
* @returns The floored maximum leverage, or `1` for invalid input.
|
|
32
|
-
* @public
|
|
33
|
-
*/
|
|
34
|
-
export const marginFractionToMaxLeverage = (fraction: number): number => {
|
|
35
|
-
if (!Number.isFinite(fraction) || fraction <= 0) {
|
|
36
|
-
return 1
|
|
37
|
-
}
|
|
38
|
-
return Math.floor(10_000 / fraction)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Convert requested leverage to Lighter's basis-point margin fraction.
|
|
43
|
-
*
|
|
44
|
-
* The signer expects `round(10_000 / leverage)`, where the result is the
|
|
45
|
-
* margin requirement in basis points.
|
|
46
|
-
*
|
|
47
|
-
* @param leverage - Requested leverage multiplier.
|
|
48
|
-
* @returns The rounded basis-point margin fraction.
|
|
49
|
-
* @throws {PerpsError} With `ValidationError` when leverage is less than
|
|
50
|
-
* or equal to zero.
|
|
51
|
-
* @public
|
|
52
|
-
*/
|
|
53
|
-
export const leverageToFraction = (leverage: number): number => {
|
|
54
|
-
if (leverage <= 0) {
|
|
55
|
-
throw new PerpsError(
|
|
56
|
-
PerpsErrorCode.ValidationError,
|
|
57
|
-
`Invalid leverage: ${leverage}`
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
return Math.round(10_000 / leverage)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
22
|
/**
|
|
64
23
|
* Map a LI.FI `OrderType` value to Lighter's integer wire type.
|
|
65
24
|
* Supported mappings are LIMIT → 0, MARKET → 1, STOP_MARKET → 2,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addDecimalString,
|
|
3
3
|
cachePromise,
|
|
4
|
+
createSafeFunction,
|
|
4
5
|
getMarketRegistry,
|
|
5
6
|
isDecimalStringGreaterThan,
|
|
6
7
|
type MarketRegistry,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
ReconnectingWebSocket,
|
|
13
14
|
resolveRetryPolicy,
|
|
14
15
|
resolveSubscribeQuote,
|
|
16
|
+
safeUnknownToDecimalString,
|
|
15
17
|
subtractDecimalString,
|
|
16
18
|
toPerpsMarketDisplay,
|
|
17
19
|
unknownToDecimalString,
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
import {
|
|
24
26
|
type AccountSummary,
|
|
25
27
|
type Fill,
|
|
28
|
+
type Market,
|
|
26
29
|
type MarketContext,
|
|
27
30
|
PerpsErrorCode,
|
|
28
31
|
type Position,
|
|
@@ -172,6 +175,57 @@ export interface LighterWsProviderOptions {
|
|
|
172
175
|
resolveAuthToken?: LighterAuthTokenResolver
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Perps equity plus the USD value of every positive held balance. A non-
|
|
180
|
+
* settlement asset counts its spot and margin routes; the settlement asset
|
|
181
|
+
* counts its spot route only, because the equity already holds its margin.
|
|
182
|
+
*
|
|
183
|
+
* @throws {PerpsError} `SDKError` when a held asset's spot mark does not
|
|
184
|
+
* match the decimal pattern.
|
|
185
|
+
*/
|
|
186
|
+
const heldPortfolioValue = (
|
|
187
|
+
perpsEquity: string,
|
|
188
|
+
balances: ReadonlyMap<number, { spot: string; margin: string }>,
|
|
189
|
+
settlementAssetIndex: number,
|
|
190
|
+
markets: readonly Market[],
|
|
191
|
+
contexts: readonly MarketContext[]
|
|
192
|
+
): string => {
|
|
193
|
+
const held = [...balances]
|
|
194
|
+
.map(
|
|
195
|
+
([assetId, { spot, margin }]) =>
|
|
196
|
+
[
|
|
197
|
+
assetId,
|
|
198
|
+
assetId === settlementAssetIndex
|
|
199
|
+
? spot
|
|
200
|
+
: addDecimalString(spot, margin),
|
|
201
|
+
] as const
|
|
202
|
+
)
|
|
203
|
+
.filter(([, balance]) => isDecimalStringGreaterThan(balance, '0'))
|
|
204
|
+
const spotPrices = spotPriceByAssetId(
|
|
205
|
+
markets,
|
|
206
|
+
LIGHTER_SPOT_CATEGORY_ID,
|
|
207
|
+
contexts,
|
|
208
|
+
new Set(
|
|
209
|
+
held
|
|
210
|
+
.filter(([assetId]) => assetId !== settlementAssetIndex)
|
|
211
|
+
.map(([assetId]) => String(assetId))
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
return lighterPortfolioValue(
|
|
215
|
+
perpsEquity,
|
|
216
|
+
held.map(
|
|
217
|
+
([assetId, balance]) =>
|
|
218
|
+
spotValuation(assetId, balance, settlementAssetIndex, spotPrices)
|
|
219
|
+
.valueUsd
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const safeHeldPortfolioValue = createSafeFunction(
|
|
225
|
+
'heldPortfolioValue',
|
|
226
|
+
heldPortfolioValue
|
|
227
|
+
)
|
|
228
|
+
|
|
175
229
|
/**
|
|
176
230
|
* Lighter WebSocket provider (extends {@link WsProviderBase}): subscribes to
|
|
177
231
|
* Lighter's WS channels (orderbook, marketsContext, orders, positions), attaching auth
|
|
@@ -723,47 +777,53 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
723
777
|
return
|
|
724
778
|
}
|
|
725
779
|
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
)
|
|
731
|
-
const collateral = unknownToDecimalString(
|
|
732
|
-
stats.collateral,
|
|
733
|
-
'stats.collateral',
|
|
734
|
-
this.providerKey
|
|
735
|
-
)
|
|
780
|
+
const read = (value: unknown, field: string): string | undefined =>
|
|
781
|
+
safeUnknownToDecimalString(value, field, this.providerKey)
|
|
782
|
+
const portfolio = read(stats.portfolio_value, 'stats.portfolio_value')
|
|
783
|
+
const collateral = read(stats.collateral, 'stats.collateral')
|
|
736
784
|
|
|
737
|
-
let available: string
|
|
738
|
-
let marginUsed: string
|
|
785
|
+
let available: string | undefined
|
|
786
|
+
let marginUsed: string | undefined
|
|
739
787
|
if (stats.cross_stats === undefined) {
|
|
740
788
|
// Compatibility with legacy gateways that omitted the entire
|
|
741
789
|
// cross_stats object.
|
|
742
|
-
available =
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
marginUsed = subtractDecimalString(portfolio, available)
|
|
790
|
+
available = read(stats.available_balance, 'stats.available_balance')
|
|
791
|
+
marginUsed =
|
|
792
|
+
portfolio === undefined || available === undefined
|
|
793
|
+
? undefined
|
|
794
|
+
: subtractDecimalString(portfolio, available)
|
|
748
795
|
} else {
|
|
749
|
-
const crossCollateral =
|
|
796
|
+
const crossCollateral = read(
|
|
750
797
|
stats.cross_stats.collateral,
|
|
751
|
-
'stats.cross_stats.collateral'
|
|
752
|
-
this.providerKey
|
|
798
|
+
'stats.cross_stats.collateral'
|
|
753
799
|
)
|
|
754
|
-
const crossPortfolio =
|
|
800
|
+
const crossPortfolio = read(
|
|
755
801
|
stats.cross_stats.portfolio_value,
|
|
756
|
-
'stats.cross_stats.portfolio_value'
|
|
757
|
-
this.providerKey
|
|
802
|
+
'stats.cross_stats.portfolio_value'
|
|
758
803
|
)
|
|
759
|
-
available =
|
|
804
|
+
available = read(
|
|
760
805
|
stats.cross_stats.available_balance,
|
|
761
|
-
'stats.cross_stats.available_balance'
|
|
762
|
-
this.providerKey
|
|
806
|
+
'stats.cross_stats.available_balance'
|
|
763
807
|
)
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
808
|
+
marginUsed =
|
|
809
|
+
collateral === undefined ||
|
|
810
|
+
crossCollateral === undefined ||
|
|
811
|
+
crossPortfolio === undefined ||
|
|
812
|
+
available === undefined
|
|
813
|
+
? undefined
|
|
814
|
+
: addDecimalString(
|
|
815
|
+
subtractDecimalString(collateral, crossCollateral),
|
|
816
|
+
subtractDecimalString(crossPortfolio, available)
|
|
817
|
+
)
|
|
818
|
+
}
|
|
819
|
+
// Every AccountSummary field is required, so the last summary stays.
|
|
820
|
+
if (
|
|
821
|
+
portfolio === undefined ||
|
|
822
|
+
collateral === undefined ||
|
|
823
|
+
available === undefined ||
|
|
824
|
+
marginUsed === undefined
|
|
825
|
+
) {
|
|
826
|
+
return
|
|
767
827
|
}
|
|
768
828
|
|
|
769
829
|
const inputs = this.accountSummaryInputs.get(address)
|
|
@@ -827,36 +887,16 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
827
887
|
if (inputs?.perps === undefined || inputs.balances === undefined) {
|
|
828
888
|
return
|
|
829
889
|
}
|
|
830
|
-
const
|
|
831
|
-
const held = [...inputs.balances]
|
|
832
|
-
.map(
|
|
833
|
-
([assetId, { spot, margin }]) =>
|
|
834
|
-
[
|
|
835
|
-
assetId,
|
|
836
|
-
assetId === settlementAssetIndex
|
|
837
|
-
? spot
|
|
838
|
-
: addDecimalString(spot, margin),
|
|
839
|
-
] as const
|
|
840
|
-
)
|
|
841
|
-
.filter(([, balance]) => isDecimalStringGreaterThan(balance, '0'))
|
|
842
|
-
const spotPrices = spotPriceByAssetId(
|
|
843
|
-
this.registry?.markets ?? [],
|
|
844
|
-
LIGHTER_SPOT_CATEGORY_ID,
|
|
845
|
-
Object.values(this.marketsContext),
|
|
846
|
-
new Set(
|
|
847
|
-
held
|
|
848
|
-
.filter(([assetId]) => assetId !== settlementAssetIndex)
|
|
849
|
-
.map(([assetId]) => String(assetId))
|
|
850
|
-
)
|
|
851
|
-
)
|
|
852
|
-
const portfolioValue = lighterPortfolioValue(
|
|
890
|
+
const portfolioValue = safeHeldPortfolioValue(
|
|
853
891
|
inputs.perps.equity,
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
)
|
|
892
|
+
inputs.balances,
|
|
893
|
+
this.settlementAssetIndex(),
|
|
894
|
+
this.registry?.markets ?? [],
|
|
895
|
+
Object.values(this.marketsContext)
|
|
859
896
|
)
|
|
897
|
+
if (portfolioValue === undefined) {
|
|
898
|
+
return
|
|
899
|
+
}
|
|
860
900
|
if (onlyOnChange && portfolioValue === inputs.lastPortfolioValue) {
|
|
861
901
|
return
|
|
862
902
|
}
|
|
@@ -1017,8 +1057,8 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
|
|
|
1017
1057
|
let state = this.orderbooks.get(marketId)
|
|
1018
1058
|
if (!state || isSnapshot) {
|
|
1019
1059
|
state = {
|
|
1020
|
-
bids: new OrderBookSide(bidOrder, this.providerKey),
|
|
1021
|
-
asks: new OrderBookSide(askOrder, this.providerKey),
|
|
1060
|
+
bids: new OrderBookSide(bidOrder, this.providerKey, assetId),
|
|
1061
|
+
asks: new OrderBookSide(askOrder, this.providerKey, assetId),
|
|
1022
1062
|
assetId,
|
|
1023
1063
|
}
|
|
1024
1064
|
this.orderbooks.set(marketId, state)
|