@lifi/perps-sdk 21.1.0 → 23.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.
Files changed (170) hide show
  1. package/README.md +89 -16
  2. package/dist/cjs/client/PerpsClient.d.ts +9 -9
  3. package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
  4. package/dist/cjs/client/PerpsClient.js +12 -17
  5. package/dist/cjs/client/PerpsClient.js.map +1 -1
  6. package/dist/cjs/decimal/convert.d.ts +1 -5
  7. package/dist/cjs/decimal/convert.d.ts.map +1 -1
  8. package/dist/cjs/decimal/convert.js +10 -28
  9. package/dist/cjs/decimal/convert.js.map +1 -1
  10. package/dist/cjs/decimal/format.d.ts +2 -1
  11. package/dist/cjs/decimal/format.d.ts.map +1 -1
  12. package/dist/cjs/decimal/format.js.map +1 -1
  13. package/dist/cjs/decimal/parse.d.ts +2 -4
  14. package/dist/cjs/decimal/parse.d.ts.map +1 -1
  15. package/dist/cjs/decimal/parse.js +5 -2
  16. package/dist/cjs/decimal/parse.js.map +1 -1
  17. package/dist/cjs/decimal/requireDecimal.d.ts.map +1 -0
  18. package/dist/cjs/{wire → decimal}/requireDecimal.js +2 -1
  19. package/dist/cjs/decimal/requireDecimal.js.map +1 -0
  20. package/dist/cjs/index.d.ts +5 -5
  21. package/dist/cjs/index.d.ts.map +1 -1
  22. package/dist/cjs/index.js +6 -20
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/math/fills.d.ts +3 -3
  25. package/dist/cjs/math/fills.d.ts.map +1 -1
  26. package/dist/cjs/math/fills.js.map +1 -1
  27. package/dist/cjs/math/order.d.ts +1 -8
  28. package/dist/cjs/math/order.d.ts.map +1 -1
  29. package/dist/cjs/math/order.js +6 -11
  30. package/dist/cjs/math/order.js.map +1 -1
  31. package/dist/cjs/math/position.d.ts +3 -8
  32. package/dist/cjs/math/position.d.ts.map +1 -1
  33. package/dist/cjs/math/position.js +12 -12
  34. package/dist/cjs/math/position.js.map +1 -1
  35. package/dist/cjs/services/resolveSubscribeQuote.d.ts +1 -1
  36. package/dist/cjs/services/resolveSubscribeQuote.d.ts.map +1 -1
  37. package/dist/cjs/services/resolveSubscribeQuote.js +24 -13
  38. package/dist/cjs/services/resolveSubscribeQuote.js.map +1 -1
  39. package/dist/cjs/types/api.d.ts +4 -4
  40. package/dist/cjs/types/api.d.ts.map +1 -1
  41. package/dist/cjs/types/config.d.ts +0 -1
  42. package/dist/cjs/types/config.d.ts.map +1 -1
  43. package/dist/cjs/types/provider.d.ts +1 -1
  44. package/dist/cjs/types/provider.d.ts.map +1 -1
  45. package/dist/cjs/version.d.ts +1 -1
  46. package/dist/cjs/version.js +1 -1
  47. package/dist/cjs/wire/orderAmounts.d.ts +1 -2
  48. package/dist/cjs/wire/orderAmounts.d.ts.map +1 -1
  49. package/dist/cjs/wire/orderAmounts.js +19 -52
  50. package/dist/cjs/wire/orderAmounts.js.map +1 -1
  51. package/dist/cjs/wire/refuel.js +1 -1
  52. package/dist/cjs/wire/refuel.js.map +1 -1
  53. package/dist/cjs/wire/transferable.js +1 -1
  54. package/dist/cjs/wire/transferable.js.map +1 -1
  55. package/dist/cjs/wire/withdraw.d.ts.map +1 -1
  56. package/dist/cjs/wire/withdraw.js +1 -1
  57. package/dist/cjs/wire/withdraw.js.map +1 -1
  58. package/dist/esm/client/PerpsClient.d.ts +9 -9
  59. package/dist/esm/client/PerpsClient.d.ts.map +1 -1
  60. package/dist/esm/client/PerpsClient.js +12 -17
  61. package/dist/esm/client/PerpsClient.js.map +1 -1
  62. package/dist/esm/decimal/convert.d.ts +8 -24
  63. package/dist/esm/decimal/convert.d.ts.map +1 -1
  64. package/dist/esm/decimal/convert.js +16 -42
  65. package/dist/esm/decimal/convert.js.map +1 -1
  66. package/dist/esm/decimal/format.d.ts +2 -1
  67. package/dist/esm/decimal/format.d.ts.map +1 -1
  68. package/dist/esm/decimal/format.js.map +1 -1
  69. package/dist/esm/decimal/parse.d.ts +7 -20
  70. package/dist/esm/decimal/parse.d.ts.map +1 -1
  71. package/dist/esm/decimal/parse.js +9 -4
  72. package/dist/esm/decimal/parse.js.map +1 -1
  73. package/dist/esm/{wire → decimal}/requireDecimal.d.ts +1 -1
  74. package/dist/esm/decimal/requireDecimal.d.ts.map +1 -0
  75. package/dist/esm/{wire → decimal}/requireDecimal.js +3 -2
  76. package/dist/esm/decimal/requireDecimal.js.map +1 -0
  77. package/dist/esm/index.d.ts +5 -5
  78. package/dist/esm/index.d.ts.map +1 -1
  79. package/dist/esm/index.js +4 -4
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/esm/math/fills.d.ts +3 -3
  82. package/dist/esm/math/fills.d.ts.map +1 -1
  83. package/dist/esm/math/fills.js +1 -1
  84. package/dist/esm/math/fills.js.map +1 -1
  85. package/dist/esm/math/order.d.ts +3 -34
  86. package/dist/esm/math/order.d.ts.map +1 -1
  87. package/dist/esm/math/order.js +8 -26
  88. package/dist/esm/math/order.js.map +1 -1
  89. package/dist/esm/math/position.d.ts +16 -34
  90. package/dist/esm/math/position.d.ts.map +1 -1
  91. package/dist/esm/math/position.js +23 -36
  92. package/dist/esm/math/position.js.map +1 -1
  93. package/dist/esm/services/resolveSubscribeQuote.d.ts +6 -1
  94. package/dist/esm/services/resolveSubscribeQuote.d.ts.map +1 -1
  95. package/dist/esm/services/resolveSubscribeQuote.js +29 -13
  96. package/dist/esm/services/resolveSubscribeQuote.js.map +1 -1
  97. package/dist/esm/types/api.d.ts +4 -4
  98. package/dist/esm/types/api.d.ts.map +1 -1
  99. package/dist/esm/types/config.d.ts +0 -11
  100. package/dist/esm/types/config.d.ts.map +1 -1
  101. package/dist/esm/types/provider.d.ts +1 -1
  102. package/dist/esm/types/provider.d.ts.map +1 -1
  103. package/dist/esm/version.d.ts +1 -1
  104. package/dist/esm/version.js +1 -1
  105. package/dist/esm/wire/orderAmounts.d.ts +15 -29
  106. package/dist/esm/wire/orderAmounts.d.ts.map +1 -1
  107. package/dist/esm/wire/orderAmounts.js +28 -88
  108. package/dist/esm/wire/orderAmounts.js.map +1 -1
  109. package/dist/esm/wire/refuel.js +1 -1
  110. package/dist/esm/wire/refuel.js.map +1 -1
  111. package/dist/esm/wire/transferable.js +1 -1
  112. package/dist/esm/wire/transferable.js.map +1 -1
  113. package/dist/esm/wire/withdraw.d.ts.map +1 -1
  114. package/dist/esm/wire/withdraw.js +1 -1
  115. package/dist/esm/wire/withdraw.js.map +1 -1
  116. package/dist/types/client/PerpsClient.d.ts +9 -9
  117. package/dist/types/client/PerpsClient.d.ts.map +1 -1
  118. package/dist/types/decimal/convert.d.ts +8 -24
  119. package/dist/types/decimal/convert.d.ts.map +1 -1
  120. package/dist/types/decimal/format.d.ts +2 -1
  121. package/dist/types/decimal/format.d.ts.map +1 -1
  122. package/dist/types/decimal/parse.d.ts +7 -20
  123. package/dist/types/decimal/parse.d.ts.map +1 -1
  124. package/dist/types/{wire → decimal}/requireDecimal.d.ts +1 -1
  125. package/dist/types/decimal/requireDecimal.d.ts.map +1 -0
  126. package/dist/types/index.d.ts +5 -5
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/types/math/fills.d.ts +3 -3
  129. package/dist/types/math/fills.d.ts.map +1 -1
  130. package/dist/types/math/order.d.ts +3 -34
  131. package/dist/types/math/order.d.ts.map +1 -1
  132. package/dist/types/math/position.d.ts +16 -34
  133. package/dist/types/math/position.d.ts.map +1 -1
  134. package/dist/types/services/resolveSubscribeQuote.d.ts +6 -1
  135. package/dist/types/services/resolveSubscribeQuote.d.ts.map +1 -1
  136. package/dist/types/types/api.d.ts +4 -4
  137. package/dist/types/types/api.d.ts.map +1 -1
  138. package/dist/types/types/config.d.ts +0 -11
  139. package/dist/types/types/config.d.ts.map +1 -1
  140. package/dist/types/types/provider.d.ts +1 -1
  141. package/dist/types/types/provider.d.ts.map +1 -1
  142. package/dist/types/version.d.ts +1 -1
  143. package/dist/types/wire/orderAmounts.d.ts +15 -29
  144. package/dist/types/wire/orderAmounts.d.ts.map +1 -1
  145. package/dist/types/wire/withdraw.d.ts.map +1 -1
  146. package/package.json +2 -2
  147. package/src/client/PerpsClient.ts +41 -26
  148. package/src/decimal/convert.ts +27 -54
  149. package/src/decimal/format.ts +3 -1
  150. package/src/decimal/parse.ts +10 -6
  151. package/src/{wire → decimal}/requireDecimal.ts +3 -6
  152. package/src/index.ts +4 -21
  153. package/src/math/fills.ts +8 -4
  154. package/src/math/order.ts +8 -31
  155. package/src/math/position.ts +32 -43
  156. package/src/services/resolveSubscribeQuote.ts +37 -19
  157. package/src/types/api.ts +4 -3
  158. package/src/types/config.ts +0 -11
  159. package/src/types/provider.ts +1 -1
  160. package/src/version.ts +1 -1
  161. package/src/wire/orderAmounts.ts +35 -117
  162. package/src/wire/refuel.ts +1 -1
  163. package/src/wire/transferable.ts +1 -1
  164. package/src/wire/withdraw.ts +1 -1
  165. package/dist/cjs/wire/requireDecimal.d.ts.map +0 -1
  166. package/dist/cjs/wire/requireDecimal.js.map +0 -1
  167. package/dist/esm/wire/requireDecimal.d.ts.map +0 -1
  168. package/dist/esm/wire/requireDecimal.js.map +0 -1
  169. package/dist/types/wire/requireDecimal.d.ts.map +0 -1
  170. /package/dist/cjs/{wire → decimal}/requireDecimal.d.ts +0 -0
