@lifi/perps-sdk 18.1.0 → 20.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 +15 -5
- package/dist/cjs/client/PerpsClient.d.ts +14 -8
- package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
- package/dist/cjs/client/PerpsClient.js +145 -53
- package/dist/cjs/client/PerpsClient.js.map +1 -1
- package/dist/cjs/client/createPerpsClient.d.ts +1 -6
- package/dist/cjs/client/createPerpsClient.d.ts.map +1 -1
- package/dist/cjs/client/createPerpsClient.js +1 -14
- package/dist/cjs/client/createPerpsClient.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -4
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/getOhlcv.d.ts.map +1 -1
- package/dist/cjs/services/getOhlcv.js.map +1 -1
- package/dist/cjs/types/api.d.ts +7 -6
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/config.d.ts +0 -9
- package/dist/cjs/types/config.d.ts.map +1 -1
- package/dist/cjs/types/provider.d.ts +5 -6
- package/dist/cjs/types/provider.d.ts.map +1 -1
- package/dist/cjs/types/withdrawal.d.ts +4 -2
- package/dist/cjs/types/withdrawal.d.ts.map +1 -1
- package/dist/cjs/utils/calculations.d.ts.map +1 -1
- package/dist/cjs/utils/calculations.js +107 -38
- package/dist/cjs/utils/calculations.js.map +1 -1
- package/dist/cjs/utils/decimal.d.ts +4 -0
- package/dist/cjs/utils/decimal.d.ts.map +1 -0
- package/dist/cjs/utils/decimal.js +14 -0
- package/dist/cjs/utils/decimal.js.map +1 -0
- package/dist/cjs/utils/math.d.ts +4 -0
- package/dist/cjs/utils/math.d.ts.map +1 -0
- package/dist/cjs/utils/math.js +8 -0
- package/dist/cjs/utils/math.js.map +1 -0
- package/dist/cjs/utils/parse.d.ts +1 -0
- package/dist/cjs/utils/parse.d.ts.map +1 -1
- package/dist/cjs/utils/parse.js +15 -0
- package/dist/cjs/utils/parse.js.map +1 -1
- package/dist/cjs/utils/positionMath.d.ts +5 -0
- package/dist/cjs/utils/positionMath.d.ts.map +1 -1
- package/dist/cjs/utils/positionMath.js +53 -6
- package/dist/cjs/utils/positionMath.js.map +1 -1
- package/dist/cjs/utils/setupActions.d.ts +4 -2
- package/dist/cjs/utils/setupActions.d.ts.map +1 -1
- package/dist/cjs/utils/setupActions.js +9 -1
- package/dist/cjs/utils/setupActions.js.map +1 -1
- package/dist/cjs/utils/switchChain.js +1 -1
- package/dist/cjs/utils/switchChain.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/websocket/PerpsWsClient.d.ts +0 -1
- package/dist/cjs/websocket/PerpsWsClient.d.ts.map +1 -1
- package/dist/cjs/websocket/PerpsWsClient.js +0 -7
- package/dist/cjs/websocket/PerpsWsClient.js.map +1 -1
- package/dist/esm/client/PerpsClient.d.ts +70 -61
- package/dist/esm/client/PerpsClient.d.ts.map +1 -1
- package/dist/esm/client/PerpsClient.js +215 -107
- package/dist/esm/client/PerpsClient.js.map +1 -1
- package/dist/esm/client/createPerpsClient.d.ts +1 -12
- package/dist/esm/client/createPerpsClient.d.ts.map +1 -1
- package/dist/esm/client/createPerpsClient.js +1 -19
- package/dist/esm/client/createPerpsClient.js.map +1 -1
- package/dist/esm/client/requireProvider.d.ts +1 -1
- package/dist/esm/client/requireProvider.js +1 -1
- package/dist/esm/index.d.ts +6 -4
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/services/getOhlcv.d.ts +1 -0
- package/dist/esm/services/getOhlcv.d.ts.map +1 -1
- package/dist/esm/services/getOhlcv.js.map +1 -1
- package/dist/esm/types/api.d.ts +39 -36
- package/dist/esm/types/api.d.ts.map +1 -1
- package/dist/esm/types/config.d.ts +0 -27
- package/dist/esm/types/config.d.ts.map +1 -1
- package/dist/esm/types/provider.d.ts +28 -33
- package/dist/esm/types/provider.d.ts.map +1 -1
- package/dist/esm/types/withdrawal.d.ts +24 -15
- package/dist/esm/types/withdrawal.d.ts.map +1 -1
- package/dist/esm/utils/calculations.d.ts +3 -2
- package/dist/esm/utils/calculations.d.ts.map +1 -1
- package/dist/esm/utils/calculations.js +109 -39
- package/dist/esm/utils/calculations.js.map +1 -1
- package/dist/esm/utils/decimal.d.ts +9 -0
- package/dist/esm/utils/decimal.d.ts.map +1 -0
- package/dist/esm/utils/decimal.js +12 -0
- package/dist/esm/utils/decimal.js.map +1 -0
- package/dist/esm/utils/math.d.ts +6 -0
- package/dist/esm/utils/math.d.ts.map +1 -0
- package/dist/esm/utils/math.js +5 -0
- package/dist/esm/utils/math.js.map +1 -0
- package/dist/esm/utils/parse.d.ts +7 -0
- package/dist/esm/utils/parse.d.ts.map +1 -1
- package/dist/esm/utils/parse.js +20 -0
- package/dist/esm/utils/parse.js.map +1 -1
- package/dist/esm/utils/positionMath.d.ts +13 -0
- package/dist/esm/utils/positionMath.d.ts.map +1 -1
- package/dist/esm/utils/positionMath.js +60 -6
- package/dist/esm/utils/positionMath.js.map +1 -1
- package/dist/esm/utils/setupActions.d.ts +17 -8
- package/dist/esm/utils/setupActions.d.ts.map +1 -1
- package/dist/esm/utils/setupActions.js +20 -7
- package/dist/esm/utils/setupActions.js.map +1 -1
- package/dist/esm/utils/switchChain.js +1 -1
- package/dist/esm/utils/switchChain.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/websocket/PerpsWsClient.d.ts +0 -7
- package/dist/esm/websocket/PerpsWsClient.d.ts.map +1 -1
- package/dist/esm/websocket/PerpsWsClient.js +0 -7
- package/dist/esm/websocket/PerpsWsClient.js.map +1 -1
- package/dist/types/client/PerpsClient.d.ts +70 -61
- package/dist/types/client/PerpsClient.d.ts.map +1 -1
- package/dist/types/client/createPerpsClient.d.ts +1 -12
- package/dist/types/client/createPerpsClient.d.ts.map +1 -1
- package/dist/types/client/requireProvider.d.ts +1 -1
- package/dist/types/index.d.ts +6 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/services/getOhlcv.d.ts +1 -0
- package/dist/types/services/getOhlcv.d.ts.map +1 -1
- package/dist/types/types/api.d.ts +39 -36
- package/dist/types/types/api.d.ts.map +1 -1
- package/dist/types/types/config.d.ts +0 -27
- package/dist/types/types/config.d.ts.map +1 -1
- package/dist/types/types/provider.d.ts +28 -33
- package/dist/types/types/provider.d.ts.map +1 -1
- package/dist/types/types/withdrawal.d.ts +24 -15
- package/dist/types/types/withdrawal.d.ts.map +1 -1
- package/dist/types/utils/calculations.d.ts +3 -2
- package/dist/types/utils/calculations.d.ts.map +1 -1
- package/dist/types/utils/decimal.d.ts +9 -0
- package/dist/types/utils/decimal.d.ts.map +1 -0
- package/dist/types/utils/math.d.ts +6 -0
- package/dist/types/utils/math.d.ts.map +1 -0
- package/dist/types/utils/parse.d.ts +7 -0
- package/dist/types/utils/parse.d.ts.map +1 -1
- package/dist/types/utils/positionMath.d.ts +13 -0
- package/dist/types/utils/positionMath.d.ts.map +1 -1
- package/dist/types/utils/setupActions.d.ts +17 -8
- package/dist/types/utils/setupActions.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/websocket/PerpsWsClient.d.ts +0 -7
- package/dist/types/websocket/PerpsWsClient.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/client/PerpsClient.ts +296 -137
- package/src/client/createPerpsClient.ts +4 -32
- package/src/client/requireProvider.ts +1 -1
- package/src/index.ts +8 -5
- package/src/services/getOhlcv.ts +1 -0
- package/src/types/api.ts +40 -40
- package/src/types/config.ts +0 -30
- package/src/types/provider.ts +29 -36
- package/src/types/withdrawal.ts +24 -15
- package/src/utils/calculations.ts +114 -43
- package/src/utils/decimal.ts +13 -0
- package/src/utils/math.ts +7 -0
- package/src/utils/parse.ts +25 -0
- package/src/utils/positionMath.ts +66 -6
- package/src/utils/setupActions.ts +27 -13
- package/src/utils/switchChain.ts +1 -1
- package/src/version.ts +1 -1
- package/src/websocket/PerpsWsClient.ts +0 -19
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
type QuoteSide,
|
|
17
17
|
type TradeType,
|
|
18
18
|
} from '@lifi/perps-types'
|
|
19
|
+
import type Big from 'big.js'
|
|
19
20
|
import { PerpsError } from '../errors/PerpsError.js'
|
|
21
|
+
import { areFinite, DivBig } from './decimal.js'
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Calculate position size in asset units from margin.
|
|
@@ -36,7 +38,13 @@ export function calculatePositionSize(
|
|
|
36
38
|
leverage: number,
|
|
37
39
|
price: number
|
|
38
40
|
): number {
|
|
39
|
-
|
|
41
|
+
if (price === 0) {
|
|
42
|
+
return (marginUsd * leverage) / price
|
|
43
|
+
}
|
|
44
|
+
if (!areFinite(marginUsd, leverage, price)) {
|
|
45
|
+
return Number.NaN
|
|
46
|
+
}
|
|
47
|
+
return new DivBig(marginUsd).times(leverage).div(price).toNumber()
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/**
|
|
@@ -48,7 +56,10 @@ export function calculatePositionSize(
|
|
|
48
56
|
* @public
|
|
49
57
|
*/
|
|
50
58
|
export function calculateNotionalValue(size: number, price: number): number {
|
|
51
|
-
|
|
59
|
+
if (!areFinite(size, price)) {
|
|
60
|
+
return Number.NaN
|
|
61
|
+
}
|
|
62
|
+
return new DivBig(size).abs().times(price).toNumber()
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
/**
|
|
@@ -65,7 +76,10 @@ export function calculateUnrealizedPnl(
|
|
|
65
76
|
currentPrice: number,
|
|
66
77
|
size: number
|
|
67
78
|
): number {
|
|
68
|
-
|
|
79
|
+
if (!areFinite(entryPrice, currentPrice, size)) {
|
|
80
|
+
return Number.NaN
|
|
81
|
+
}
|
|
82
|
+
return new DivBig(currentPrice).minus(entryPrice).times(size).toNumber()
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
/**
|
|
@@ -80,7 +94,10 @@ export function calculateRoe(pnl: number, margin: number): number {
|
|
|
80
94
|
if (margin === 0) {
|
|
81
95
|
return 0
|
|
82
96
|
}
|
|
83
|
-
|
|
97
|
+
if (!areFinite(pnl, margin)) {
|
|
98
|
+
return Number.NaN
|
|
99
|
+
}
|
|
100
|
+
return new DivBig(pnl).div(margin).times(100).toNumber()
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
/**
|
|
@@ -95,7 +112,13 @@ export function calculateRequiredMargin(
|
|
|
95
112
|
notionalValue: number,
|
|
96
113
|
leverage: number
|
|
97
114
|
): number {
|
|
98
|
-
|
|
115
|
+
if (leverage === 0) {
|
|
116
|
+
return notionalValue / leverage
|
|
117
|
+
}
|
|
118
|
+
if (!areFinite(notionalValue, leverage)) {
|
|
119
|
+
return Number.NaN
|
|
120
|
+
}
|
|
121
|
+
return new DivBig(notionalValue).div(leverage).toNumber()
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
/**
|
|
@@ -107,7 +130,10 @@ export function calculateRequiredMargin(
|
|
|
107
130
|
* @public
|
|
108
131
|
*/
|
|
109
132
|
export function estimateFees(sizeUsd: number, feeRate: number): number {
|
|
110
|
-
|
|
133
|
+
if (!areFinite(sizeUsd, feeRate)) {
|
|
134
|
+
return Number.NaN
|
|
135
|
+
}
|
|
136
|
+
return new DivBig(sizeUsd).times(feeRate).toNumber()
|
|
111
137
|
}
|
|
112
138
|
|
|
113
139
|
/**
|
|
@@ -124,8 +150,17 @@ export function applySlippage(
|
|
|
124
150
|
slippagePercent: number,
|
|
125
151
|
isBuy: boolean
|
|
126
152
|
): number {
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
if (!areFinite(price, slippagePercent)) {
|
|
154
|
+
return Number.NaN
|
|
155
|
+
}
|
|
156
|
+
const multiplier = new DivBig(slippagePercent).div(100).plus(1)
|
|
157
|
+
if (isBuy) {
|
|
158
|
+
return new DivBig(price).times(multiplier).toNumber()
|
|
159
|
+
}
|
|
160
|
+
if (multiplier.eq(0)) {
|
|
161
|
+
return price / multiplier.toNumber()
|
|
162
|
+
}
|
|
163
|
+
return new DivBig(price).div(multiplier).toNumber()
|
|
129
164
|
}
|
|
130
165
|
|
|
131
166
|
/**
|
|
@@ -166,7 +201,10 @@ export function effectiveLeverage(params: {
|
|
|
166
201
|
if (marginUsd === 0) {
|
|
167
202
|
return 0
|
|
168
203
|
}
|
|
169
|
-
|
|
204
|
+
if (!areFinite(positionValueUsd, marginUsd)) {
|
|
205
|
+
return Number.NaN
|
|
206
|
+
}
|
|
207
|
+
return new DivBig(positionValueUsd).div(marginUsd).toNumber()
|
|
170
208
|
}
|
|
171
209
|
|
|
172
210
|
/**
|
|
@@ -202,12 +240,15 @@ export function calculateExpectedPnl(
|
|
|
202
240
|
if (!triggerPrice || entryPrice === 0 || margin === 0) {
|
|
203
241
|
return null
|
|
204
242
|
}
|
|
243
|
+
if (!areFinite(triggerPrice, entryPrice, leverage, margin)) {
|
|
244
|
+
return { amount: Number.NaN, percent: Number.NaN }
|
|
245
|
+
}
|
|
205
246
|
const priceDiff = isLong
|
|
206
|
-
? triggerPrice
|
|
207
|
-
: entryPrice
|
|
208
|
-
const percent = (
|
|
209
|
-
const amount = margin
|
|
210
|
-
return { amount, percent }
|
|
247
|
+
? new DivBig(triggerPrice).minus(entryPrice)
|
|
248
|
+
: new DivBig(entryPrice).minus(triggerPrice)
|
|
249
|
+
const percent = priceDiff.div(entryPrice).times(leverage).times(100)
|
|
250
|
+
const amount = percent.times(margin).div(100)
|
|
251
|
+
return { amount: amount.toNumber(), percent: percent.toNumber() }
|
|
211
252
|
}
|
|
212
253
|
|
|
213
254
|
/**
|
|
@@ -228,8 +269,14 @@ export function priceFromPercent(
|
|
|
228
269
|
if (entryPrice === 0 || leverage === 0) {
|
|
229
270
|
return 0
|
|
230
271
|
}
|
|
231
|
-
|
|
232
|
-
|
|
272
|
+
if (!areFinite(percent, entryPrice, leverage)) {
|
|
273
|
+
return Number.NaN
|
|
274
|
+
}
|
|
275
|
+
const priceDelta = new DivBig(percent)
|
|
276
|
+
.times(entryPrice)
|
|
277
|
+
.div(new DivBig(leverage).times(100))
|
|
278
|
+
const entry = new DivBig(entryPrice)
|
|
279
|
+
return (isLong ? entry.plus(priceDelta) : entry.minus(priceDelta)).toNumber()
|
|
233
280
|
}
|
|
234
281
|
|
|
235
282
|
/**
|
|
@@ -250,8 +297,13 @@ export function percentFromPrice(
|
|
|
250
297
|
if (entryPrice === 0 || leverage === 0) {
|
|
251
298
|
return 0
|
|
252
299
|
}
|
|
253
|
-
|
|
254
|
-
|
|
300
|
+
if (!areFinite(price, entryPrice, leverage)) {
|
|
301
|
+
return Number.NaN
|
|
302
|
+
}
|
|
303
|
+
const priceDiff = isLong
|
|
304
|
+
? new DivBig(price).minus(entryPrice)
|
|
305
|
+
: new DivBig(entryPrice).minus(price)
|
|
306
|
+
return priceDiff.div(entryPrice).times(leverage).times(100).toNumber()
|
|
255
307
|
}
|
|
256
308
|
|
|
257
309
|
/**
|
|
@@ -268,11 +320,14 @@ export function calculateRealizedPnlPercent(
|
|
|
268
320
|
size: number,
|
|
269
321
|
price: number
|
|
270
322
|
): number {
|
|
271
|
-
|
|
272
|
-
|
|
323
|
+
if (!areFinite(realizedPnl, size, price)) {
|
|
324
|
+
return Number.NaN
|
|
325
|
+
}
|
|
326
|
+
const positionValue = new DivBig(size).abs().times(price)
|
|
327
|
+
if (positionValue.eq(0)) {
|
|
273
328
|
return 0
|
|
274
329
|
}
|
|
275
|
-
return (realizedPnl
|
|
330
|
+
return new DivBig(realizedPnl).div(positionValue).times(100).toNumber()
|
|
276
331
|
}
|
|
277
332
|
|
|
278
333
|
/** Result of {@link walkOrderbook} — the fill obtained for a USD-notional walk. */
|
|
@@ -283,7 +338,7 @@ interface BookWalk {
|
|
|
283
338
|
filledNotional: number
|
|
284
339
|
/** Volume-weighted average fill price, or 0 when the book is empty. */
|
|
285
340
|
vwap: number
|
|
286
|
-
/** True when the levels could not absorb the requested notional. */
|
|
341
|
+
/** True when the levels could not absorb the requested notional, and for a non-finite notional. */
|
|
287
342
|
insufficientLiquidity: boolean
|
|
288
343
|
}
|
|
289
344
|
|
|
@@ -293,7 +348,8 @@ interface BookWalk {
|
|
|
293
348
|
* in array order — the caller passes asks for a buy and bids for a sell, each
|
|
294
349
|
* already ordered best-price-first. When the book cannot absorb the full
|
|
295
350
|
* notional, the walk stops at the last level and flags `insufficientLiquidity`,
|
|
296
|
-
* returning the best obtainable fill.
|
|
351
|
+
* returning the best obtainable fill. A non-finite `sizeUsd` gives NaN fill
|
|
352
|
+
* fields with `insufficientLiquidity` set.
|
|
297
353
|
*
|
|
298
354
|
* @throws {PerpsError} `ValidationError` when a level's `price` or `size`
|
|
299
355
|
* does not parse to a finite number.
|
|
@@ -303,32 +359,47 @@ export function walkOrderbook(
|
|
|
303
359
|
levels: OrderbookLevel[],
|
|
304
360
|
sizeUsd: number
|
|
305
361
|
): BookWalk {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
362
|
+
if (!Number.isFinite(sizeUsd)) {
|
|
363
|
+
return {
|
|
364
|
+
baseSize: Number.NaN,
|
|
365
|
+
filledNotional: Number.NaN,
|
|
366
|
+
vwap: Number.NaN,
|
|
367
|
+
insufficientLiquidity: true,
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
let remaining = new DivBig(sizeUsd)
|
|
371
|
+
let baseSize = new DivBig(0)
|
|
372
|
+
let filledNotional = new DivBig(0)
|
|
309
373
|
for (const level of levels) {
|
|
310
|
-
if (remaining
|
|
374
|
+
if (remaining.lte(0)) {
|
|
311
375
|
break
|
|
312
376
|
}
|
|
313
|
-
const price =
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
`Malformed orderbook level: price='${level.price}', size='${level.size}'`
|
|
319
|
-
)
|
|
377
|
+
const { price, size } = parseLevel(level)
|
|
378
|
+
const levelNotional = size.times(price)
|
|
379
|
+
const take = remaining.lt(levelNotional) ? remaining : levelNotional
|
|
380
|
+
if (take.eq(0)) {
|
|
381
|
+
continue
|
|
320
382
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
baseSize += take / price
|
|
325
|
-
remaining -= take
|
|
383
|
+
filledNotional = filledNotional.plus(take)
|
|
384
|
+
baseSize = baseSize.plus(take.div(price))
|
|
385
|
+
remaining = remaining.minus(take)
|
|
326
386
|
}
|
|
327
387
|
return {
|
|
328
|
-
baseSize,
|
|
329
|
-
filledNotional,
|
|
330
|
-
vwap: baseSize
|
|
331
|
-
insufficientLiquidity: remaining
|
|
388
|
+
baseSize: baseSize.toNumber(),
|
|
389
|
+
filledNotional: filledNotional.toNumber(),
|
|
390
|
+
vwap: baseSize.eq(0) ? 0 : filledNotional.div(baseSize).toNumber(),
|
|
391
|
+
insufficientLiquidity: remaining.gt(0),
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function parseLevel(level: OrderbookLevel): { price: Big; size: Big } {
|
|
396
|
+
try {
|
|
397
|
+
return { price: new DivBig(level.price), size: new DivBig(level.size) }
|
|
398
|
+
} catch {
|
|
399
|
+
throw new PerpsError(
|
|
400
|
+
PerpsErrorCode.ValidationError,
|
|
401
|
+
`Malformed orderbook level: price='${level.price}', size='${level.size}'`
|
|
402
|
+
)
|
|
332
403
|
}
|
|
333
404
|
}
|
|
334
405
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Big from 'big.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scoped big.js constructor with 40 decimal places for division, so the
|
|
5
|
+
* shared global `Big.DP` and `Big.RM` stay untouched.
|
|
6
|
+
*/
|
|
7
|
+
export const DivBig = Big()
|
|
8
|
+
DivBig.DP = 40
|
|
9
|
+
|
|
10
|
+
/** True when every value is a finite number, so `new DivBig(value)` cannot throw. */
|
|
11
|
+
export function areFinite(...values: number[]): boolean {
|
|
12
|
+
return values.every(Number.isFinite)
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type Big from 'big.js'
|
|
2
|
+
|
|
3
|
+
/** The larger of two Big values, as `Math.max` for numbers. */
|
|
4
|
+
export const maxOf = (a: Big, b: Big): Big => (a.gt(b) ? a : b)
|
|
5
|
+
|
|
6
|
+
/** The smaller of two Big values, as `Math.min` for numbers. */
|
|
7
|
+
export const minOf = (a: Big, b: Big): Big => (a.lt(b) ? a : b)
|
package/src/utils/parse.ts
CHANGED
|
@@ -27,3 +27,28 @@ export function stringToFloat(value: string): number {
|
|
|
27
27
|
}
|
|
28
28
|
return parseFloat(cleaned)
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
const FORMATTED_NUMBER =
|
|
32
|
+
/^(?:[+-]\s*)?(?:\$\s*)?(?:(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?(?:\s*(?:%|USD))?$/i
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse an optional decimal field, with the same formatting tolerance as {@link stringToFloat}.
|
|
36
|
+
* Gives back `undefined` for a missing, malformed or non-finite value, and `0` for an empty string.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export function parseDecimal(
|
|
41
|
+
value: string | null | undefined
|
|
42
|
+
): number | undefined {
|
|
43
|
+
if (value == null) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
if (value.trim() === '') {
|
|
47
|
+
return stringToFloat(value)
|
|
48
|
+
}
|
|
49
|
+
if (!FORMATTED_NUMBER.test(value.trim())) {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
const parsed = stringToFloat(value)
|
|
53
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
54
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* always non-negative magnitudes; direction is carried by `isLong`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { areFinite, DivBig } from './decimal.js'
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Direction sign for a position.
|
|
11
13
|
*
|
|
@@ -50,8 +52,39 @@ export function estimateIsolatedLiquidationPrice(params: {
|
|
|
50
52
|
if (denominator === 0) {
|
|
51
53
|
return undefined
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
if (!areFinite(entryPrice, leverage, maintenanceMarginRate)) {
|
|
56
|
+
return Number.NaN
|
|
57
|
+
}
|
|
58
|
+
const mmr = new DivBig(maintenanceMarginRate)
|
|
59
|
+
const marginAvailable = new DivBig(1)
|
|
60
|
+
.div(leverage)
|
|
61
|
+
.minus(mmr)
|
|
62
|
+
.times(entryPrice)
|
|
63
|
+
return new DivBig(entryPrice)
|
|
64
|
+
.minus(marginAvailable.times(side).div(mmr.times(-side).plus(1)))
|
|
65
|
+
.toNumber()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether an isolated position with this liquidation price is already past
|
|
70
|
+
* liquidation at `currentPrice`: a long liquidates when the price falls to its
|
|
71
|
+
* liquidation level, a short when it rises to it. A non-positive price on
|
|
72
|
+
* either side counts as unknown, so the result is `false`.
|
|
73
|
+
*
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export function wouldImmediatelyLiquidate(params: {
|
|
77
|
+
liquidationPrice: number
|
|
78
|
+
currentPrice: number
|
|
79
|
+
isLong: boolean
|
|
80
|
+
}): boolean {
|
|
81
|
+
const { liquidationPrice, currentPrice, isLong } = params
|
|
82
|
+
if (currentPrice <= 0 || liquidationPrice <= 0) {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
return isLong
|
|
86
|
+
? liquidationPrice >= currentPrice
|
|
87
|
+
: liquidationPrice <= currentPrice
|
|
55
88
|
}
|
|
56
89
|
|
|
57
90
|
/**
|
|
@@ -84,7 +117,14 @@ export function predictAverageEntryPrice(params: {
|
|
|
84
117
|
if (!Number.isFinite(currentEntry) || !Number.isFinite(fillPrice)) {
|
|
85
118
|
return undefined
|
|
86
119
|
}
|
|
87
|
-
|
|
120
|
+
if (!areFinite(currentSize, addSize)) {
|
|
121
|
+
return Number.NaN
|
|
122
|
+
}
|
|
123
|
+
return new DivBig(currentSize)
|
|
124
|
+
.times(currentEntry)
|
|
125
|
+
.plus(new DivBig(addSize).times(fillPrice))
|
|
126
|
+
.div(new DivBig(currentSize).plus(addSize))
|
|
127
|
+
.toNumber()
|
|
88
128
|
}
|
|
89
129
|
|
|
90
130
|
/**
|
|
@@ -109,7 +149,13 @@ export function predictNewLeverage(params: {
|
|
|
109
149
|
if (totalMargin <= 0) {
|
|
110
150
|
return undefined
|
|
111
151
|
}
|
|
112
|
-
|
|
152
|
+
if (!areFinite(currentNotional, currentMargin, addNotional, addMargin)) {
|
|
153
|
+
return Number.NaN
|
|
154
|
+
}
|
|
155
|
+
return new DivBig(currentNotional)
|
|
156
|
+
.plus(addNotional)
|
|
157
|
+
.div(new DivBig(currentMargin).plus(addMargin))
|
|
158
|
+
.toNumber()
|
|
113
159
|
}
|
|
114
160
|
|
|
115
161
|
/**
|
|
@@ -127,7 +173,14 @@ export function predictUnrealizedPnl(params: {
|
|
|
127
173
|
isLong: boolean
|
|
128
174
|
}): number {
|
|
129
175
|
const { entryPrice, markPrice, size, isLong } = params
|
|
130
|
-
|
|
176
|
+
if (!areFinite(entryPrice, markPrice, size)) {
|
|
177
|
+
return Number.NaN
|
|
178
|
+
}
|
|
179
|
+
return new DivBig(markPrice)
|
|
180
|
+
.minus(entryPrice)
|
|
181
|
+
.times(size)
|
|
182
|
+
.times(directionSign(isLong))
|
|
183
|
+
.toNumber()
|
|
131
184
|
}
|
|
132
185
|
|
|
133
186
|
/**
|
|
@@ -145,5 +198,12 @@ export function realizedPnlOnClose(params: {
|
|
|
145
198
|
isLong: boolean
|
|
146
199
|
}): number {
|
|
147
200
|
const { entryPrice, closePrice, closeSize, isLong } = params
|
|
148
|
-
|
|
201
|
+
if (!areFinite(entryPrice, closePrice, closeSize)) {
|
|
202
|
+
return Number.NaN
|
|
203
|
+
}
|
|
204
|
+
return new DivBig(closePrice)
|
|
205
|
+
.minus(entryPrice)
|
|
206
|
+
.times(closeSize)
|
|
207
|
+
.times(directionSign(isLong))
|
|
208
|
+
.toNumber()
|
|
149
209
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ActionType, SetupAction, SetupOption } from '@lifi/perps-types'
|
|
2
2
|
import { PerpsSigner } from '@lifi/perps-types'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Select the `Provider.setup` descriptors a user
|
|
6
|
-
*
|
|
7
|
-
* signs on its own
|
|
8
|
-
*
|
|
5
|
+
* Select the `Provider.setup` descriptors a user sees: every choice step, and
|
|
6
|
+
* every non-choice step signed by {@link PerpsSigner.USER}. A non-choice step
|
|
7
|
+
* the SDK signs on its own is held back — `checkSetup` completes it inline, so
|
|
8
|
+
* surfacing it would render an inert card the user can't act on.
|
|
9
9
|
*
|
|
10
|
-
* Membership is a property of the descriptor
|
|
11
|
-
*
|
|
10
|
+
* Membership is a property of the descriptor alone, independent of any
|
|
11
|
+
* account. When an account is available, prefer `ProviderSetup.checklist`
|
|
12
12
|
* from `checkSetup` for the onboarding list — it applies this filter, omits
|
|
13
13
|
* not-required conditional steps, and carries each entry's satisfied state.
|
|
14
14
|
* This function remains the static, account-free projection.
|
|
@@ -16,10 +16,24 @@ import { PerpsSigner } from '@lifi/perps-types'
|
|
|
16
16
|
* @param setup The provider's `setup` descriptors, in their declared order.
|
|
17
17
|
* @public
|
|
18
18
|
*/
|
|
19
|
-
export function selectUserSetupActions(
|
|
20
|
-
setup
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
export function selectUserSetupActions(setup: SetupAction[]): SetupAction[] {
|
|
20
|
+
return setup.filter(isUserFacingSetupStep)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether a setup step is shown to the user rather than drained silently. */
|
|
24
|
+
export function isUserFacingSetupStep(descriptor: SetupAction): boolean {
|
|
25
|
+
return descriptor.options !== null || descriptor.signer === PerpsSigner.USER
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Narrow a setup option to the action it executes, so its bound `params` read
|
|
30
|
+
* with that action's param shape.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export function isSetupOptionFor<T extends ActionType>(
|
|
35
|
+
option: SetupOption,
|
|
36
|
+
type: T
|
|
37
|
+
): option is SetupOption<T> {
|
|
38
|
+
return option.type === type
|
|
25
39
|
}
|
package/src/utils/switchChain.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -20,13 +20,6 @@ export interface WsProviderFactoryParams {
|
|
|
20
20
|
provider: string
|
|
21
21
|
/** WS URL discovered from `/providers`. */
|
|
22
22
|
wsUrl: string
|
|
23
|
-
/**
|
|
24
|
-
* Markets visible to this provider via `/providers`, optionally filtered
|
|
25
|
-
* by the consumer's `createPerpsClient({ providers: { [key]: { markets } } })`
|
|
26
|
-
* config. Each provider interprets these its own way — Hyperliquid uses
|
|
27
|
-
* them to decide which sub-DEXes to subscribe to; Lighter ignores them.
|
|
28
|
-
*/
|
|
29
|
-
markets: string[]
|
|
30
23
|
/**
|
|
31
24
|
* The SDK client. WS providers use it to call core services (e.g.
|
|
32
25
|
* `getAssets` to source the wire-id ↔ display-symbol map) without
|
|
@@ -235,21 +228,9 @@ export class PerpsWsClient {
|
|
|
235
228
|
throw new Error(`No WebSocket URL found for provider: ${provider}`)
|
|
236
229
|
}
|
|
237
230
|
|
|
238
|
-
const allMarkets = (providerInfo.categories ?? []) as Array<{
|
|
239
|
-
id: string
|
|
240
|
-
}>
|
|
241
|
-
const providerConfig = this.client.config.providers?.[provider]
|
|
242
|
-
const configuredMarkets = providerConfig?.markets
|
|
243
|
-
const markets = (
|
|
244
|
-
configuredMarkets
|
|
245
|
-
? allMarkets.filter((m) => configuredMarkets.includes(m.id))
|
|
246
|
-
: allMarkets
|
|
247
|
-
).map((m) => m.id)
|
|
248
|
-
|
|
249
231
|
const wsProvider = factory({
|
|
250
232
|
provider,
|
|
251
233
|
wsUrl: providerInfo.wsUrl,
|
|
252
|
-
markets,
|
|
253
234
|
client: this.client,
|
|
254
235
|
})
|
|
255
236
|
this.providers.set(provider, wsProvider)
|