@lifi/perps-sdk 23.1.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 (305) hide show
  1. package/README.md +102 -56
  2. package/dist/cjs/client/PerpsClient.d.ts +3 -2
  3. package/dist/cjs/client/PerpsClient.d.ts.map +1 -1
  4. package/dist/cjs/client/PerpsClient.js +16 -9
  5. package/dist/cjs/client/PerpsClient.js.map +1 -1
  6. package/dist/cjs/decimal/arithmetic.d.ts +15 -0
  7. package/dist/cjs/decimal/arithmetic.d.ts.map +1 -0
  8. package/dist/cjs/decimal/arithmetic.js +53 -0
  9. package/dist/cjs/decimal/arithmetic.js.map +1 -0
  10. package/dist/cjs/decimal/big.d.ts +0 -1
  11. package/dist/cjs/decimal/big.d.ts.map +1 -1
  12. package/dist/cjs/decimal/big.js +0 -4
  13. package/dist/cjs/decimal/big.js.map +1 -1
  14. package/dist/cjs/decimal/compare.d.ts +6 -2
  15. package/dist/cjs/decimal/compare.d.ts.map +1 -1
  16. package/dist/cjs/decimal/compare.js +15 -2
  17. package/dist/cjs/decimal/compare.js.map +1 -1
  18. package/dist/cjs/decimal/convert.d.ts +10 -6
  19. package/dist/cjs/decimal/convert.d.ts.map +1 -1
  20. package/dist/cjs/decimal/convert.js +34 -21
  21. package/dist/cjs/decimal/convert.js.map +1 -1
  22. package/dist/cjs/decimal/decimalStringToBig.d.ts +5 -0
  23. package/dist/cjs/decimal/decimalStringToBig.d.ts.map +1 -0
  24. package/dist/cjs/decimal/decimalStringToBig.js +25 -0
  25. package/dist/cjs/decimal/decimalStringToBig.js.map +1 -0
  26. package/dist/cjs/decimal/format.d.ts.map +1 -1
  27. package/dist/cjs/decimal/format.js +70 -52
  28. package/dist/cjs/decimal/format.js.map +1 -1
  29. package/dist/cjs/decimal/parse.d.ts +5 -4
  30. package/dist/cjs/decimal/parse.d.ts.map +1 -1
  31. package/dist/cjs/decimal/parse.js +43 -25
  32. package/dist/cjs/decimal/parse.js.map +1 -1
  33. package/dist/cjs/errors/invalidInput.d.ts +3 -0
  34. package/dist/cjs/errors/invalidInput.d.ts.map +1 -0
  35. package/dist/cjs/errors/invalidInput.js +9 -0
  36. package/dist/cjs/errors/invalidInput.js.map +1 -0
  37. package/dist/cjs/index.d.ts +15 -12
  38. package/dist/cjs/index.d.ts.map +1 -1
  39. package/dist/cjs/index.js +69 -9
  40. package/dist/cjs/index.js.map +1 -1
  41. package/dist/cjs/math/fills.d.ts +1 -1
  42. package/dist/cjs/math/fills.d.ts.map +1 -1
  43. package/dist/cjs/math/fills.js +28 -13
  44. package/dist/cjs/math/fills.js.map +1 -1
  45. package/dist/cjs/math/order.d.ts +27 -16
  46. package/dist/cjs/math/order.d.ts.map +1 -1
  47. package/dist/cjs/math/order.js +131 -134
  48. package/dist/cjs/math/order.js.map +1 -1
  49. package/dist/cjs/math/position.d.ts +85 -35
  50. package/dist/cjs/math/position.d.ts.map +1 -1
  51. package/dist/cjs/math/position.js +93 -94
  52. package/dist/cjs/math/position.js.map +1 -1
  53. package/dist/cjs/services/getGasRecommendation.js +1 -1
  54. package/dist/cjs/services/getGasRecommendation.js.map +1 -1
  55. package/dist/cjs/services/getQuote.d.ts +1 -1
  56. package/dist/cjs/services/resolveSubscribeQuote.d.ts.map +1 -1
  57. package/dist/cjs/services/resolveSubscribeQuote.js +3 -2
  58. package/dist/cjs/services/resolveSubscribeQuote.js.map +1 -1
  59. package/dist/cjs/transport/request.d.ts +1 -0
  60. package/dist/cjs/transport/request.d.ts.map +1 -1
  61. package/dist/cjs/transport/request.js +2 -2
  62. package/dist/cjs/transport/request.js.map +1 -1
  63. package/dist/cjs/types/api.d.ts +4 -0
  64. package/dist/cjs/types/api.d.ts.map +1 -1
  65. package/dist/cjs/types/provider.d.ts +9 -5
  66. package/dist/cjs/types/provider.d.ts.map +1 -1
  67. package/dist/cjs/types/withdrawal.d.ts +12 -4
  68. package/dist/cjs/types/withdrawal.d.ts.map +1 -1
  69. package/dist/cjs/utils/createSafeFunction.d.ts +2 -0
  70. package/dist/cjs/utils/createSafeFunction.d.ts.map +1 -0
  71. package/dist/cjs/utils/createSafeFunction.js +15 -0
  72. package/dist/cjs/utils/createSafeFunction.js.map +1 -0
  73. package/dist/cjs/utils/timestampToIsoString.d.ts +3 -0
  74. package/dist/cjs/utils/timestampToIsoString.d.ts.map +1 -0
  75. package/dist/cjs/utils/timestampToIsoString.js +16 -0
  76. package/dist/cjs/utils/timestampToIsoString.js.map +1 -0
  77. package/dist/cjs/utils/warnOnce.d.ts +6 -0
  78. package/dist/cjs/utils/warnOnce.d.ts.map +1 -1
  79. package/dist/cjs/utils/warnOnce.js +19 -1
  80. package/dist/cjs/utils/warnOnce.js.map +1 -1
  81. package/dist/cjs/version.d.ts +1 -1
  82. package/dist/cjs/version.js +1 -1
  83. package/dist/cjs/websocket/PerpsWsClient.d.ts +2 -0
  84. package/dist/cjs/websocket/PerpsWsClient.d.ts.map +1 -1
  85. package/dist/cjs/websocket/PerpsWsClient.js +14 -1
  86. package/dist/cjs/websocket/PerpsWsClient.js.map +1 -1
  87. package/dist/cjs/websocket/wsLog.d.ts +2 -0
  88. package/dist/cjs/websocket/wsLog.d.ts.map +1 -1
  89. package/dist/cjs/websocket/wsLog.js +9 -0
  90. package/dist/cjs/websocket/wsLog.js.map +1 -1
  91. package/dist/cjs/wire/orderAmounts.d.ts +3 -2
  92. package/dist/cjs/wire/orderAmounts.d.ts.map +1 -1
  93. package/dist/cjs/wire/orderAmounts.js +22 -21
  94. package/dist/cjs/wire/orderAmounts.js.map +1 -1
  95. package/dist/cjs/wire/refuel.d.ts +4 -1
  96. package/dist/cjs/wire/refuel.d.ts.map +1 -1
  97. package/dist/cjs/wire/refuel.js +23 -6
  98. package/dist/cjs/wire/refuel.js.map +1 -1
  99. package/dist/cjs/wire/transferable.js +3 -3
  100. package/dist/cjs/wire/transferable.js.map +1 -1
  101. package/dist/cjs/wire/withdraw.d.ts.map +1 -1
  102. package/dist/cjs/wire/withdraw.js +3 -3
  103. package/dist/cjs/wire/withdraw.js.map +1 -1
  104. package/dist/esm/client/PerpsClient.d.ts +17 -4
  105. package/dist/esm/client/PerpsClient.d.ts.map +1 -1
  106. package/dist/esm/client/PerpsClient.js +30 -8
  107. package/dist/esm/client/PerpsClient.js.map +1 -1
  108. package/dist/esm/decimal/arithmetic.d.ts +98 -0
  109. package/dist/esm/decimal/arithmetic.d.ts.map +1 -0
  110. package/dist/esm/decimal/arithmetic.js +126 -0
  111. package/dist/esm/decimal/arithmetic.js.map +1 -0
  112. package/dist/esm/decimal/big.d.ts +0 -2
  113. package/dist/esm/decimal/big.d.ts.map +1 -1
  114. package/dist/esm/decimal/big.js +0 -4
  115. package/dist/esm/decimal/big.js.map +1 -1
  116. package/dist/esm/decimal/compare.d.ts +40 -6
  117. package/dist/esm/decimal/compare.d.ts.map +1 -1
  118. package/dist/esm/decimal/compare.js +46 -6
  119. package/dist/esm/decimal/compare.js.map +1 -1
  120. package/dist/esm/decimal/convert.d.ts +50 -36
  121. package/dist/esm/decimal/convert.d.ts.map +1 -1
  122. package/dist/esm/decimal/convert.js +64 -42
  123. package/dist/esm/decimal/convert.js.map +1 -1
  124. package/dist/esm/decimal/decimalStringToBig.d.ts +25 -0
  125. package/dist/esm/decimal/decimalStringToBig.d.ts.map +1 -0
  126. package/dist/esm/decimal/decimalStringToBig.js +37 -0
  127. package/dist/esm/decimal/decimalStringToBig.js.map +1 -0
  128. package/dist/esm/decimal/format.d.ts +4 -1
  129. package/dist/esm/decimal/format.d.ts.map +1 -1
  130. package/dist/esm/decimal/format.js +71 -70
  131. package/dist/esm/decimal/format.js.map +1 -1
  132. package/dist/esm/decimal/parse.d.ts +29 -13
  133. package/dist/esm/decimal/parse.d.ts.map +1 -1
  134. package/dist/esm/decimal/parse.js +61 -44
  135. package/dist/esm/decimal/parse.js.map +1 -1
  136. package/dist/esm/errors/invalidInput.d.ts +4 -0
  137. package/dist/esm/errors/invalidInput.d.ts.map +1 -0
  138. package/dist/esm/errors/invalidInput.js +7 -0
  139. package/dist/esm/errors/invalidInput.js.map +1 -0
  140. package/dist/esm/index.d.ts +15 -12
  141. package/dist/esm/index.d.ts.map +1 -1
  142. package/dist/esm/index.js +11 -8
  143. package/dist/esm/index.js.map +1 -1
  144. package/dist/esm/math/fills.d.ts +6 -3
  145. package/dist/esm/math/fills.d.ts.map +1 -1
  146. package/dist/esm/math/fills.js +31 -16
  147. package/dist/esm/math/fills.js.map +1 -1
  148. package/dist/esm/math/order.d.ts +84 -66
  149. package/dist/esm/math/order.d.ts.map +1 -1
  150. package/dist/esm/math/order.js +178 -175
  151. package/dist/esm/math/order.js.map +1 -1
  152. package/dist/esm/math/position.d.ts +155 -98
  153. package/dist/esm/math/position.d.ts.map +1 -1
  154. package/dist/esm/math/position.js +160 -156
  155. package/dist/esm/math/position.js.map +1 -1
  156. package/dist/esm/services/getGasRecommendation.js +1 -1
  157. package/dist/esm/services/getGasRecommendation.js.map +1 -1
  158. package/dist/esm/services/getQuote.d.ts +2 -2
  159. package/dist/esm/services/getQuote.js +1 -1
  160. package/dist/esm/services/resolveSubscribeQuote.d.ts +1 -1
  161. package/dist/esm/services/resolveSubscribeQuote.d.ts.map +1 -1
  162. package/dist/esm/services/resolveSubscribeQuote.js +4 -3
  163. package/dist/esm/services/resolveSubscribeQuote.js.map +1 -1
  164. package/dist/esm/transport/request.d.ts +5 -0
  165. package/dist/esm/transport/request.d.ts.map +1 -1
  166. package/dist/esm/transport/request.js +2 -2
  167. package/dist/esm/transport/request.js.map +1 -1
  168. package/dist/esm/types/api.d.ts +9 -0
  169. package/dist/esm/types/api.d.ts.map +1 -1
  170. package/dist/esm/types/provider.d.ts +30 -16
  171. package/dist/esm/types/provider.d.ts.map +1 -1
  172. package/dist/esm/types/withdrawal.d.ts +32 -7
  173. package/dist/esm/types/withdrawal.d.ts.map +1 -1
  174. package/dist/esm/utils/createSafeFunction.d.ts +9 -0
  175. package/dist/esm/utils/createSafeFunction.d.ts.map +1 -0
  176. package/dist/esm/utils/createSafeFunction.js +19 -0
  177. package/dist/esm/utils/createSafeFunction.js.map +1 -0
  178. package/dist/esm/utils/timestampToIsoString.d.ts +15 -0
  179. package/dist/esm/utils/timestampToIsoString.d.ts.map +1 -0
  180. package/dist/esm/utils/timestampToIsoString.js +24 -0
  181. package/dist/esm/utils/timestampToIsoString.js.map +1 -0
  182. package/dist/esm/utils/warnOnce.d.ts +27 -0
  183. package/dist/esm/utils/warnOnce.d.ts.map +1 -1
  184. package/dist/esm/utils/warnOnce.js +36 -0
  185. package/dist/esm/utils/warnOnce.js.map +1 -1
  186. package/dist/esm/version.d.ts +1 -1
  187. package/dist/esm/version.js +1 -1
  188. package/dist/esm/websocket/PerpsWsClient.d.ts +7 -0
  189. package/dist/esm/websocket/PerpsWsClient.d.ts.map +1 -1
  190. package/dist/esm/websocket/PerpsWsClient.js +20 -2
  191. package/dist/esm/websocket/PerpsWsClient.js.map +1 -1
  192. package/dist/esm/websocket/wsLog.d.ts +12 -0
  193. package/dist/esm/websocket/wsLog.d.ts.map +1 -1
  194. package/dist/esm/websocket/wsLog.js +19 -0
  195. package/dist/esm/websocket/wsLog.js.map +1 -1
  196. package/dist/esm/wire/orderAmounts.d.ts +9 -7
  197. package/dist/esm/wire/orderAmounts.d.ts.map +1 -1
  198. package/dist/esm/wire/orderAmounts.js +24 -20
  199. package/dist/esm/wire/orderAmounts.js.map +1 -1
  200. package/dist/esm/wire/refuel.d.ts +24 -14
  201. package/dist/esm/wire/refuel.d.ts.map +1 -1
  202. package/dist/esm/wire/refuel.js +38 -17
  203. package/dist/esm/wire/refuel.js.map +1 -1
  204. package/dist/esm/wire/transferable.d.ts +2 -2
  205. package/dist/esm/wire/transferable.js +5 -5
  206. package/dist/esm/wire/transferable.js.map +1 -1
  207. package/dist/esm/wire/withdraw.d.ts.map +1 -1
  208. package/dist/esm/wire/withdraw.js +3 -3
  209. package/dist/esm/wire/withdraw.js.map +1 -1
  210. package/dist/types/client/PerpsClient.d.ts +17 -4
  211. package/dist/types/client/PerpsClient.d.ts.map +1 -1
  212. package/dist/types/decimal/arithmetic.d.ts +98 -0
  213. package/dist/types/decimal/arithmetic.d.ts.map +1 -0
  214. package/dist/types/decimal/big.d.ts +0 -2
  215. package/dist/types/decimal/big.d.ts.map +1 -1
  216. package/dist/types/decimal/compare.d.ts +40 -6
  217. package/dist/types/decimal/compare.d.ts.map +1 -1
  218. package/dist/types/decimal/convert.d.ts +50 -36
  219. package/dist/types/decimal/convert.d.ts.map +1 -1
  220. package/dist/types/decimal/decimalStringToBig.d.ts +25 -0
  221. package/dist/types/decimal/decimalStringToBig.d.ts.map +1 -0
  222. package/dist/types/decimal/format.d.ts +4 -1
  223. package/dist/types/decimal/format.d.ts.map +1 -1
  224. package/dist/types/decimal/parse.d.ts +29 -13
  225. package/dist/types/decimal/parse.d.ts.map +1 -1
  226. package/dist/types/errors/invalidInput.d.ts +4 -0
  227. package/dist/types/errors/invalidInput.d.ts.map +1 -0
  228. package/dist/types/index.d.ts +15 -12
  229. package/dist/types/index.d.ts.map +1 -1
  230. package/dist/types/math/fills.d.ts +6 -3
  231. package/dist/types/math/fills.d.ts.map +1 -1
  232. package/dist/types/math/order.d.ts +84 -66
  233. package/dist/types/math/order.d.ts.map +1 -1
  234. package/dist/types/math/position.d.ts +155 -98
  235. package/dist/types/math/position.d.ts.map +1 -1
  236. package/dist/types/services/getQuote.d.ts +2 -2
  237. package/dist/types/services/resolveSubscribeQuote.d.ts +1 -1
  238. package/dist/types/services/resolveSubscribeQuote.d.ts.map +1 -1
  239. package/dist/types/transport/request.d.ts +5 -0
  240. package/dist/types/transport/request.d.ts.map +1 -1
  241. package/dist/types/types/api.d.ts +9 -0
  242. package/dist/types/types/api.d.ts.map +1 -1
  243. package/dist/types/types/provider.d.ts +30 -16
  244. package/dist/types/types/provider.d.ts.map +1 -1
  245. package/dist/types/types/withdrawal.d.ts +32 -7
  246. package/dist/types/types/withdrawal.d.ts.map +1 -1
  247. package/dist/types/utils/createSafeFunction.d.ts +9 -0
  248. package/dist/types/utils/createSafeFunction.d.ts.map +1 -0
  249. package/dist/types/utils/timestampToIsoString.d.ts +15 -0
  250. package/dist/types/utils/timestampToIsoString.d.ts.map +1 -0
  251. package/dist/types/utils/warnOnce.d.ts +27 -0
  252. package/dist/types/utils/warnOnce.d.ts.map +1 -1
  253. package/dist/types/version.d.ts +1 -1
  254. package/dist/types/websocket/PerpsWsClient.d.ts +7 -0
  255. package/dist/types/websocket/PerpsWsClient.d.ts.map +1 -1
  256. package/dist/types/websocket/wsLog.d.ts +12 -0
  257. package/dist/types/websocket/wsLog.d.ts.map +1 -1
  258. package/dist/types/wire/orderAmounts.d.ts +9 -7
  259. package/dist/types/wire/orderAmounts.d.ts.map +1 -1
  260. package/dist/types/wire/refuel.d.ts +24 -14
  261. package/dist/types/wire/refuel.d.ts.map +1 -1
  262. package/dist/types/wire/transferable.d.ts +2 -2
  263. package/dist/types/wire/withdraw.d.ts.map +1 -1
  264. package/package.json +2 -2
  265. package/src/client/PerpsClient.ts +48 -10
  266. package/src/decimal/arithmetic.ts +170 -0
  267. package/src/decimal/big.ts +0 -5
  268. package/src/decimal/compare.ts +61 -11
  269. package/src/decimal/convert.ts +105 -63
  270. package/src/decimal/decimalStringToBig.ts +42 -0
  271. package/src/decimal/format.ts +81 -72
  272. package/src/decimal/parse.ts +78 -57
  273. package/src/errors/invalidInput.ts +10 -0
  274. package/src/index.ts +80 -12
  275. package/src/math/fills.ts +32 -17
  276. package/src/math/order.ts +279 -226
  277. package/src/math/position.ts +280 -196
  278. package/src/services/getGasRecommendation.ts +1 -1
  279. package/src/services/getQuote.ts +2 -2
  280. package/src/services/resolveSubscribeQuote.ts +4 -3
  281. package/src/transport/request.ts +7 -2
  282. package/src/types/api.ts +10 -0
  283. package/src/types/provider.ts +39 -16
  284. package/src/types/withdrawal.ts +35 -8
  285. package/src/utils/createSafeFunction.ts +20 -0
  286. package/src/utils/timestampToIsoString.ts +31 -0
  287. package/src/utils/warnOnce.ts +60 -0
  288. package/src/version.ts +1 -1
  289. package/src/websocket/PerpsWsClient.ts +25 -2
  290. package/src/websocket/wsLog.ts +44 -0
  291. package/src/wire/orderAmounts.ts +45 -26
  292. package/src/wire/refuel.ts +56 -20
  293. package/src/wire/transferable.ts +5 -5
  294. package/src/wire/withdraw.ts +3 -5
  295. package/dist/cjs/decimal/requireDecimal.d.ts +0 -4
  296. package/dist/cjs/decimal/requireDecimal.d.ts.map +0 -1
  297. package/dist/cjs/decimal/requireDecimal.js +0 -12
  298. package/dist/cjs/decimal/requireDecimal.js.map +0 -1
  299. package/dist/esm/decimal/requireDecimal.d.ts +0 -5
  300. package/dist/esm/decimal/requireDecimal.d.ts.map +0 -1
  301. package/dist/esm/decimal/requireDecimal.js +0 -7
  302. package/dist/esm/decimal/requireDecimal.js.map +0 -1
  303. package/dist/types/decimal/requireDecimal.d.ts +0 -5
  304. package/dist/types/decimal/requireDecimal.d.ts.map +0 -1
  305. package/src/decimal/requireDecimal.ts +0 -8