@@ -1,23 +1,20 @@
1
1
  /**
2
- * Display-tier position formulas. Every function takes and gives `number`;
3
- * exact decimal arithmetic happens internally with `DivBig`.
2
+ * Display-tier position formulas and margin-adjustment predicates. Every
3
+ * formula takes and gives `number`; exact decimal arithmetic happens
4
+ * internally with `DivBig`.
4
5
  *
5
6
  * Sign convention: long = +1, short = -1. Sizes passed to the `estimate*`
6
7
  * helpers are always non-negative magnitudes; direction is carried by
7
8
  * `isLong`.
8
9
  */
9
10
 
11
+ import {
12
+ MarginMode,
13
+ type Position,
14
+ PositionMarginAdjustment,
15
+ } from '@lifi/perps-types'
10
16
  import { areFinite, DivBig } from '../decimal/big.js'
11
17
 
12
- /**
13
- * Direction sign for a position.
14
- *
15
- * @deprecated Inline `isLong ? 1 : -1`. Removed in the next major.
16
- */
17
- export function directionSign(isLong: boolean): 1 | -1 {
18
- return isLong ? 1 : -1
19
- }
20
-
21
18
  /**
22
19
  * Calculate notional value of a position.
23
20
  *
@@ -112,11 +109,6 @@ export function calculateLiquidationDistance(params: {
112
109
  return Math.abs((liquidationPrice - currentPrice) / currentPrice) * 100
113
110
  }
114
111
 
115
- /**
116
- * @deprecated Use `calculateLiquidationDistance`. Removed in the next major.
117
- */
118
- export const liquidationDistancePercent = calculateLiquidationDistance
119
-
120
112
  /**
121
113
  * Effective leverage of an open position.
122
114
  *
@@ -141,11 +133,6 @@ export function calculateEffectiveLeverage(params: {
141
133
  return new DivBig(positionValueUsd).div(marginUsd).toNumber()
142
134
  }
143
135
 
144
- /**
145
- * @deprecated Use `calculateEffectiveLeverage`. Removed in the next major.
146
- */
147
- export const effectiveLeverage = calculateEffectiveLeverage
148
-
149
136
  /**
150
137
  * Estimated liquidation price for an isolated-margin position, parameterised
151
138
  * by the venue's maintenance margin rate.
@@ -192,11 +179,6 @@ export function estimateLiquidationPrice(params: {
192
179
  .toNumber()
193
180
  }
194
181
 
195
- /**
196
- * @deprecated Use `estimateLiquidationPrice`. Removed in the next major.
197
- */
198
- export const estimateIsolatedLiquidationPrice = estimateLiquidationPrice
199
-
200
182
  /**
201
183
  * Whether an isolated position with this liquidation price is already past
202
184
  * liquidation at `currentPrice`: a long liquidates when the price falls to its
@@ -259,11 +241,6 @@ export function estimateAverageEntryPrice(params: {
259
241
  .toNumber()
260
242
  }
261
243
 
262
- /**
263
- * @deprecated Use `estimateAverageEntryPrice`. Removed in the next major.
264
- */
265
- export const predictAverageEntryPrice = estimateAverageEntryPrice
266
-
267
244
  /**
268
245
  * Estimated effective leverage after adding margin and notional.
269
246
  *
@@ -295,11 +272,6 @@ export function estimateNewLeverage(params: {
295
272
  .toNumber()
296
273
  }
297
274
 
298
- /**
299
- * @deprecated Use `estimateNewLeverage`. Removed in the next major.
300
- */
301
- export const predictNewLeverage = estimateNewLeverage
302
-
303
275
  /**
304
276
  * Estimated unrealised PnL at the current mark price.
305
277
  *
@@ -325,11 +297,6 @@ export function estimateUnrealizedPnl(params: {
325
297
  .toNumber()
326
298
  }
327
299
 
328
- /**
329
- * @deprecated Use `estimateUnrealizedPnl`. Removed in the next major.
330
- */
331
- export const predictUnrealizedPnl = estimateUnrealizedPnl
332
-
333
300
  /**
334
301
  * Realised PnL on the portion of a position being closed.
335
302
  *
@@ -356,6 +323,28 @@ export function calculateRealizedPnl(params: {
356
323
  }
357
324
 
358
325
  /**
359
- * @deprecated Use `calculateRealizedPnl`. Removed in the next major.
326
+ * Whether this position can take a margin adjustment at all: it holds margin of
327
+ * its own and its market exposes individual position margin.
328
+ *
329
+ * @public
330
+ */
331
+ export function positionSupportsMarginAdjustment(position: Position): boolean {
332
+ return (
333
+ position.marginMode === MarginMode.ISOLATED &&
334
+ position.market.positionMarginAdjustment !== PositionMarginAdjustment.NONE
335
+ )
336
+ }
337
+
338
+ /**
339
+ * Whether a removal is among the adjustments this position permits. An
340
+ * `ADD_ONLY` market takes adds and no withdrawal.
341
+ *
342
+ * @public
360
343
  */
