@lifi/perps-sdk 24.0.0 → 25.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 (163) hide show
  1. package/README.md +34 -28
  2. package/dist/cjs/decimal/arithmetic.d.ts +2 -0
  3. package/dist/cjs/decimal/arithmetic.d.ts.map +1 -1
  4. package/dist/cjs/decimal/arithmetic.js +6 -1
  5. package/dist/cjs/decimal/arithmetic.js.map +1 -1
  6. package/dist/cjs/decimal/convert.d.ts +0 -2
  7. package/dist/cjs/decimal/convert.d.ts.map +1 -1
  8. package/dist/cjs/decimal/convert.js +2 -11
  9. package/dist/cjs/decimal/convert.js.map +1 -1
  10. package/dist/cjs/decimal/decimalStringToBig.d.ts.map +1 -1
  11. package/dist/cjs/decimal/decimalStringToBig.js +3 -5
  12. package/dist/cjs/decimal/decimalStringToBig.js.map +1 -1
  13. package/dist/cjs/decimal/parse.d.ts +1 -0
  14. package/dist/cjs/decimal/parse.d.ts.map +1 -1
  15. package/dist/cjs/decimal/parse.js +2 -1
  16. package/dist/cjs/decimal/parse.js.map +1 -1
  17. package/dist/cjs/errors/invalidInput.d.ts +3 -0
  18. package/dist/cjs/errors/invalidInput.d.ts.map +1 -0
  19. package/dist/cjs/errors/invalidInput.js +9 -0
  20. package/dist/cjs/errors/invalidInput.js.map +1 -0
  21. package/dist/cjs/index.d.ts +5 -4
  22. package/dist/cjs/index.d.ts.map +1 -1
  23. package/dist/cjs/index.js +10 -6
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/math/order.d.ts +4 -4
  26. package/dist/cjs/math/order.d.ts.map +1 -1
  27. package/dist/cjs/math/order.js +31 -18
  28. package/dist/cjs/math/order.js.map +1 -1
  29. package/dist/cjs/math/position.d.ts +3 -3
  30. package/dist/cjs/math/position.d.ts.map +1 -1
  31. package/dist/cjs/math/position.js +13 -11
  32. package/dist/cjs/math/position.js.map +1 -1
  33. package/dist/cjs/types/provider.d.ts.map +1 -1
  34. package/dist/cjs/utils/warnOnce.d.ts +6 -2
  35. package/dist/cjs/utils/warnOnce.d.ts.map +1 -1
  36. package/dist/cjs/utils/warnOnce.js +12 -6
  37. package/dist/cjs/utils/warnOnce.js.map +1 -1
  38. package/dist/cjs/version.d.ts +1 -1
  39. package/dist/cjs/version.js +1 -1
  40. package/dist/cjs/websocket/wsLog.d.ts +1 -1
  41. package/dist/cjs/websocket/wsLog.d.ts.map +1 -1
  42. package/dist/cjs/websocket/wsLog.js +4 -3
  43. package/dist/cjs/websocket/wsLog.js.map +1 -1
  44. package/dist/cjs/wire/orderAmounts.d.ts +3 -2
  45. package/dist/cjs/wire/orderAmounts.d.ts.map +1 -1
  46. package/dist/cjs/wire/orderAmounts.js +17 -13
  47. package/dist/cjs/wire/orderAmounts.js.map +1 -1
  48. package/dist/cjs/wire/refuel.js +2 -2
  49. package/dist/cjs/wire/refuel.js.map +1 -1
  50. package/dist/esm/decimal/arithmetic.d.ts +24 -10
  51. package/dist/esm/decimal/arithmetic.d.ts.map +1 -1
  52. package/dist/esm/decimal/arithmetic.js +26 -10
  53. package/dist/esm/decimal/arithmetic.js.map +1 -1
  54. package/dist/esm/decimal/compare.d.ts +3 -3
  55. package/dist/esm/decimal/compare.js +3 -3
  56. package/dist/esm/decimal/convert.d.ts +2 -24
  57. package/dist/esm/decimal/convert.d.ts.map +1 -1
  58. package/dist/esm/decimal/convert.js +3 -32
  59. package/dist/esm/decimal/convert.js.map +1 -1
  60. package/dist/esm/decimal/decimalStringToBig.d.ts +4 -5
  61. package/dist/esm/decimal/decimalStringToBig.d.ts.map +1 -1
  62. package/dist/esm/decimal/decimalStringToBig.js +7 -10
  63. package/dist/esm/decimal/decimalStringToBig.js.map +1 -1
  64. package/dist/esm/decimal/format.js +3 -3
  65. package/dist/esm/decimal/parse.d.ts +8 -1
  66. package/dist/esm/decimal/parse.d.ts.map +1 -1
  67. package/dist/esm/decimal/parse.js +8 -1
  68. package/dist/esm/decimal/parse.js.map +1 -1
  69. package/dist/esm/errors/invalidInput.d.ts +4 -0
  70. package/dist/esm/errors/invalidInput.d.ts.map +1 -0
  71. package/dist/esm/errors/invalidInput.js +7 -0
  72. package/dist/esm/errors/invalidInput.js.map +1 -0
  73. package/dist/esm/index.d.ts +5 -4
  74. package/dist/esm/index.d.ts.map +1 -1
  75. package/dist/esm/index.js +5 -4
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/math/fills.d.ts +4 -4
  78. package/dist/esm/math/fills.js +4 -4
  79. package/dist/esm/math/order.d.ts +24 -26
  80. package/dist/esm/math/order.d.ts.map +1 -1
  81. package/dist/esm/math/order.js +52 -41
  82. package/dist/esm/math/order.js.map +1 -1
  83. package/dist/esm/math/position.d.ts +27 -27
  84. package/dist/esm/math/position.d.ts.map +1 -1
  85. package/dist/esm/math/position.js +38 -36
  86. package/dist/esm/math/position.js.map +1 -1
  87. package/dist/esm/services/resolveSubscribeQuote.d.ts +1 -1
  88. package/dist/esm/services/resolveSubscribeQuote.js +1 -1
  89. package/dist/esm/types/provider.d.ts +4 -3
  90. package/dist/esm/types/provider.d.ts.map +1 -1
  91. package/dist/esm/utils/createSafeFunction.d.ts +1 -1
  92. package/dist/esm/utils/createSafeFunction.js +1 -1
  93. package/dist/esm/utils/warnOnce.d.ts +17 -6
  94. package/dist/esm/utils/warnOnce.d.ts.map +1 -1
  95. package/dist/esm/utils/warnOnce.js +22 -10
  96. package/dist/esm/utils/warnOnce.js.map +1 -1
  97. package/dist/esm/version.d.ts +1 -1
  98. package/dist/esm/version.js +1 -1
  99. package/dist/esm/websocket/wsLog.d.ts +4 -4
  100. package/dist/esm/websocket/wsLog.d.ts.map +1 -1
  101. package/dist/esm/websocket/wsLog.js +8 -7
  102. package/dist/esm/websocket/wsLog.js.map +1 -1
  103. package/dist/esm/wire/orderAmounts.d.ts +9 -7
  104. package/dist/esm/wire/orderAmounts.d.ts.map +1 -1
  105. package/dist/esm/wire/orderAmounts.js +22 -18
  106. package/dist/esm/wire/orderAmounts.js.map +1 -1
  107. package/dist/esm/wire/refuel.d.ts +1 -1
  108. package/dist/esm/wire/refuel.js +4 -4
  109. package/dist/esm/wire/refuel.js.map +1 -1
  110. package/dist/esm/wire/transferable.d.ts +2 -2
  111. package/dist/esm/wire/transferable.js +2 -2
  112. package/dist/types/decimal/arithmetic.d.ts +24 -10
  113. package/dist/types/decimal/arithmetic.d.ts.map +1 -1
  114. package/dist/types/decimal/compare.d.ts +3 -3
  115. package/dist/types/decimal/convert.d.ts +2 -24
  116. package/dist/types/decimal/convert.d.ts.map +1 -1
  117. package/dist/types/decimal/decimalStringToBig.d.ts +4 -5
  118. package/dist/types/decimal/decimalStringToBig.d.ts.map +1 -1
  119. package/dist/types/decimal/parse.d.ts +8 -1
  120. package/dist/types/decimal/parse.d.ts.map +1 -1
  121. package/dist/types/errors/invalidInput.d.ts +4 -0
  122. package/dist/types/errors/invalidInput.d.ts.map +1 -0
  123. package/dist/types/index.d.ts +5 -4
  124. package/dist/types/index.d.ts.map +1 -1
  125. package/dist/types/math/fills.d.ts +4 -4
  126. package/dist/types/math/order.d.ts +24 -26
  127. package/dist/types/math/order.d.ts.map +1 -1
  128. package/dist/types/math/position.d.ts +27 -27
  129. package/dist/types/math/position.d.ts.map +1 -1
  130. package/dist/types/services/resolveSubscribeQuote.d.ts +1 -1
  131. package/dist/types/types/provider.d.ts +4 -3
  132. package/dist/types/types/provider.d.ts.map +1 -1
  133. package/dist/types/utils/createSafeFunction.d.ts +1 -1
  134. package/dist/types/utils/warnOnce.d.ts +17 -6
  135. package/dist/types/utils/warnOnce.d.ts.map +1 -1
  136. package/dist/types/version.d.ts +1 -1
  137. package/dist/types/websocket/wsLog.d.ts +4 -4
  138. package/dist/types/websocket/wsLog.d.ts.map +1 -1
  139. package/dist/types/wire/orderAmounts.d.ts +9 -7
  140. package/dist/types/wire/orderAmounts.d.ts.map +1 -1
  141. package/dist/types/wire/refuel.d.ts +1 -1
  142. package/dist/types/wire/transferable.d.ts +2 -2
  143. package/package.json +2 -2
  144. package/src/decimal/arithmetic.ts +36 -10
  145. package/src/decimal/compare.ts +3 -3
  146. package/src/decimal/convert.ts +3 -40
  147. package/src/decimal/decimalStringToBig.ts +7 -12
  148. package/src/decimal/format.ts +3 -3
  149. package/src/decimal/parse.ts +12 -1
  150. package/src/errors/invalidInput.ts +10 -0
  151. package/src/index.ts +8 -3
  152. package/src/math/fills.ts +4 -4
  153. package/src/math/order.ts +56 -48
  154. package/src/math/position.ts +43 -42
  155. package/src/services/resolveSubscribeQuote.ts +1 -1
  156. package/src/types/provider.ts +4 -3
  157. package/src/utils/createSafeFunction.ts +1 -1
  158. package/src/utils/warnOnce.ts +28 -12
  159. package/src/version.ts +1 -1
  160. package/src/websocket/wsLog.ts +22 -9
  161. package/src/wire/orderAmounts.ts +42 -24
  162. package/src/wire/refuel.ts +4 -4
  163. package/src/wire/transferable.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/perps-sdk",