package/src/math/order.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Display-tier order formulas. Every function takes and gives `number`;
3
- * exact decimal arithmetic happens internally with `DivBig`.
2
+ * Display-tier order formulas. Every function takes and gives decimal
3
+ * strings; arithmetic is exact Big.js, with division to 40 decimal places.
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.
4
6
  */
5
7
 
6
8
  import {
@@ -20,10 +22,15 @@ import {
20
22
  type TradeType,
21
23
  type TriggerOrder,
22
24
  } from '@lifi/perps-types'
23
- import type Big from 'big.js'
24
- import { areFinite, DivBig } from '../decimal/big.js'
25
- import { numberToDecimalString } from '../decimal/convert.js'
25
+ import { DivBig } from '../decimal/big.js'
26
+ import {
27
+ bigToDecimalString,
28
+ decimalStringToBig,
29
+ decimalStringToDivBig,
30
+ } from '../decimal/decimalStringToBig.js'
31
+ import { invalidInput } from '../errors/invalidInput.js'
26
32
  import { PerpsError } from '../errors/PerpsError.js'
33
+ import { createSafeFunction } from '../utils/createSafeFunction.js'
27
34
  import {
28
35
  isActiveOrderStatus,
29
36
  isRegularOrder,
@@ -32,74 +39,88 @@ import {
32
39
  import { calculateRealizedPnl } from './position.js'
33
40
 
34
41
  /**
35
- * Calculate position size in asset units from margin.
42
+ * Position size in asset units from margin: `marginUsd × leverage ÷ price`.
36
43
  *
37
- * @param marginUsd - Margin amount in USD
38
- * @param leverage - Position leverage
39
- * @param price - Current asset price
40
- * @returns Position size in asset units
41
44
  * @example
42
45
  * ```ts
43
- * calculateSize(100, 10, 2000) // 0.5 (ETH at $2000)
46
+ * calculateSize('100', '10', '2000') // '0.5' (ETH at $2000)
44
47
  * ```
48
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern
49
+ * or `price` is zero.
45
50
  * @public
46
51
  */
47
52
  export function calculateSize(
48
- marginUsd: number,
49
- leverage: number,
50
- price: number
51
- ): number {
52
- if (price === 0) {
53
- return (marginUsd * leverage) / price
54
- }
55
- if (!areFinite(marginUsd, leverage, price)) {
56
- return Number.NaN
53
+ marginUsd: string,
54
+ leverage: string,
55
+ price: string
56
+ ): string {
57
+ const notional = decimalStringToDivBig(marginUsd).times(
58
+ decimalStringToBig(leverage)
59
+ )
60
+ const priceBig = decimalStringToBig(price)
61
+ if (priceBig.eq(0)) {
62
+ throw invalidInput('price', 'must not be zero')
57
63
  }
58
- return new DivBig(marginUsd).times(leverage).div(price).toNumber()
64
+ return bigToDecimalString(notional.div(priceBig))
59
65
  }
60
66
 
67
+ /** @public */
68
+ export const safeCalculateSize = createSafeFunction(
69
+ 'calculateSize',
70
+ calculateSize
71
+ )
72
+
61
73
  /**
62
- * Estimate trading fees.
74
+ * Estimated trading fee in USD: `sizeUsd × feeRate`.
63
75
  *
64
- * @param sizeUsd - Position size in USD (notional value)
65
- * @param feeRate - Fee rate as decimal (e.g., 0.00035 for 0.035%)
66
- * @returns Estimated fee in USD
76
+ * @param feeRate - Fee rate as a fraction (`'0.00035'` for 0.035%).
77
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
67
78
  * @public
68
79
  */
69
- export function estimateFees(sizeUsd: number, feeRate: number): number {
70
- if (!areFinite(sizeUsd, feeRate)) {
71
- return Number.NaN
72
- }
73
- return new DivBig(sizeUsd).times(feeRate).toNumber()
80
+ export function estimateFees(sizeUsd: string, feeRate: string): string {
81
+ return bigToDecimalString(
82
+ decimalStringToBig(sizeUsd).times(decimalStringToBig(feeRate))
83
+ )
74
84
  }
75
85
 
86
+ /** @public */
87
+ export const safeEstimateFees = createSafeFunction('estimateFees', estimateFees)
88
+
76
89
  /**
77
- * Apply slippage to a price for order execution.
90
+ * Apply slippage to an order-entry price with exact decimal math: a buy
91
+ * multiplies by `1 + slippagePercent / 100`, a sell divides by it. The result
92
+ * is not rounded; snap it to the market tick before it goes to a venue.
78
93
  *
79
- * @param price - Base price
80
- * @param slippagePercent - Slippage tolerance as percentage (e.g., 0.5 for 0.5%)
81
- * @param isBuy - True if buying (price goes up), false if selling (price goes down)
82
- * @returns Price adjusted for slippage
94
+ * @param slippagePercent - Slippage tolerance as a percentage (`'0.5'` is 0.5%).
95
+ * @throws {PerpsError} `ValidationError` when an input does not match the
96
+ * decimal pattern, or `slippagePercent` is -100 or less.
83
97
  * @public
84
98
  */
85
- export function applySlippage(
86
- price: number,
87
- slippagePercent: number,
99
+ export function applySlippageToPrice(
100
+ price: string,
101
+ slippagePercent: string,
88
102
  isBuy: boolean
89
- ): number {
90
- if (!areFinite(price, slippagePercent)) {
91
- return Number.NaN
92
- }
93
- const multiplier = new DivBig(slippagePercent).div(100).plus(1)
94
- if (isBuy) {
95
- return new DivBig(price).times(multiplier).toNumber()
96
- }
97
- if (multiplier.eq(0)) {
98
- return price / multiplier.toNumber()
103
+ ): string {
104
+ const base = decimalStringToDivBig(price)
105
+ const slippage = decimalStringToBig(slippagePercent)
106
+ if (slippage.lte(-100)) {
107
+ throw new PerpsError(
108
+ PerpsErrorCode.ValidationError,
109
+ `Invalid \`slippagePercent\`: ${slippagePercent}.`
110
+ )
99
111
  }
100
- return new DivBig(price).div(multiplier).toNumber()
112
+ const multiplier = new DivBig(slippage).div(100).plus(1)
113
+ return bigToDecimalString(
114
+ isBuy ? base.times(multiplier) : base.div(multiplier)
115
+ )
101
116
  }
102
117
 
118
+ /** @public */
119
+ export const safeApplySlippageToPrice = createSafeFunction(
120
+ 'applySlippageToPrice',
121
+ applySlippageToPrice
122
+ )
123
+
103
124
  /**
104
125
  * Signed expected PnL for a trigger price — see {@link calculateExpectedPnl}.
105
126
  *
@@ -107,131 +128,164 @@ export function applySlippage(
107
128
  */
108
129
  export interface ExpectedPnl {
109
130
  /** Signed expected profit/loss in USD. */
110
- amount: number
111
- /** Signed expected return as a percentage (10 means 10%). */
112
- percent: number
131
+ amount: string
132
+ /** Signed expected return as a percentage (`'10'` means 10%). */
133
+ percent: string
113
134
  }
114
135
 
115
136
  /**
116
- * Calculate expected gain/loss for a TP or SL trigger price.
117
- * Returns signed values — positive means profit, negative means loss.
137
+ * Expected gain or loss for a TP or SL trigger price. Positive is a profit,
138
+ * negative a loss.
118
139
  *
119
- * @param triggerPrice - The TP or SL target price
120
- * @param entryPrice - Position entry / current market price
121
- * @param leverage - Position leverage multiplier
122
- * @param isLong - True for long positions, false for short
123
- * @param margin - Margin amount in USD
140
+ * @param entryPrice - Position entry or current market price.
141
+ * @param margin - Margin amount in USD.
142
+ * @throws {PerpsError} `ValidationError` when an input does not match the
143
+ * decimal pattern, or `triggerPrice`, `entryPrice` or `margin` is zero.
124
144
  * @public
125
145
  */
126
146
  export function calculateExpectedPnl(
127
- triggerPrice: number,
128
- entryPrice: number,
129
- leverage: number,
147
+ triggerPrice: string,
148
+ entryPrice: string,
149
+ leverage: string,
130
150
  isLong: boolean,
131
- margin: number
132
- ): ExpectedPnl | null {
133
- if (!triggerPrice || entryPrice === 0 || margin === 0) {
134
- return null
151
+ margin: string
152
+ ): ExpectedPnl {
153
+ const trigger = decimalStringToDivBig(triggerPrice)
154
+ const entry = decimalStringToBig(entryPrice)
155
+ const leverageBig = decimalStringToBig(leverage)
156
+ const marginBig = decimalStringToBig(margin)
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')
135
162
  }
136
- if (!areFinite(triggerPrice, entryPrice, leverage, margin)) {
137
- return { amount: Number.NaN, percent: Number.NaN }
163
+ if (marginBig.eq(0)) {
164
+ throw invalidInput('margin', 'must not be zero')
138
165
  }
139
166
  const priceDiff = isLong
140
- ? new DivBig(triggerPrice).minus(entryPrice)
141
- : new DivBig(entryPrice).minus(triggerPrice)
142
- const percent = priceDiff.div(entryPrice).times(leverage).times(100)
143
- const amount = percent.times(margin).div(100)
144
- return { amount: amount.toNumber(), percent: percent.toNumber() }
167
+ ? trigger.minus(entry)
168
+ : trigger.times(-1).plus(entry)
169
+ const percent = priceDiff.div(entry).times(leverageBig).times(100)
170
+ const amount = percent.times(marginBig).div(100)
171
+ return {
172
+ amount: bigToDecimalString(amount),
173
+ percent: bigToDecimalString(percent),
174
+ }
145
175
  }
146
176
 
177
+ /** @public */
178
+ export const safeCalculateExpectedPnl = createSafeFunction(
179
+ 'calculateExpectedPnl',
180
+ calculateExpectedPnl
181
+ )
182
+
147
183
  /**
148
- * Calculate the trigger price that realises a percentage gain/loss.
184
+ * Trigger price that realises a percentage gain or loss.
149
185
  *
150
- * @param percent - Target gain/loss percentage (positive = profitable direction)
151
- * @param entryPrice - Position entry price
152
- * @param leverage - Position leverage multiplier
153
- * @param isLong - True for long positions, false for short
186
+ * @param percent - Target gain or loss percentage (positive = profitable
187
+ * direction).
188
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
154
189
  * @public
155
190
  */
156
191
  export function calculateTriggerPrice(
157
- percent: number,
158
- entryPrice: number,
159
- leverage: number,
192
+ percent: string,
193
+ entryPrice: string,
194
+ leverage: string,
160
195
  isLong: boolean
161
- ): number {
162
- if (entryPrice === 0 || leverage === 0) {
163
- return 0
196
+ ): string {
197
+ const percentBig = decimalStringToDivBig(percent)
198
+ const entry = decimalStringToBig(entryPrice)
199
+ const leverageBig = decimalStringToBig(leverage)
200
+ if (entry.eq(0)) {
201
+ throw invalidInput('entryPrice', 'must not be zero')
164
202
  }
165
- if (!areFinite(percent, entryPrice, leverage)) {
166
- return Number.NaN
203
+ if (leverageBig.eq(0)) {
204
+ throw invalidInput('leverage', 'must not be zero')
167
205
  }
168
- const priceDelta = new DivBig(percent)
169
- .times(entryPrice)
170
- .div(new DivBig(leverage).times(100))
171
- const entry = new DivBig(entryPrice)
172
- return (isLong ? entry.plus(priceDelta) : entry.minus(priceDelta)).toNumber()
206
+ const priceDelta = percentBig.times(entry).div(leverageBig.times(100))
207
+ return bigToDecimalString(
208
+ isLong ? entry.plus(priceDelta) : entry.minus(priceDelta)
209
+ )
173
210
  }
174
211
 
212
+ /** @public */
213
+ export const safeCalculateTriggerPrice = createSafeFunction(
214
+ 'calculateTriggerPrice',
215
+ calculateTriggerPrice
216
+ )
217
+
175
218
  /**
176
- * Calculate the percentage gain/loss a trigger price realises.
219
+ * Percentage gain or loss that a trigger price realises.
177
220
  *
178
- * @param price - Target price
179
- * @param entryPrice - Position entry price
180
- * @param leverage - Position leverage multiplier
181
- * @param isLong - True for long positions, false for short
221
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
182
222
  * @public
183
223
  */
184
224
  export function calculateTriggerPercent(
185
- price: number,
186
- entryPrice: number,
187
- leverage: number,
225
+ price: string,
226
+ entryPrice: string,
227
+ leverage: string,
188
228
  isLong: boolean
189
- ): number {
190
- if (entryPrice === 0 || leverage === 0) {
191
- return 0
229
+ ): string {
230
+ const priceBig = decimalStringToDivBig(price)
231
+ const entry = decimalStringToBig(entryPrice)
232
+ const leverageBig = decimalStringToBig(leverage)
233
+ if (entry.eq(0)) {
234
+ throw invalidInput('entryPrice', 'must not be zero')
192
235
  }
193
- if (!areFinite(price, entryPrice, leverage)) {
194
- return Number.NaN
236
+ if (leverageBig.eq(0)) {
237
+ throw invalidInput('leverage', 'must not be zero')
195
238
  }
196
239
  const priceDiff = isLong
197
- ? new DivBig(price).minus(entryPrice)
198
- : new DivBig(entryPrice).minus(price)
199
- return priceDiff.div(entryPrice).times(leverage).times(100).toNumber()
240
+ ? priceBig.minus(entry)
241
+ : priceBig.times(-1).plus(entry)
242
+ return bigToDecimalString(priceDiff.div(entry).times(leverageBig).times(100))
200
243
  }
201
244
 
245
+ /** @public */
246
+ export const safeCalculateTriggerPercent = createSafeFunction(
247
+ 'calculateTriggerPercent',
248
+ calculateTriggerPercent
249
+ )
250
+
202
251
  /**
203
- * Calculate realized PnL as a percentage of position value at close.
252
+ * Realized PnL as a percentage of the position value at close:
253
+ * `realizedPnl ÷ (|size| × price) × 100`.
204
254
  *
205
- * @param realizedPnl - The realized profit/loss in USD
206
- * @param size - Position size in asset units at close
207
- * @param price - Price at close
208
- * @returns PnL as a percentage of position value
255
+ * @throws {PerpsError} `ValidationError` when an input does not match the
256
+ * decimal pattern or `size × price` is zero.
209
257
  * @public
210
258
  */
211
259
  export function calculateRealizedPnlPercent(
212
- realizedPnl: number,
213
- size: number,
214
- price: number
215
- ): number {
216
- if (!areFinite(realizedPnl, size, price)) {
217
- return Number.NaN
218
- }
219
- const positionValue = new DivBig(size).abs().times(price)
260
+ realizedPnl: string,
261
+ size: string,
262
+ price: string
263
+ ): string {
264
+ const pnl = decimalStringToDivBig(realizedPnl)
265
+ const positionValue = decimalStringToBig(size)
266
+ .abs()
267
+ .times(decimalStringToBig(price))
220
268
  if (positionValue.eq(0)) {
221
- return 0
269
+ throw invalidInput('size × price', 'must not be zero')
222
270
  }
223
- return new DivBig(realizedPnl).div(positionValue).times(100).toNumber()
271
+ return bigToDecimalString(pnl.div(positionValue).times(100))
224
272
  }
225
273
 
274
+ /** @public */
275
+ export const safeCalculateRealizedPnlPercent = createSafeFunction(
276
+ 'calculateRealizedPnlPercent',
277
+ calculateRealizedPnlPercent
278
+ )
279
+
226
280
  /** Result of {@link walkOrderbook} — the fill obtained for a USD-notional walk. */
227
281
  interface BookWalk {
228
282
  /** Base amount filled. */
229
- baseSize: number
283
+ baseSize: string
230
284
  /** Notional actually filled in USD (equals the requested size unless the book ran dry). */
231
- filledNotional: number
232
- /** Volume-weighted average fill price, or 0 when the book is empty. */
233
- vwap: number
234
- /** True when the levels could not absorb the requested notional, and for a non-finite notional. */
285
+ filledNotional: string
286
+ /** Volume-weighted average fill price, or `'0'` when the book is empty. */
287
+ vwap: string
288
+ /** True when the levels could not absorb the requested notional. */
235
289
  insufficientLiquidity: boolean
236
290
  }
237
291
 
@@ -241,34 +295,25 @@ interface BookWalk {
241
295
  * in array order — the caller passes asks for a buy and bids for a sell, each
242
296
  * already ordered best-price-first. When the book cannot absorb the full
243
297
  * notional, the walk stops at the last level and flags `insufficientLiquidity`,
244
- * returning the best obtainable fill. A non-finite `sizeUsd` gives NaN fill
245
- * fields with `insufficientLiquidity` set.
298
+ * returning the best obtainable fill.
246
299
  *
247
- * @throws {PerpsError} `ValidationError` when a level's `price` or `size`
248
- * does not parse to a finite number.
300
+ * @throws {PerpsError} `ValidationError` when `sizeUsd` or a level's `price`
301
+ * or `size` does not match the decimal pattern.
249
302
  * @public
250
303
  */
251
304
  export function walkOrderbook(
252
305
  levels: OrderbookLevel[],
253
- sizeUsd: number
306
+ sizeUsd: string
254
307
  ): BookWalk {
255
- if (!Number.isFinite(sizeUsd)) {
256
- return {
257
- baseSize: Number.NaN,
258
- filledNotional: Number.NaN,
259
- vwap: Number.NaN,
260
- insufficientLiquidity: true,
261
- }
262
- }
263
- let remaining = new DivBig(sizeUsd)
308
+ let remaining = decimalStringToDivBig(sizeUsd)
264
309
  let baseSize = new DivBig(0)
265
310
  let filledNotional = new DivBig(0)
266
311
  for (const level of levels) {
267
312
  if (remaining.lte(0)) {
268
313
  break
269
314
  }
270
- const { price, size } = parseLevel(level)
271
- const levelNotional = size.times(price)
315
+ const price = decimalStringToDivBig(level.price)
316
+ const levelNotional = decimalStringToBig(level.size).times(price)
272
317
  const take = remaining.lt(levelNotional) ? remaining : levelNotional
273
318
  if (take.eq(0)) {
274
319
  continue
@@ -278,23 +323,20 @@ export function walkOrderbook(
278
323
  remaining = remaining.minus(take)
279
324
  }
280
325
  return {
281
- baseSize: baseSize.toNumber(),
282
- filledNotional: filledNotional.toNumber(),
283
- vwap: baseSize.eq(0) ? 0 : filledNotional.div(baseSize).toNumber(),
326
+ baseSize: bigToDecimalString(baseSize),
327
+ filledNotional: bigToDecimalString(filledNotional),
328
+ vwap: baseSize.eq(0)
329
+ ? '0'
330
+ : bigToDecimalString(filledNotional.div(baseSize)),
284
331
  insufficientLiquidity: remaining.gt(0),
285
332
  }
286
333
  }
287
334
 
288
- function parseLevel(level: OrderbookLevel): { price: Big; size: Big } {
289
- try {
290
- return { price: new DivBig(level.price), size: new DivBig(level.size) }
291
- } catch {
292
- throw new PerpsError(
293
- PerpsErrorCode.ValidationError,
294
- `Malformed orderbook level: price='${level.price}', size='${level.size}'`
295
- )
296
- }
297
- }
335
+ /** @public */
336
+ export const safeWalkOrderbook = createSafeFunction(
337
+ 'walkOrderbook',
338
+ walkOrderbook
339
+ )
298
340
 
299
341
  /** Inputs to {@link buildQuote} — the resolved market, its live price, its book, and the trade ask. */
300
342
  interface BuildQuoteInput {
@@ -302,7 +344,7 @@ interface BuildQuoteInput {
302
344
  symbol: string
303
345
  type: TradeType
304
346
  side: QuoteSide
305
- sizeUsd: number
347
+ sizeUsd: string
306
348
  market: Market
307
349
  price: MarketContext
308
350
  bids: OrderbookLevel[]
@@ -319,44 +361,45 @@ interface BuildQuoteInput {
319
361
  * versus mark, applies the base taker fee on the filled notional, and carries
320
362
  * the market's `funding` (`null` for spot, which has none).
321
363
  *
322
- * @throws {PerpsError} `ValidationError` when a book level does not parse to
323
- * a finite number — see {@link walkOrderbook} — or when a quoted figure is
324
- * not finite (a non-finite `sizeUsd`, mark price or taker fee).
364
+ * @throws {PerpsError} `ValidationError` when `sizeUsd`, the mark price, the
365
+ * taker fee or a book level does not match the decimal pattern.
325
366
  * @public
326
367
  */
327
368
  export function buildQuote(input: BuildQuoteInput): Quote {
328
369
  const { market, price, side, sizeUsd, feeTier } = input
329
- const markPrice = Number.parseFloat(price.markPrice)
370
+ const markPrice = decimalStringToDivBig(price.markPrice)
330
371
  const levels = side === 'buy' ? input.asks : input.bids
331
372
  const walk = walkOrderbook(levels, sizeUsd)
373
+ const vwap = decimalStringToBig(walk.vwap)
332
374
  const priceImpactBps =
333
- markPrice === 0 || walk.vwap === 0
334
- ? 0
335
- : Math.abs((walk.vwap - markPrice) / markPrice) * 10_000
336
- const feeUsd = estimateFees(
337
- walk.filledNotional,
338
- Number.parseFloat(feeTier.taker)
339
- )
375
+ markPrice.eq(0) || vwap.eq(0)
376
+ ? '0'
377
+ : bigToDecimalString(
378
+ vwap.minus(markPrice).div(markPrice).abs().times(10_000)
379
+ )
340
380
  return {
341
381
  provider: input.provider,
342
382
  symbol: input.symbol,
343
383
  marketId: market.id,
344
384
  type: input.type,
345
385
  side,
346
- sizeUsd: numberToDecimalString(sizeUsd),
347
- baseSize: numberToDecimalString(walk.baseSize),
386
+ sizeUsd: bigToDecimalString(decimalStringToBig(sizeUsd)),
387
+ baseSize: walk.baseSize,
348
388
  markPrice: price.markPrice,
349
- expectedFillPrice: numberToDecimalString(walk.vwap),
350
- priceImpactBps: numberToDecimalString(priceImpactBps),
389
+ expectedFillPrice: walk.vwap,
390
+ priceImpactBps,
351
391
  feeTier,
352
392
  isDefaultFeeTier: true,
353
- feeUsd: numberToDecimalString(feeUsd),
393
+ feeUsd: estimateFees(walk.filledNotional, feeTier.taker),
354
394
  funding: price.funding ?? null,
355
395
  insufficientLiquidity: walk.insufficientLiquidity,
356
396
  timestamp: input.timestamp,
357
397
  }
358
398
  }
359
399
 
400
+ /** @public */
401
+ export const safeBuildQuote = createSafeFunction('buildQuote', buildQuote)
402
+
360
403
  /**
361
404
  * Pick the matching open position for an order's market, if any.
362
405
  *
@@ -379,23 +422,33 @@ export function findMatchingPosition(
379
422
  * position can only close what's open.
380
423
  *
381
424
  * Inputs are non-negative magnitudes.
425
+ *
426
+ * @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
382
427
  * @public
383
428
  */
384
429
  export function resolveCloseSize(
385
- orderSize: number,
386
- positionSize: number
387
- ): number {
388
- if (orderSize === 0) {
389
- return positionSize
430
+ orderSize: string,
431
+ positionSize: string
432
+ ): string {
433
+ const order = decimalStringToBig(orderSize)
434
+ const position = decimalStringToBig(positionSize)
435
+ if (order.eq(0)) {
436
+ return bigToDecimalString(position)
390
437
  }
391
- return Math.min(orderSize, positionSize)
438
+ return bigToDecimalString(order.lt(position) ? order : position)
392
439
  }
393
440
 
441
+ /** @public */
442
+ export const safeResolveCloseSize = createSafeFunction(
443
+ 'resolveCloseSize',
444
+ resolveCloseSize
445
+ )
446
+
394
447
  /**
395
448
  * Expected rPnL for a resting limit order against a matching position.
396
449
  *
397
450
  * Reducing requires opposite sides (long position + SELL, short position +
398
- * 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`.
399
452
  * Projects `remainingSize`, because an already-filled quantity has realised
400
453
  * its PnL at the fill price rather than at this order's limit price. A
401
454
  * `remainingSize` of zero leaves nothing to project.
@@ -403,9 +456,9 @@ export function resolveCloseSize(
403
456
  function regularOrderRealizedPnl(
404
457
  order: RegularOrder,
405
458
  position: Position | undefined
406
- ): number | null {
459
+ ): string | undefined {
407
460
  if (!position || !isActiveOrderStatus(order.status)) {
408
- return null
461
+ return undefined
409
462
  }
410
463
 
411
464
  const isLong = position.side === PositionSide.LONG
@@ -413,35 +466,32 @@ function regularOrderRealizedPnl(
413
466
  (isLong && order.side === OrderSide.SELL) ||
414
467
  (!isLong && order.side === OrderSide.BUY)
415
468
  if (!reducesPosition) {
416
- return null
469
+ return undefined
417
470
  }
418
471
 
419
- const limitPrice = Number.parseFloat(order.price ?? '')
420
- const entryPrice = Number.parseFloat(position.entryPrice)
421
- const orderSize = Math.abs(Number.parseFloat(order.remainingSize))
422
- const positionSize = Math.abs(Number.parseFloat(position.size))
423
- if (
424
- !Number.isFinite(limitPrice) ||
425
- !Number.isFinite(entryPrice) ||
426
- !Number.isFinite(orderSize) ||
427
- !Number.isFinite(positionSize) ||
428
- positionSize <= 0
429
- ) {
430
- return null
472
+ if (order.price === undefined) {
473
+ return undefined
474
+ }
475
+ const orderSize = decimalStringToBig(order.remainingSize).abs()
476
+ const positionSize = decimalStringToBig(position.size).abs()
477
+ if (positionSize.lte(0)) {
478
+ return undefined
431
479
  }
432
480
 
433
481
  // `resolveCloseSize` reads a zero size as "close the whole position", a
434
482
  // convention that belongs to an order's submitted size. `remainingSize` is
435
483
  // the unfilled quantity, so zero means nothing is left to fill.
436
- if (orderSize === 0) {
437
- return null
484
+ if (orderSize.eq(0)) {
485
+ return undefined
438
486
  }
439
487
 
440
- const closeSize = resolveCloseSize(orderSize, positionSize)
441
488
  return calculateRealizedPnl({
442
- entryPrice,
443
- closePrice: limitPrice,
444
- closeSize,
489
+ entryPrice: position.entryPrice,
490
+ closePrice: order.price,
491
+ closeSize: resolveCloseSize(
492
+ bigToDecimalString(orderSize),
493
+ bigToDecimalString(positionSize)
494
+ ),
445
495
  isLong,
446
496
  })
447
497
  }
@@ -450,13 +500,14 @@ function regularOrderRealizedPnl(
450
500
  function triggerOrderRealizedPnl(
451
501
  order: TriggerOrder,
452
502
  position: Position | undefined
453
- ): number | null {
503
+ ): string | undefined {
454
504
  if (
455
505
  !position ||
506
+ order.triggerPrice === undefined ||
456
507
  !isActiveOrderStatus(order.status) ||
457
508
  order.status === OrderStatus.PENDING
458
509
  ) {
459
- return null
510
+ return undefined
460
511
  }
461
512
 
462
513
  const isLong = position.side === PositionSide.LONG
@@ -464,33 +515,27 @@ function triggerOrderRealizedPnl(
464
515
  (isLong && order.side !== OrderSide.SELL) ||
465
516
  (!isLong && order.side !== OrderSide.BUY)
466
517
  ) {
467
- return null
518
+ return undefined
468
519
  }
469
- const triggerPrice = Number.parseFloat(order.triggerPrice)
470
- const entryPrice = Number.parseFloat(position.entryPrice)
471
- const orderSize = Math.abs(Number.parseFloat(order.remainingSize))
472
- const positionSize = Math.abs(Number.parseFloat(position.size))
473
- if (
474
- !Number.isFinite(triggerPrice) ||
475
- !Number.isFinite(entryPrice) ||
476
- !Number.isFinite(orderSize) ||
477
- !Number.isFinite(positionSize) ||
478
- positionSize <= 0
479
- ) {
480
- return null
520
+ const orderSize = decimalStringToBig(order.remainingSize).abs()
521
+ const positionSize = decimalStringToBig(position.size).abs()
522
+ if (positionSize.lte(0)) {
523
+ return undefined
481
524
  }
482
525
  if (
483
- orderSize === 0 &&
484
- (Number.parseFloat(order.originalSize) !== 0 || !order.reduceOnly)
526
+ orderSize.eq(0) &&
527
+ (!decimalStringToBig(order.originalSize).eq(0) || !order.reduceOnly)
485
528
  ) {
486
- return null
529
+ return undefined
487
530
  }
488
531
 
489
- const closeSize = resolveCloseSize(orderSize, positionSize)
490
532
  return calculateRealizedPnl({
491
- entryPrice,
492
- closePrice: triggerPrice,
493
- closeSize,
533
+ entryPrice: position.entryPrice,
534
+ closePrice: order.triggerPrice,
535
+ closeSize: resolveCloseSize(
536
+ bigToDecimalString(orderSize),
537
+ bigToDecimalString(positionSize)
538
+ ),
494
539
  isLong,
495
540
  })
496
541
  }
@@ -502,18 +547,26 @@ function triggerOrderRealizedPnl(
502
547
  * nothing.
503
548
  *
504
549
  * @returns Realised PnL if the order would reduce the position, otherwise
505
- * `null`.
550
+ * `undefined`.
551
+ * @throws {PerpsError} `ValidationError` when a price or size on the order or
552
+ * the position does not match the decimal pattern.
506
553
  * @public
507
554
  */
508
555
  export function estimateRealizedPnl(
509
556
  order: Order,
510
557
  position: Position | undefined
511
- ): number | null {
558
+ ): string | undefined {
512
559
  if (isTriggerOrder(order)) {
513
560
  return triggerOrderRealizedPnl(order, position)
514
561
  }
515
562
  if (isRegularOrder(order)) {
516
563
  return regularOrderRealizedPnl(order, position)
517
564
  }
518
- return null
565
+ return undefined
519
566
  }
567
+
568
+ /** @public */
569
+ export const safeEstimateRealizedPnl = createSafeFunction(
570
+ 'estimateRealizedPnl',
571
+ estimateRealizedPnl
572
+ )