361
- export const realizedPnlOnClose = calculateRealizedPnl
344
+ export function positionSupportsMarginRemoval(position: Position): boolean {
345
+ return (
346
+ positionSupportsMarginAdjustment(position) &&
347
+ position.market.positionMarginAdjustment ===
348
+ PositionMarginAdjustment.ADD_AND_REMOVE
349
+ )
350
+ }
@@ -1,8 +1,10 @@
1
- import type {
2
- FeeTier,
3
- MarketContext,
4
- OrderbookResponse,
1
+ import {
2
+ type FeeTier,
3
+ type MarketContext,
4
+ type OrderbookResponse,
5
+ PerpsErrorCode,
5
6
  } from '@lifi/perps-types'
7
+ import { PerpsError } from '../errors/PerpsError.js'
6
8
  import { buildQuote } from '../math/order.js'
7
9
  import type {
8
10
  PerpsSDKClient,
@@ -10,6 +12,7 @@ import type {
10
12
  QuoteListener,
11
13
  } from '../types/provider.js'
12
14
  import type { WsProvider } from '../websocket/types.js'
15
+ import { wsLog } from '../websocket/wsLog.js'
13
16
  import { resolveQuoteMarket, resolveQuotePrice } from './resolveQuote.js'
14
17
 
15
18
  /**
@@ -37,6 +40,11 @@ export const QUOTE_THROTTLE_MS = 100
37
40
  * listeners once and cancels any pending trailing emission, so the wire
38
41
  * subscriptions' ref counts cannot be double-decremented.
39
42
  *
43
+ * A throw while building or delivering a streamed quote (an unparsable mark
44
+ * price, a throwing `onQuote`) is logged via `wsLog.listenerFailure` and the
45
+ * emission is skipped, on both the immediate and the trailing-timer path.
46
+ *
47
+ * @throws {PerpsError} `ValidationError` when `params.size` is not finite.
40
48
  * @throws {PerpsError} `MarketNotFound` when no market matches symbol+type.
41
49
  * @internal
42
50
  */
@@ -48,6 +56,12 @@ export async function resolveSubscribeQuote(
48
56
  feeTier: FeeTier,
49
57
  onQuote: QuoteListener
50
58
  ): Promise<() => void> {
59
+ if (!Number.isFinite(params.size)) {
60
+ throw new PerpsError(
61
+ PerpsErrorCode.ValidationError,
62
+ `Quote size must be a finite number, got ${params.size}`
63
+ )
64
+ }
51
65
  const market = await resolveQuoteMarket(client, provider, params)
52
66
  // REST snapshot seeds the context; the marketContext subscription below
53
67
  // keeps it live so streamed impact/funding track the moving market.
@@ -66,21 +80,25 @@ export async function resolveSubscribeQuote(
66
80
  return
67
81
  }
68
82
  lastEmitAt = Date.now()
69
- onQuote(
70
- buildQuote({
71
- provider,
72
- symbol: params.symbol,
73
- type: params.type,
74
- side: params.side,
75
- sizeUsd: params.size,
76
- market,
77
- price: latestContext,
78
- bids: latestBook.bids,
79
- asks: latestBook.asks,
80
- feeTier,
81
- timestamp: Date.now(),
82
- })
83
- )
83
+ try {
84
+ onQuote(
85
+ buildQuote({
86
+ provider,
87
+ symbol: params.symbol,
88
+ type: params.type,
89
+ side: params.side,
90
+ sizeUsd: params.size,
91
+ market,
92
+ price: latestContext,
93
+ bids: latestBook.bids,
94
+ asks: latestBook.asks,
95
+ feeTier,
96
+ timestamp: Date.now(),
97
+ })
98
+ )
99
+ } catch (error) {
100
+ wsLog.listenerFailure(provider, 'quote', error)
101
+ }
84
102
  }
85
103
 
86
104
  const unsubscribeContext = await ws.subscribe(
package/src/types/api.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  ActionParamsMap,
7
7
  PlaceTwapOrderParams as ActionPlaceTwapOrderParams,
8
8
  ActionStep,
9
+ DecimalString,
9
10
  ExecuteActionResponse,
10
11
  MarginMode,
11
12
  MarketRef,
@@ -137,9 +138,9 @@ export interface PlaceOrderParams {
137
138
  side: OrderSide
138
139
  type: Exclude<OrderType, OrderType.TWAP>
139
140
  /** Base-asset size as a decimal wire string. */
140
- size: string
141
+ size: DecimalString
141
142
  /** Limit price as a decimal wire string; required by limit-style orders. */
142
- price: string
143
+ price: DecimalString
143
144
  /** Optional leverage multiplier; provider defaults apply when omitted. */
144
145
  leverage?: number
145
146
  /**
@@ -223,7 +224,7 @@ export interface SendAssetActionParams {
223
224
  /** Destination DEX/account identifier understood by the provider. */
224
225
  destinationDex: string
225
226
  /** Transfer amount as a provider-compatible decimal wire string. */
226
- amount: string
227
+ amount: DecimalString
227
228
  }
228
229
 
229
230
  /**
@@ -41,17 +41,6 @@ export type RequestInterceptor = (
41
41
  export interface SDKRequestOptions {
42
42
  /** Abort the request and any pending backoff delay. */
43
43
  signal?: AbortSignal
44
- /**
45
- * Lighter auth token consumed by provider-direct venue reads (getOrders,
46
- * getOrder, getActivity, getAccount). Create via
47
- * `lighterSigner.createAuthToken(deadline, context)`. Sent only to Lighter
48
- * venue endpoints, never to the LI.FI backend — read-only by design
49
- * (8h max TTL, cannot authorize writes). Each call routes to a single Lighter
50
- * instance via `params.provider`, so this token scopes to that instance's
51
- * venue; token caches are held per `lighterProvider()` instance and do not
52
- * cross between instances.
53
- */
54
- lighterAuthToken?: string
55
44
  }
56
45
 
57
46
  /**
@@ -597,7 +597,7 @@ export interface PerpsProviderPlugin {
597
597
  * @throws {PerpsError} `ValidationError` when a `Position` decimal that the
598
598
  * venue formula reads is malformed.
599
599
  */
600
- positionRemovableMargin(position: Position): string | undefined
600
+ positionRemovableMargin(position: Position): DecimalString | undefined
601
601
 
602
602
  /**
603
603
  * Project a typed {@link AccountConfig} against the provider's `setup`
package/src/version.ts CHANGED
@@ -10,4 +10,4 @@ export const name = '@lifi/perps-sdk'
10
10
  *
11
11
  * @public
12
12
  */
13
- export const version = '21.1.0'
13
+ export const version = '23.0.0'
@@ -1,12 +1,7 @@
1
- import {
2
- type DecimalString,
3
- isDecimalString,
4
- type Market,
5
- PerpsErrorCode,
6
- } from '@lifi/perps-types'
1
+ import type { DecimalString, Market } from '@lifi/perps-types'
7
2
  import Big from 'big.js'
8
- import { TruncBig } from '../decimal/big.js'
9
- import { PerpsError } from '../errors/PerpsError.js'
3
+ import { DivBig, TruncBig } from '../decimal/big.js'
4
+ import { isDecimalString } from '../decimal/parse.js'
10
5
  import type { PerpsSDKClient } from '../types/provider.js'
11
6
  import { snapOrderSize } from './snap.js'
12
7
 
@@ -19,66 +14,22 @@ export interface OrderAmountsInput {
19
14
  amount: DecimalString
20
15
  leverage: number
21
16
  price: DecimalString
22
- /** Decimals for the quote-asset amounts (margin, notional). Default 2. */
23
- quoteDecimals?: number
24
17
  }
25
18
 
26
19
  /** @public */
27
20
  export interface OrderAmounts {
28
21
  /**
29
- * On the `quoteDecimals` grid: rounded up when derived, so it funds `size`
30
- * at the requested leverage, and truncated when the user holds it.
22
+ * The held `amount`, byte-identical, when the user holds the margin;
23
+ * otherwise `notional ÷ leverage`, a display and comparison value that
24
+ * never reaches a venue.
31
25
  */
32
26
  margin: DecimalString
33
- /** Snapped onto the venue lot grid. */
27
+ /** Snapped onto the venue lot grid; the one amount an order sends. */
34
28
  size: DecimalString
35
- /** Truncated to `quoteDecimals`. */
29
+ /** Exactly `size × price`, from the snapped size. */
36
30
  notional: DecimalString
37
31
  }
38
32
 
39
- const DEFAULT_QUOTE_DECIMALS = 2
40
- const TEN = new Big(10)
41
-
42
- /** Round a quote amount down onto its decimal grid, with no trailing zeros. */
43
- function quoteAmount(value: Big, decimals: number): DecimalString {
44
- const truncated = value.round(decimals, Big.roundDown)
45
- return truncated.eq(0) ? '0' : truncated.toFixed()
46
- }
47
-
48
- /**
49
- * The smallest margin on the `decimals` grid that funds `notional` at
50
- * `leverage`, so `margin × leverage ≥ notional` holds. The one derived amount
51
- * that rounds up: a margin rounded down under-funds the size it backs.
52
- *
53
- * The ceiling is exact: the remainder decides the carry, so the result never
54
- * leans on a fixed decimal-place limit the way `div` then `round` would.
55
- */
56
- function coveringMargin(
57
- notional: Big,
58
- leverage: Big,
59
- decimals: number
60
- ): DecimalString {
61
- const scale = TEN.pow(decimals)
62
- const scaled = notional.times(scale)
63
- const remainder = scaled.mod(leverage)
64
- const whole = scaled.minus(remainder).div(leverage)
65
- const covering = remainder.eq(0) ? whole : whole.plus(1)
66
- return covering.eq(0) ? '0' : covering.div(scale).toFixed()
67
- }
68
-
69
- /**
70
- * An order needs three positive amounts. A grid that snaps any of them to
71
- * zero — a sub-lot size, a sub-cent quote amount — describes no order a venue
72
- * can take, so the caller gets `null` instead of a result it has to re-check.
73
- */
74
- function executable(amounts: OrderAmounts): OrderAmounts | null {
75
- const positive =
76
- new Big(amounts.size).gt(0) &&
77
- new Big(amounts.margin).gt(0) &&
78
- new Big(amounts.notional).gt(0)
79
- return positive ? amounts : null
80
- }
81
-
82
33
  function positiveDecimal(value: DecimalString): Big | null {
83
34
  if (!isDecimalString(value)) {
84
35
  return null
@@ -89,47 +40,22 @@ function positiveDecimal(value: DecimalString): Big | null {
89
40
 
90
41
  /**
91
42
  * The three order-entry amounts, derived from whichever one the user typed.
43
+ * Only `size` snaps, onto the market's venue lot grid, because an order sends
44
+ * size alone; no quote amount has a grid. A held margin returns byte-identical,
45
+ * `notional` is the snapped size × price, and a derived margin is
46
+ * `notional ÷ leverage` in `DivBig` (40 places, half-up).
92
47
  *
93
- * The held field is normalised first — truncated to `quoteDecimals` for a
94
- * margin or notional, snapped onto the venue lot grid for a size — and the
95
- * other two are then derived from that normalised value, so the three agree
96
- * with each other rather than with an input the user cannot see.
97
- *
98
- * Rounding is directional, so the result always funds itself:
99
- * `size × price ≤ margin × leverage`. A derived size truncates onto the lot
100
- * grid and a derived notional truncates onto the quote grid, but a derived
101
- * margin rounds **up** onto the quote grid, because it is the one amount that
102
- * has to cover the others rather than cap them.
103
- *
104
- * Formulas: `size = margin × leverage ÷ price`,
105
- * `margin = size × price ÷ leverage`, `notional = size × price`
106
- * (or `margin × leverage` when the margin is held).
107
- *
108
- * @returns `null` when `amount` or `price` is not a positive decimal string,
109
- * when `leverage` is not a positive finite number, or when the venue and
110
- * quote grids snap the result to a non-positive amount — a sub-lot size or
111
- * a sub-cent quote amount describes no order a venue can take.
112
- * @throws {PerpsError} `ValidationError` when `quoteDecimals` is not a
113
- * non-negative integer, or `SDKError` when `market.providerId` names no
114
- * registered provider plugin.
48
+ * @returns `null` when `amount` or `price` is not a positive
49
+ * {@link DecimalString}, when `leverage` is not a positive finite number, or
50
+ * when the snapped size is zero (below one lot).
51
+ * @throws {PerpsError} `SDKError` when `market.providerId` names no registered
52
+ * provider plugin.
115
53
  * @public
116
54
  */
117
55
  export function calculateOrderAmounts(
118
56
  input: OrderAmountsInput
119
57
  ): OrderAmounts | null {
120
- const {
121
- sdk,
122
- market,
123
- held,
124
- leverage,
125
- quoteDecimals = DEFAULT_QUOTE_DECIMALS,
126
- } = input
127
- if (!Number.isInteger(quoteDecimals) || quoteDecimals < 0) {
128
- throw new PerpsError(
129
- PerpsErrorCode.ValidationError,
130
- `Invalid quoteDecimals for order amounts: ${quoteDecimals}`
131
- )
132
- }
58
+ const { sdk, market, held, leverage } = input
133
59
  const amount = positiveDecimal(input.amount)
134
60
  const price = positiveDecimal(input.price)
135
61
  if (
@@ -140,33 +66,25 @@ export function calculateOrderAmounts(
140
66
  ) {
141
67
  return null
142
68
  }
143
- const leverageFactor = new TruncBig(leverage)
144
69
 
145
- if (held === 'size') {
146
- const size = snapOrderSize(sdk, market, amount.toFixed())
147
- const notional = new TruncBig(size).times(price)
148
- return executable({
149
- margin: coveringMargin(notional, leverageFactor, quoteDecimals),
150
- size,
151
- notional: quoteAmount(notional, quoteDecimals),
152
- })
70
+ const requested =
71
+ held === 'size'
72
+ ? input.amount
73
+ : held === 'notional'
74
+ ? amount.div(price).toFixed()
75
+ : amount.times(leverage).div(price).toFixed()
76
+ const size = snapOrderSize(sdk, market, requested)
77
+ if (new Big(size).eq(0)) {
78
+ return null
153
79
  }
154
80
 
155
- if (held === 'notional') {
156
- const notional = quoteAmount(amount, quoteDecimals)
157
- const exact = new TruncBig(notional)
158
- return executable({
159
- margin: coveringMargin(exact, leverageFactor, quoteDecimals),
160
- size: snapOrderSize(sdk, market, exact.div(price).toFixed()),
161
- notional,
162
- })
81
+ const notional = new TruncBig(size).times(price)
82
+ return {
83
+ margin:
84
+ held === 'margin'
85
+ ? input.amount
86
+ : new DivBig(notional).div(leverage).toFixed(),
87
+ size,
88
+ notional: notional.toFixed(),
163
89
  }
164
-
165
- const margin = quoteAmount(amount, quoteDecimals)
166
- const notional = new TruncBig(margin).times(leverage)
167
- return executable({
168
- margin,
169
- size: snapOrderSize(sdk, market, notional.div(price).toFixed()),
170
- notional: quoteAmount(notional, quoteDecimals),
171
- })
172
90
  }
@@ -1,7 +1,7 @@
1
1
  import { type DecimalString, PerpsErrorCode } from '@lifi/perps-types'
2
2
  import { DivBig } from '../decimal/big.js'
3
+ import { requireDecimal } from '../decimal/requireDecimal.js'
3
4
  import { PerpsError } from '../errors/PerpsError.js'
4
- import { requireDecimal } from './requireDecimal.js'
5
5
 
6
6
  /** @public */
7
7
  export interface RefuelAmountInput {
@@ -1,5 +1,5 @@
1
1
  import type { DecimalString } from '@lifi/perps-types'
2
- import { requireDecimal } from './requireDecimal.js'
2
+ import { requireDecimal } from '../decimal/requireDecimal.js'
3
3
 
4
4
  /**
5
5
  * The part of `units` that a venue-wide free figure releases: never below
@@ -1,6 +1,6 @@
1
1
  import type { DecimalString } from '@lifi/perps-types'
2
+ import { requireDecimal } from '../decimal/requireDecimal.js'
2
3
  import type { WithdrawableBalance } from '../types/withdrawal.js'
3
- import { requireDecimal } from './requireDecimal.js'
4
4
 
5
5
  /**
6
6
  * Largest `amount` a withdrawable row can fund: `available` when the venue
@@ -1 +0,0 @@
1
- {"version":3,"file":"requireDecimal.d.ts","sourceRoot":"","sources":["../../../src/wire/requireDecimal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,GAAG,MAAM,QAAQ,CAAA;AAUxB,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAQvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"requireDecimal.js","sourceRoot":"","sources":["../../../src/wire/requireDecimal.ts"],"names":[],"mappings":";;;;;AAeA,wCAQC;AAvBD,mDAI0B;AAC1B,oDAAwB;AACxB,2DAAoD;AASpD,SAAgB,cAAc,CAAC,KAAoB,EAAE,KAAa;IAChE,IAAI,CAAC,IAAA,6BAAe,EAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,0BAAU,CAClB,4BAAc,CAAC,eAAe,EAC9B,aAAa,KAAK,QAAQ,KAAK,4BAA4B,CAC5D,CAAA;IACH,CAAC;IACD,OAAO,IAAI,gBAAG,CAAC,KAAK,CAAC,CAAA;AACvB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"requireDecimal.d.ts","sourceRoot":"","sources":["../../../src/wire/requireDecimal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,GAAG,MAAM,QAAQ,CAAA;AAGxB;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAQvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"requireDecimal.js","sourceRoot":"","sources":["../../../src/wire/requireDecimal.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,cAAc,GACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,GAAG,MAAM,QAAQ,CAAA;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAoB,EAAE,KAAa;IAChE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,UAAU,CAClB,cAAc,CAAC,eAAe,EAC9B,aAAa,KAAK,QAAQ,KAAK,4BAA4B,CAC5D,CAAA;IACH,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;AACvB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"requireDecimal.d.ts","sourceRoot":"","sources":["../../../src/wire/requireDecimal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,GAAG,MAAM,QAAQ,CAAA;AAGxB;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAQvE"}
File without changes