3
- "version": "24.0.0",
3
+ "version": "25.0.0",
4
4
  "description": "LI.FI Perps SDK for perpetuals trading",
5
5
  "homepage": "https://github.com/lifinance/perps-sdk",
6
6
  "bugs": {
@@ -46,7 +46,7 @@
46
46
  "big.js": "^7.0.1",
47
47
  "partysocket": "^1.1.19",
48
48
  "viem": "^2.55.18",
49
- "@lifi/perps-types": "^21.0.0"
49
+ "@lifi/perps-types": "^22.0.0"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"
@@ -6,10 +6,9 @@ import { DivBig, TruncBig } from './big.js'
6
6
  import { bigToDecimalString, decimalStringToBig } from './decimalStringToBig.js'
7
7
 
8
8
  /**
9
- * Exact `a + b`. Each input may carry `$`, `%`, `,` or whitespace:
10
- * `addDecimalString('$4', '$6')` is `'10'`.
9
+ * Exact `a + b`.
11
10
  *
12
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
11
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
13
12
  * @public
14
13
  */
15
14
  export function addDecimalString(a: string, b: string): string {
@@ -26,10 +25,37 @@ export const safeAddDecimalString = createSafeFunction(
26
25
  addDecimalString
27
26
  )
28
27
 
28
+ /**
29
+ * Exact sum of every value; `'0'` for an empty list.
30
+ *
31
+ * @throws {PerpsError} `ValidationError` when a value does not match the
32
+ * decimal pattern.
33
+ * @public
34
+ */
35
+ export function addDecimalStrings(values: readonly string[]): string {
36
+ return bigToDecimalString(
37
+ values.reduce<Big>(
38
+ (sum, value) => sum.plus(decimalStringToBig(value)),
39
+ decimalStringToBig('0')
40
+ )
41
+ )
42
+ }
43
+
44
+ /**
45
+ * {@link addDecimalStrings}, or `undefined` with a warning when it throws.
46
+ * One bad value gives `undefined`, never a partial sum.
47
+ *
48
+ * @public
49
+ */
50
+ export const safeAddDecimalStrings = createSafeFunction(
51
+ 'addDecimalStrings',
52
+ addDecimalStrings
53
+ )
54
+
29
55
  /**
30
56
  * Exact `a - b`.
31
57
  *
32
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
58
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
33
59
  * @public
34
60
  */
35
61
  export function subtractDecimalString(a: string, b: string): string {
@@ -49,7 +75,7 @@ export const safeSubtractDecimalString = createSafeFunction(
49
75
  /**
50
76
  * Exact `a × b`.
51
77
  *
52
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
78
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
53
79
  * @public
54
80
  */
55
81
  export function multiplyDecimalString(a: string, b: string): string {
@@ -80,8 +106,8 @@ function requireNonZeroDivisor(divisor: string): Big {
80
106
  /**
81
107
  * `a ÷ b` to 40 decimal places, rounded half up.
82
108
  *
83
- * @throws {PerpsError} `ValidationError` when an input is not a decimal
84
- * string or `b` is zero.
109
+ * @throws {PerpsError} `ValidationError` when an input does not match the
110
+ * decimal pattern or `b` is zero.
85
111
  * @public
86
112
  */
87
113
  export function divideDecimalString(a: string, b: string): string {
@@ -103,8 +129,8 @@ export const safeDivideDecimalString = createSafeFunction(
103
129
  * `a ÷ b` to 40 decimal places, truncated toward zero, so a derived amount
104
130
  * never exceeds the exact quotient.
105
131
  *
106
- * @throws {PerpsError} `ValidationError` when an input is not a decimal
107
- * string or `b` is zero.
132
+ * @throws {PerpsError} `ValidationError` when an input does not match the
133
+ * decimal pattern or `b` is zero.
108
134
  * @public
109
135
  */
110
136
  export function divideDecimalStringRoundDown(a: string, b: string): string {
@@ -126,7 +152,7 @@ export const safeDivideDecimalStringRoundDown = createSafeFunction(
126
152
  /**
127
153
  * Exact `|value|`.
128
154
  *
129
- * @throws {PerpsError} `ValidationError` when `value` is not a decimal string.
155
+ * @throws {PerpsError} `ValidationError` when `value` does not match the decimal pattern.
130
156
  * @public
131
157
  */
132
158
  export function absDecimalString(value: string): string {
@@ -6,7 +6,7 @@ import { decimalStringToBig } from './decimalStringToBig.js'
6
6
  * gate can tell `'1.0000000000000001'` from `'1'`. The other relations follow
7
7
  * from it: `a <= b` is `!isDecimalStringGreaterThan(a, b)`.
8
8
  *
9
- * @throws {PerpsError} `ValidationError` when either operand is not a decimal string.
9
+ * @throws {PerpsError} `ValidationError` when either operand does not match the decimal pattern.
10
10
  * @public
11
11
  */
12
12
  export function isDecimalStringGreaterThan(a: string, b: string): boolean {
@@ -28,7 +28,7 @@ export const safeIsDecimalStringGreaterThan = createSafeFunction(
28
28
  * Exact `value == 0`, so a flat-position check never reads `'0.0'` as
29
29
  * non-zero.
30
30
  *
31
- * @throws {PerpsError} `ValidationError` when `value` is not a decimal string.
31
+ * @throws {PerpsError} `ValidationError` when `value` does not match the decimal pattern.
32
32
  * @public
33
33
  */
34
34
  export function isDecimalStringZero(value: string): boolean {
@@ -49,7 +49,7 @@ export const safeIsDecimalStringZero = createSafeFunction(
49
49
  * Exact three-way compare for a sort: `-1` when `a < b`, `0` when equal, `1`
50
50
  * when `a > b`.
51
51
  *
52
- * @throws {PerpsError} `ValidationError` when either operand is not a decimal string.
52
+ * @throws {PerpsError} `ValidationError` when either operand does not match the decimal pattern.
53
53
  * @public
54
54
  */
55
55
  export function compareDecimalStrings(a: string, b: string): -1 | 0 | 1 {
@@ -27,7 +27,7 @@ const BIG_ROUNDING = {
27
27
  * The result has no trailing zeros: `roundDecimalString('1.50', 1, 'round')`
28
28
  * is `'1.5'`.
29
29
  *
30
- * @throws {PerpsError} `ValidationError` when `value` is not a decimal string,
30
+ * @throws {PerpsError} `ValidationError` when `value` does not match the decimal pattern,
31
31
  * or `decimals` is not a non-negative integer.
32
32
  * @public
33
33
  */
@@ -62,7 +62,7 @@ export const safeRoundDecimalString = createSafeFunction(
62
62
  * arithmetic: `'0.29'` at 2 decimals is 29, never 28. Off-grid input resolves
63
63
  * per `rounding`. No `$`, `%` or `,` is removed first.
64
64
  *
65
- * @throws {PerpsError} `ValidationError` when `value` is not a decimal string,
65
+ * @throws {PerpsError} `ValidationError` when `value` does not match the decimal pattern,
66
66
  * `decimals` is not a non-negative integer, or the result's magnitude
67
67
  * exceeds `Number.MAX_SAFE_INTEGER`.
68
68
  * @public
@@ -75,7 +75,7 @@ export function decimalStringToScaledInteger(
75
75
  if (!isDecimalString(value)) {
76
76
  throw new PerpsError(
77
77
  PerpsErrorCode.ValidationError,
78
- `'${value}' is not a decimal string.`
78
+ `'${value}' does not match the decimal pattern.`
79
79
  )
80
80
  }
81
81
  if (!Number.isInteger(decimals) || decimals < 0) {
@@ -147,43 +147,6 @@ export const safeScaledIntegerToDecimalString = createSafeFunction(
147
147
  scaledIntegerToDecimalString
148
148
  )
149
149
 
150
- /**
151
- * Round `value` down to `decimals` and pad the result to exactly that many
152
- * decimal places. The padding is the point: this seeds a fixed-decimal input,
153
- * where `'500.00'` and `'500'` are two renderings of one amount and the field
154
- * wants the first. For a canonical wire amount with no trailing zeros, read
155
- * the amount off `calculateOrderAmounts` or a provider snap instead.
156
- *
157
- * Exact decimal arithmetic throughout, so there is no 2^53 ceiling and no
158
- * float artifact: `'0.01'` at 18 decimals gives
159
- * `'0.010000000000000000'`, which `Number#toFixed` cannot.
160
- *
161
- * @throws {PerpsError} `ValidationError` when `value` is not a
162
- * decimal string, or `decimals` is not a non-negative integer.
163
- * @public
164
- */
165
- export function truncateDecimal(value: string, decimals: number): string {
166
- if (!Number.isInteger(decimals) || decimals < 0) {
167
- throw new PerpsError(
168
- PerpsErrorCode.ValidationError,
169
- `Invalid decimals for truncation: ${decimals}`
170
- )
171
- }
172
- const truncated = decimalStringToBig(value).round(decimals, Big.roundDown)
173
- // big.js carries the sign through a round to zero; '-0.00' is not a spelling.
174
- return (truncated.eq(0) ? new Big(0) : truncated).toFixed(decimals)
175
- }
176
-
177
- /**
178
- * {@link truncateDecimal}, or `undefined` with a warning when it throws.
179
- *
180
- * @public
181
- */
182
- export const safeTruncateDecimal = createSafeFunction(
183
- 'truncateDecimal',
184
- truncateDecimal
185
- )
186
-
187
150
  /**
188
151
  * Spell a `number` as a {@link DecimalString} in plain notation: `1e-7`
189
152
  * becomes `'0.0000001'`, never `'1e-7'`. The boundary helper for a venue or
@@ -3,27 +3,22 @@ import Big from 'big.js'
3
3
  import { PerpsError } from '../errors/PerpsError.js'
4
4
  import { DivBig } from './big.js'
5
5
 
6
- const DISPLAY_CHARACTERS = /[\s$%,]/g
7
-
8
6
  /**
9
- * Read a decimal string as a `Big`. Whitespace, `$`, `%` and `,` are removed
10
- * first, so `'$1,234.5'` reads as `1234.5`; the rest must match
11
- * `DECIMAL_PATTERN`, so an exponent form such as `'1e5'` is rejected.
7
+ * Read a decimal string as a `Big`. The string must match `DECIMAL_PATTERN`,
8
+ * so `'$1,234.5'`, `'0.05%'` and the exponent form `'1e5'` are rejected.
12
9
  *
13
- * @throws {PerpsError} `ValidationError` when `value` is not a string or the
14
- * cleaned string is not a decimal string.
10
+ * @throws {PerpsError} `ValidationError` when `value` does not match the
11
+ * decimal pattern.
15
12
  * @internal
16
13
  */
17
14
  export function decimalStringToBig(value: string): Big {
18
- const cleaned =
19
- typeof value === 'string' ? value.replace(DISPLAY_CHARACTERS, '') : ''
20
- if (!DECIMAL_PATTERN.test(cleaned)) {
15
+ if (typeof value !== 'string' || !DECIMAL_PATTERN.test(value)) {
21
16
  throw new PerpsError(
22
17
  PerpsErrorCode.ValidationError,
23
- `'${String(value)}' is not a decimal string.`
18
+ `'${String(value)}' does not match the decimal pattern.`
24
19
  )
25
20
  }
26
- return new Big(cleaned)
21
+ return new Big(value)
27
22
  }
28
23
 
29
24
  /**
@@ -38,9 +38,9 @@ const DEFAULT_PLACEHOLDER = '—'
38
38
  type FormatInput = number | DecimalString | string | null | undefined
39
39
 
40
40
  /**
41
- * Read a display input as a `Big`: `$`, `%`, `,` and whitespace are removed
42
- * from a string first. `null` means the formatter renders the placeholder;
43
- * a string it cannot read is returned unchanged for display.
41
+ * Read a display input as a `Big`. `null` means the formatter renders the
42
+ * placeholder; a string that does not match the decimal pattern is returned
43
+ * unchanged for display.
44
44
  */
45
45
  function toBig(value: FormatInput): Big | null | string {
46
46
  if (typeof value === 'number') {
@@ -9,7 +9,7 @@ import { decimalStringToBig } from './decimalStringToBig.js'
9
9
  * float holds about 15 significant digits, so the result can lose precision;
10
10
  * never use it for an amount sent to a venue.
11
11
  *
12
- * @throws {PerpsError} `ValidationError` when `value` is not a decimal string
12
+ * @throws {PerpsError} `ValidationError` when `value` does not match the decimal pattern
13
13
  * or is too large for a finite float.
14
14
  * @public
15
15
  */
@@ -95,3 +95,14 @@ export function unknownToDecimalString(
95
95
  error.tool = tool
96
96
  throw error
97
97
  }
98
+
99
+ /**
100
+ * The safe pair of {@link unknownToDecimalString}: `undefined` and a warning
101
+ * in place of a throw.
102
+ *
103
+ * @public
104
+ */
105
+ export const safeUnknownToDecimalString = createSafeFunction(
106
+ 'unknownToDecimalString',
107
+ unknownToDecimalString
108
+ )
@@ -0,0 +1,10 @@
1
+ import { PerpsErrorCode } from '@lifi/perps-types'
2
+ import { PerpsError } from './PerpsError.js'
3
+
4
+ /** A `ValidationError` that names the input parameter and the rule it breaks. */
5
+ export function invalidInput(parameter: string, rule: string): PerpsError {
6
+ return new PerpsError(
7
+ PerpsErrorCode.ValidationError,
8
+ `\`${parameter}\` ${rule}.`
9
+ )
10
+ }
package/src/index.ts CHANGED
@@ -20,11 +20,13 @@ export { requireProvider } from './client/requireProvider.js'
20
20
  export {
21
21
  absDecimalString,
22
22
  addDecimalString,
23
+ addDecimalStrings,
23
24
  divideDecimalString,
24
25
  divideDecimalStringRoundDown,
25
26
  multiplyDecimalString,
26
27
  safeAbsDecimalString,
27
28
  safeAddDecimalString,
29
+ safeAddDecimalStrings,
28
30
  safeDivideDecimalString,
29
31
  safeDivideDecimalStringRoundDown,
30
32
  safeMultiplyDecimalString,
@@ -48,9 +50,7 @@ export {
48
50
  safeNumberToDecimalString,
49
51
  safeRoundDecimalString,
50
52
  safeScaledIntegerToDecimalString,
51
- safeTruncateDecimal,
52
53
  scaledIntegerToDecimalString,
53
- truncateDecimal,
54
54
  } from './decimal/convert.js'
55
55
  export type { FormatOptions, RoundingMode } from './decimal/format.js'
56
56
  export {
@@ -65,6 +65,7 @@ export {
65
65
  decimalStringToNumber,
66
66
  isDecimalString,
67
67
  safeDecimalStringToNumber,
68
+ safeUnknownToDecimalString,
68
69
  unknownToDecimalString,
69
70
  } from './decimal/parse.js'
70
71
  export { PerpsErrorMessage } from './errors/constants.js'
@@ -293,6 +294,7 @@ export type {
293
294
  WithdrawFlowSetupRequired,
294
295
  } from './types/withdrawal.js'
295
296
  export { paginateActivity } from './utils/activityPaging.js'
297
+ export { createSafeFunction } from './utils/createSafeFunction.js'
296
298
  export {
297
299
  ETHEREUM_NATIVE_GAS,
298
300
  ETHEREUM_USDC,
@@ -359,7 +361,10 @@ export type {
359
361
  OrderAmounts,
360
362
  OrderAmountsInput,
361
363
  } from './wire/orderAmounts.js'
362
- export { calculateOrderAmounts } from './wire/orderAmounts.js'
364
+ export {
365
+ calculateOrderAmounts,
366
+ safeCalculateOrderAmounts,
367
+ } from './wire/orderAmounts.js'
363
368
  export type { RefuelAmountInput } from './wire/refuel.js'
364
369
  export {
365
370
  calculateRefuelAmount,
package/src/math/fills.ts CHANGED
@@ -21,8 +21,8 @@ export { FillClassification }
21
21
  * @param startPosition Signed position held BEFORE this fill (`> 0` long,
22
22
  * `< 0` short, `0` flat).
23
23
  * @param sz Unsigned fill size.
24
- * @returns `BUY` or `SELL` when `startPosition` or `sz` is not a decimal
25
- * string, so one malformed venue fill still maps.
24
+ * @returns `BUY` or `SELL` when `startPosition` or `sz` does not match the
25
+ * decimal pattern, so one malformed venue fill still maps.
26
26
  * @public
27
27
  */
28
28
  export function classifyFillFromPosition(
@@ -74,8 +74,8 @@ export function classifyFillFromPosition(
74
74
  * Classify a fill as open or close based on realizedPnl.
75
75
  * @deprecated Use `Fill.classification` instead — it uses startPosition
76
76
  * for accurate open/increase/reduce/close/reverse classification.
77
- * @returns `BUY` or `SELL` when `realizedPnl` is a string that is not a
78
- * decimal string.
77
+ * @returns `BUY` or `SELL` when `realizedPnl` is a string that does not match the
78
+ * decimal pattern.
79
79
  * @public
80
80
  */
81
81
  export function classifyFill(
package/src/math/order.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Display-tier order formulas. Every function takes and gives decimal
3
3
  * strings; arithmetic is exact Big.js, with division to 40 decimal places.
4
- * Each formula throws `ValidationError` on an input that is not a decimal
5
- * string and has a `safe*` pair that gives `undefined` in place of the throw.
4
+ * Each formula throws `ValidationError` on an input that does not match the
5
+ * decimal pattern and has a `safe*` pair that gives `undefined` in place of the throw.
6
6
  */
7
7
 
8
8
  import {
@@ -28,6 +28,7 @@ import {
28
28
  decimalStringToBig,
29
29
  decimalStringToDivBig,
30
30
  } from '../decimal/decimalStringToBig.js'
31
+ import { invalidInput } from '../errors/invalidInput.js'
31
32
  import { PerpsError } from '../errors/PerpsError.js'
32
33
  import { createSafeFunction } from '../utils/createSafeFunction.js'
33
34
  import {
@@ -44,7 +45,7 @@ import { calculateRealizedPnl } from './position.js'
44
45
  * ```ts
45
46
  * calculateSize('100', '10', '2000') // '0.5' (ETH at $2000)
46
47
  * ```
47
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string
48
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern
48
49
  * or `price` is zero.
49
50
  * @public
50
51
  */
@@ -58,10 +59,7 @@ export function calculateSize(
58
59
  )
59
60
  const priceBig = decimalStringToBig(price)
60
61
  if (priceBig.eq(0)) {
61
- throw new PerpsError(
62
- PerpsErrorCode.ValidationError,
63
- 'Price must not be zero.'
64
- )
62
+ throw invalidInput('price', 'must not be zero')
65
63
  }
66
64
  return bigToDecimalString(notional.div(priceBig))
67
65
  }
@@ -76,7 +74,7 @@ export const safeCalculateSize = createSafeFunction(
76
74
  * Estimated trading fee in USD: `sizeUsd × feeRate`.
77
75
  *
78
76
  * @param feeRate - Fee rate as a fraction (`'0.00035'` for 0.035%).
79
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
77
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
80
78
  * @public
81
79
  */
82
80
  export function estimateFees(sizeUsd: string, feeRate: string): string {
@@ -94,8 +92,8 @@ export const safeEstimateFees = createSafeFunction('estimateFees', estimateFees)
94
92
  * is not rounded; snap it to the market tick before it goes to a venue.
95
93
  *
96
94
  * @param slippagePercent - Slippage tolerance as a percentage (`'0.5'` is 0.5%).
97
- * @throws {PerpsError} `ValidationError` when an input is not a decimal
98
- * string, or `slippagePercent` is -100 or less.
95
+ * @throws {PerpsError} `ValidationError` when an input does not match the
96
+ * decimal pattern, or `slippagePercent` is -100 or less.
99
97
  * @public
100
98
  */
101
99
  export function applySlippageToPrice(
@@ -141,9 +139,8 @@ export interface ExpectedPnl {
141
139
  *
142
140
  * @param entryPrice - Position entry or current market price.
143
141
  * @param margin - Margin amount in USD.
144
- * @returns The expected PnL, or `null` when `triggerPrice`, `entryPrice` or
145
- * `margin` is zero.
146
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
142
+ * @throws {PerpsError} `ValidationError` when an input does not match the
143
+ * decimal pattern, or `triggerPrice`, `entryPrice` or `margin` is zero.
147
144
  * @public
148
145
  */
149
146
  export function calculateExpectedPnl(
@@ -152,13 +149,19 @@ export function calculateExpectedPnl(
152
149
  leverage: string,
153
150
  isLong: boolean,
154
151
  margin: string
155
- ): ExpectedPnl | null {
152
+ ): ExpectedPnl {
156
153
  const trigger = decimalStringToDivBig(triggerPrice)
157
154
  const entry = decimalStringToBig(entryPrice)
158
155
  const leverageBig = decimalStringToBig(leverage)
159
156
  const marginBig = decimalStringToBig(margin)
160
- if (trigger.eq(0) || entry.eq(0) || marginBig.eq(0)) {
161
- return null
157
+ if (trigger.eq(0)) {
158
+ throw invalidInput('triggerPrice', 'must not be zero')
159
+ }
160
+ if (entry.eq(0)) {
161
+ throw invalidInput('entryPrice', 'must not be zero')
162
+ }
163
+ if (marginBig.eq(0)) {
164
+ throw invalidInput('margin', 'must not be zero')
162
165
  }
163
166
  const priceDiff = isLong
164
167
  ? trigger.minus(entry)
@@ -178,12 +181,11 @@ export const safeCalculateExpectedPnl = createSafeFunction(
178
181
  )
179
182
 
180
183
  /**
181
- * Trigger price that realises a percentage gain or loss. A zero `entryPrice`
182
- * or `leverage` gives `'0'`.
184
+ * Trigger price that realises a percentage gain or loss.
183
185
  *
184
186
  * @param percent - Target gain or loss percentage (positive = profitable
185
187
  * direction).
186
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
188
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
187
189
  * @public
188
190
  */
189
191
  export function calculateTriggerPrice(
@@ -195,8 +197,11 @@ export function calculateTriggerPrice(
195
197
  const percentBig = decimalStringToDivBig(percent)
196
198
  const entry = decimalStringToBig(entryPrice)
197
199
  const leverageBig = decimalStringToBig(leverage)
198
- if (entry.eq(0) || leverageBig.eq(0)) {
199
- return '0'
200
+ if (entry.eq(0)) {
201
+ throw invalidInput('entryPrice', 'must not be zero')
202
+ }
203
+ if (leverageBig.eq(0)) {
204
+ throw invalidInput('leverage', 'must not be zero')
200
205
  }
201
206
  const priceDelta = percentBig.times(entry).div(leverageBig.times(100))
202
207
  return bigToDecimalString(
@@ -211,10 +216,9 @@ export const safeCalculateTriggerPrice = createSafeFunction(
211
216
  )
212
217
 
213
218
  /**
214
- * Percentage gain or loss that a trigger price realises. A zero `entryPrice`
215
- * or `leverage` gives `'0'`.
219
+ * Percentage gain or loss that a trigger price realises.
216
220
  *
217
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
221
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
218
222
  * @public
219
223
  */
220
224
  export function calculateTriggerPercent(
@@ -226,8 +230,11 @@ export function calculateTriggerPercent(
226
230
  const priceBig = decimalStringToDivBig(price)
227
231
  const entry = decimalStringToBig(entryPrice)
228
232
  const leverageBig = decimalStringToBig(leverage)
229
- if (entry.eq(0) || leverageBig.eq(0)) {
230
- return '0'
233
+ if (entry.eq(0)) {
234
+ throw invalidInput('entryPrice', 'must not be zero')
235
+ }
236
+ if (leverageBig.eq(0)) {
237
+ throw invalidInput('leverage', 'must not be zero')
231
238
  }
232
239
  const priceDiff = isLong
233
240
  ? priceBig.minus(entry)
@@ -243,9 +250,10 @@ export const safeCalculateTriggerPercent = createSafeFunction(
243
250
 
244
251
  /**
245
252
  * Realized PnL as a percentage of the position value at close:
246
- * `realizedPnl ÷ (|size| × price) × 100`. A zero position value gives `'0'`.
253
+ * `realizedPnl ÷ (|size| × price) × 100`.
247
254
  *
248
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
255
+ * @throws {PerpsError} `ValidationError` when an input does not match the
256
+ * decimal pattern or `size × price` is zero.
249
257
  * @public
250
258
  */
251
259
  export function calculateRealizedPnlPercent(
@@ -258,7 +266,7 @@ export function calculateRealizedPnlPercent(
258
266
  .abs()
259
267
  .times(decimalStringToBig(price))
260
268
  if (positionValue.eq(0)) {
261
- return '0'
269
+ throw invalidInput('size × price', 'must not be zero')
262
270
  }
263
271
  return bigToDecimalString(pnl.div(positionValue).times(100))
264
272
  }
@@ -290,7 +298,7 @@ interface BookWalk {
290
298
  * returning the best obtainable fill.
291
299
  *
292
300
  * @throws {PerpsError} `ValidationError` when `sizeUsd` or a level's `price`
293
- * or `size` is not a decimal string.
301
+ * or `size` does not match the decimal pattern.
294
302
  * @public
295
303
  */
296
304
  export function walkOrderbook(
@@ -354,7 +362,7 @@ interface BuildQuoteInput {
354
362
  * the market's `funding` (`null` for spot, which has none).
355
363
  *
356
364
  * @throws {PerpsError} `ValidationError` when `sizeUsd`, the mark price, the
357
- * taker fee or a book level is not a decimal string.
365
+ * taker fee or a book level does not match the decimal pattern.
358
366
  * @public
359
367
  */
360
368
  export function buildQuote(input: BuildQuoteInput): Quote {
@@ -415,7 +423,7 @@ export function findMatchingPosition(
415
423
  *
416
424
  * Inputs are non-negative magnitudes.
417
425
  *
418
- * @throws {PerpsError} `ValidationError` when an input is not a decimal string.
426
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
419
427
  * @public
420
428
  */
421
429
  export function resolveCloseSize(
@@ -440,7 +448,7 @@ export const safeResolveCloseSize = createSafeFunction(
440
448
  * Expected rPnL for a resting limit order against a matching position.
441
449
  *
442
450
  * Reducing requires opposite sides (long position + SELL, short position +
443
- * BUY). Same-side orders add to the position and have no rPnL → `null`.
451
+ * BUY). Same-side orders add to the position and have no rPnL → `undefined`.
444
452
  * Projects `remainingSize`, because an already-filled quantity has realised
445
453
  * its PnL at the fill price rather than at this order's limit price. A
446
454
  * `remainingSize` of zero leaves nothing to project.
@@ -448,9 +456,9 @@ export const safeResolveCloseSize = createSafeFunction(
448
456
  function regularOrderRealizedPnl(
449
457
  order: RegularOrder,
450
458
  position: Position | undefined
451
- ): string | null {
459
+ ): string | undefined {
452
460
  if (!position || !isActiveOrderStatus(order.status)) {
453
- return null
461
+ return undefined
454
462
  }
455
463
 
456
464
  const isLong = position.side === PositionSide.LONG
@@ -458,23 +466,23 @@ function regularOrderRealizedPnl(
458
466
  (isLong && order.side === OrderSide.SELL) ||
459
467
  (!isLong && order.side === OrderSide.BUY)
460
468
  if (!reducesPosition) {
461
- return null
469
+ return undefined
462
470
  }
463
471
 
464
472
  if (order.price === undefined) {
465
- return null
473
+ return undefined
466
474
  }
467
475
  const orderSize = decimalStringToBig(order.remainingSize).abs()
468
476
  const positionSize = decimalStringToBig(position.size).abs()
469
477
  if (positionSize.lte(0)) {
470
- return null
478
+ return undefined
471
479
  }
472
480
 
473
481
  // `resolveCloseSize` reads a zero size as "close the whole position", a
474
482
  // convention that belongs to an order's submitted size. `remainingSize` is
475
483
  // the unfilled quantity, so zero means nothing is left to fill.
476
484
  if (orderSize.eq(0)) {
477
- return null
485
+ return undefined
478
486
  }
479
487
 
480
488
  return calculateRealizedPnl({
@@ -492,14 +500,14 @@ function regularOrderRealizedPnl(
492
500
  function triggerOrderRealizedPnl(
493
501
  order: TriggerOrder,
494
502
  position: Position | undefined
495
- ): string | null {
503
+ ): string | undefined {
496
504
  if (
497
505
  !position ||
498
506
  order.triggerPrice === undefined ||
499
507
  !isActiveOrderStatus(order.status) ||
500
508
  order.status === OrderStatus.PENDING
501
509
  ) {
502
- return null
510
+ return undefined
503
511
  }
504
512
 
505
513
  const isLong = position.side === PositionSide.LONG
@@ -507,18 +515,18 @@ function triggerOrderRealizedPnl(
507
515
  (isLong && order.side !== OrderSide.SELL) ||
508
516
  (!isLong && order.side !== OrderSide.BUY)
509
517
  ) {
510
- return null
518
+ return undefined
511
519
  }
512
520
  const orderSize = decimalStringToBig(order.remainingSize).abs()
513
521
  const positionSize = decimalStringToBig(position.size).abs()
514
522
  if (positionSize.lte(0)) {
515
- return null
523
+ return undefined
516
524
  }
517
525
  if (
518
526
  orderSize.eq(0) &&
519
527
  (!decimalStringToBig(order.originalSize).eq(0) || !order.reduceOnly)
520
528
  ) {
521
- return null
529
+ return undefined
522
530
  }
523
531
 
524
532
  return calculateRealizedPnl({
@@ -539,22 +547,22 @@ function triggerOrderRealizedPnl(
539
547
  * nothing.
540
548
  *
541
549
  * @returns Realised PnL if the order would reduce the position, otherwise
542
- * `null`.
550
+ * `undefined`.
543
551
  * @throws {PerpsError} `ValidationError` when a price or size on the order or
544
- * the position is not a decimal string.
552
+ * the position does not match the decimal pattern.
545
553
  * @public
546
554
  */
547
555
  export function estimateRealizedPnl(
548
556
  order: Order,
549
557
  position: Position | undefined
550
- ): string | null {
558
+ ): string | undefined {
551
559
  if (isTriggerOrder(order)) {
552
560
  return triggerOrderRealizedPnl(order, position)
553
561
  }
554
562
  if (isRegularOrder(order)) {
555
563
  return regularOrderRealizedPnl(order, position)
556
564
  }
557
- return null
565
+ return undefined
558
566
  }
559
567
 
560
568
  /** @public */