@lifi/perps-sdk 23.0.0 → 24.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 (289) hide show
  1. package/README.md +85 -45
  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 +13 -0
  7. package/dist/cjs/decimal/arithmetic.d.ts.map +1 -0
  8. package/dist/cjs/decimal/arithmetic.js +48 -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 +7 -0
  15. package/dist/cjs/decimal/compare.d.ts.map +1 -0
  16. package/dist/cjs/decimal/compare.js +21 -0
  17. package/dist/cjs/decimal/compare.js.map +1 -0
  18. package/dist/cjs/decimal/convert.d.ts +12 -6
  19. package/dist/cjs/decimal/convert.d.ts.map +1 -1
  20. package/dist/cjs/decimal/convert.js +36 -14
  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 +27 -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 -49
  28. package/dist/cjs/decimal/format.js.map +1 -1
  29. package/dist/cjs/decimal/parse.d.ts +4 -3
  30. package/dist/cjs/decimal/parse.d.ts.map +1 -1
  31. package/dist/cjs/decimal/parse.js +44 -19
  32. package/dist/cjs/decimal/parse.js.map +1 -1
  33. package/dist/cjs/index.d.ts +13 -10
  34. package/dist/cjs/index.d.ts.map +1 -1
  35. package/dist/cjs/index.js +66 -7
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/math/fills.d.ts +1 -1
  38. package/dist/cjs/math/fills.d.ts.map +1 -1
  39. package/dist/cjs/math/fills.js +28 -13
  40. package/dist/cjs/math/fills.js.map +1 -1
  41. package/dist/cjs/math/order.d.ts +27 -16
  42. package/dist/cjs/math/order.d.ts.map +1 -1
  43. package/dist/cjs/math/order.js +106 -122
  44. package/dist/cjs/math/order.js.map +1 -1
  45. package/dist/cjs/math/position.d.ts +85 -35
  46. package/dist/cjs/math/position.d.ts.map +1 -1
  47. package/dist/cjs/math/position.js +88 -91
  48. package/dist/cjs/math/position.js.map +1 -1
  49. package/dist/cjs/services/getGasRecommendation.js +1 -1
  50. package/dist/cjs/services/getGasRecommendation.js.map +1 -1
  51. package/dist/cjs/services/getQuote.d.ts +1 -1
  52. package/dist/cjs/services/resolveSubscribeQuote.d.ts.map +1 -1
  53. package/dist/cjs/services/resolveSubscribeQuote.js +3 -2
  54. package/dist/cjs/services/resolveSubscribeQuote.js.map +1 -1
  55. package/dist/cjs/transport/request.d.ts +1 -0
  56. package/dist/cjs/transport/request.d.ts.map +1 -1
  57. package/dist/cjs/transport/request.js +2 -2
  58. package/dist/cjs/transport/request.js.map +1 -1
  59. package/dist/cjs/types/api.d.ts +4 -0
  60. package/dist/cjs/types/api.d.ts.map +1 -1
  61. package/dist/cjs/types/provider.d.ts +9 -5
  62. package/dist/cjs/types/provider.d.ts.map +1 -1
  63. package/dist/cjs/types/withdrawal.d.ts +12 -4
  64. package/dist/cjs/types/withdrawal.d.ts.map +1 -1
  65. package/dist/cjs/utils/createSafeFunction.d.ts +2 -0
  66. package/dist/cjs/utils/createSafeFunction.d.ts.map +1 -0
  67. package/dist/cjs/utils/createSafeFunction.js +15 -0
  68. package/dist/cjs/utils/createSafeFunction.js.map +1 -0
  69. package/dist/cjs/utils/timestampToIsoString.d.ts +3 -0
  70. package/dist/cjs/utils/timestampToIsoString.d.ts.map +1 -0
  71. package/dist/cjs/utils/timestampToIsoString.js +16 -0
  72. package/dist/cjs/utils/timestampToIsoString.js.map +1 -0
  73. package/dist/cjs/utils/warnOnce.d.ts +2 -0
  74. package/dist/cjs/utils/warnOnce.d.ts.map +1 -1
  75. package/dist/cjs/utils/warnOnce.js +13 -1
  76. package/dist/cjs/utils/warnOnce.js.map +1 -1
  77. package/dist/cjs/version.d.ts +1 -1
  78. package/dist/cjs/version.js +1 -1
  79. package/dist/cjs/websocket/PerpsWsClient.d.ts +2 -0
  80. package/dist/cjs/websocket/PerpsWsClient.d.ts.map +1 -1
  81. package/dist/cjs/websocket/PerpsWsClient.js +14 -1
  82. package/dist/cjs/websocket/PerpsWsClient.js.map +1 -1
  83. package/dist/cjs/websocket/wsLog.d.ts +2 -0
  84. package/dist/cjs/websocket/wsLog.d.ts.map +1 -1
  85. package/dist/cjs/websocket/wsLog.js +8 -0
  86. package/dist/cjs/websocket/wsLog.js.map +1 -1
  87. package/dist/cjs/wire/orderAmounts.d.ts.map +1 -1
  88. package/dist/cjs/wire/orderAmounts.js +6 -9
  89. package/dist/cjs/wire/orderAmounts.js.map +1 -1
  90. package/dist/cjs/wire/refuel.d.ts +4 -1
  91. package/dist/cjs/wire/refuel.d.ts.map +1 -1
  92. package/dist/cjs/wire/refuel.js +23 -6
  93. package/dist/cjs/wire/refuel.js.map +1 -1
  94. package/dist/cjs/wire/transferable.js +3 -3
  95. package/dist/cjs/wire/transferable.js.map +1 -1
  96. package/dist/cjs/wire/withdraw.d.ts.map +1 -1
  97. package/dist/cjs/wire/withdraw.js +3 -3
  98. package/dist/cjs/wire/withdraw.js.map +1 -1
  99. package/dist/esm/client/PerpsClient.d.ts +17 -4
  100. package/dist/esm/client/PerpsClient.d.ts.map +1 -1
  101. package/dist/esm/client/PerpsClient.js +30 -8
  102. package/dist/esm/client/PerpsClient.js.map +1 -1
  103. package/dist/esm/decimal/arithmetic.d.ts +84 -0
  104. package/dist/esm/decimal/arithmetic.d.ts.map +1 -0
  105. package/dist/esm/decimal/arithmetic.js +110 -0
  106. package/dist/esm/decimal/arithmetic.js.map +1 -0
  107. package/dist/esm/decimal/big.d.ts +0 -2
  108. package/dist/esm/decimal/big.d.ts.map +1 -1
  109. package/dist/esm/decimal/big.js +0 -4
  110. package/dist/esm/decimal/big.js.map +1 -1
  111. package/dist/esm/decimal/compare.d.ts +45 -0
  112. package/dist/esm/decimal/compare.d.ts.map +1 -0
  113. package/dist/esm/decimal/compare.js +53 -0
  114. package/dist/esm/decimal/compare.js.map +1 -0
  115. package/dist/esm/decimal/convert.d.ts +62 -26
  116. package/dist/esm/decimal/convert.d.ts.map +1 -1
  117. package/dist/esm/decimal/convert.js +76 -25
  118. package/dist/esm/decimal/convert.js.map +1 -1
  119. package/dist/esm/decimal/decimalStringToBig.d.ts +26 -0
  120. package/dist/esm/decimal/decimalStringToBig.d.ts.map +1 -0
  121. package/dist/esm/decimal/decimalStringToBig.js +40 -0
  122. package/dist/esm/decimal/decimalStringToBig.js.map +1 -0
  123. package/dist/esm/decimal/format.d.ts +4 -1
  124. package/dist/esm/decimal/format.d.ts.map +1 -1
  125. package/dist/esm/decimal/format.js +71 -66
  126. package/dist/esm/decimal/format.js.map +1 -1
  127. package/dist/esm/decimal/parse.d.ts +26 -7
  128. package/dist/esm/decimal/parse.d.ts.map +1 -1
  129. package/dist/esm/decimal/parse.js +59 -33
  130. package/dist/esm/decimal/parse.js.map +1 -1
  131. package/dist/esm/index.d.ts +13 -10
  132. package/dist/esm/index.d.ts.map +1 -1
  133. package/dist/esm/index.js +9 -6
  134. package/dist/esm/index.js.map +1 -1
  135. package/dist/esm/math/fills.d.ts +6 -3
  136. package/dist/esm/math/fills.d.ts.map +1 -1
  137. package/dist/esm/math/fills.js +31 -16
  138. package/dist/esm/math/fills.js.map +1 -1
  139. package/dist/esm/math/order.d.ts +85 -65
  140. package/dist/esm/math/order.d.ts.map +1 -1
  141. package/dist/esm/math/order.js +158 -166
  142. package/dist/esm/math/order.js.map +1 -1
  143. package/dist/esm/math/position.d.ts +155 -98
  144. package/dist/esm/math/position.d.ts.map +1 -1
  145. package/dist/esm/math/position.js +157 -155
  146. package/dist/esm/math/position.js.map +1 -1
  147. package/dist/esm/services/getGasRecommendation.js +1 -1
  148. package/dist/esm/services/getGasRecommendation.js.map +1 -1
  149. package/dist/esm/services/getQuote.d.ts +2 -2
  150. package/dist/esm/services/getQuote.js +1 -1
  151. package/dist/esm/services/resolveSubscribeQuote.d.ts +1 -1
  152. package/dist/esm/services/resolveSubscribeQuote.d.ts.map +1 -1
  153. package/dist/esm/services/resolveSubscribeQuote.js +4 -3
  154. package/dist/esm/services/resolveSubscribeQuote.js.map +1 -1
  155. package/dist/esm/transport/request.d.ts +5 -0
  156. package/dist/esm/transport/request.d.ts.map +1 -1
  157. package/dist/esm/transport/request.js +2 -2
  158. package/dist/esm/transport/request.js.map +1 -1
  159. package/dist/esm/types/api.d.ts +9 -0
  160. package/dist/esm/types/api.d.ts.map +1 -1
  161. package/dist/esm/types/provider.d.ts +26 -13
  162. package/dist/esm/types/provider.d.ts.map +1 -1
  163. package/dist/esm/types/withdrawal.d.ts +32 -7
  164. package/dist/esm/types/withdrawal.d.ts.map +1 -1
  165. package/dist/esm/utils/createSafeFunction.d.ts +9 -0
  166. package/dist/esm/utils/createSafeFunction.d.ts.map +1 -0
  167. package/dist/esm/utils/createSafeFunction.js +19 -0
  168. package/dist/esm/utils/createSafeFunction.js.map +1 -0
  169. package/dist/esm/utils/timestampToIsoString.d.ts +15 -0
  170. package/dist/esm/utils/timestampToIsoString.d.ts.map +1 -0
  171. package/dist/esm/utils/timestampToIsoString.js +24 -0
  172. package/dist/esm/utils/timestampToIsoString.js.map +1 -0
  173. package/dist/esm/utils/warnOnce.d.ts +16 -0
  174. package/dist/esm/utils/warnOnce.d.ts.map +1 -1
  175. package/dist/esm/utils/warnOnce.js +24 -0
  176. package/dist/esm/utils/warnOnce.js.map +1 -1
  177. package/dist/esm/version.d.ts +1 -1
  178. package/dist/esm/version.js +1 -1
  179. package/dist/esm/websocket/PerpsWsClient.d.ts +7 -0
  180. package/dist/esm/websocket/PerpsWsClient.d.ts.map +1 -1
  181. package/dist/esm/websocket/PerpsWsClient.js +20 -2
  182. package/dist/esm/websocket/PerpsWsClient.js.map +1 -1
  183. package/dist/esm/websocket/wsLog.d.ts +12 -0
  184. package/dist/esm/websocket/wsLog.d.ts.map +1 -1
  185. package/dist/esm/websocket/wsLog.js +18 -0
  186. package/dist/esm/websocket/wsLog.js.map +1 -1
  187. package/dist/esm/wire/orderAmounts.d.ts.map +1 -1
  188. package/dist/esm/wire/orderAmounts.js +2 -2
  189. package/dist/esm/wire/orderAmounts.js.map +1 -1
  190. package/dist/esm/wire/refuel.d.ts +24 -14
  191. package/dist/esm/wire/refuel.d.ts.map +1 -1
  192. package/dist/esm/wire/refuel.js +38 -17
  193. package/dist/esm/wire/refuel.js.map +1 -1
  194. package/dist/esm/wire/transferable.js +3 -3
  195. package/dist/esm/wire/transferable.js.map +1 -1
  196. package/dist/esm/wire/withdraw.d.ts.map +1 -1
  197. package/dist/esm/wire/withdraw.js +3 -3
  198. package/dist/esm/wire/withdraw.js.map +1 -1
  199. package/dist/types/client/PerpsClient.d.ts +17 -4
  200. package/dist/types/client/PerpsClient.d.ts.map +1 -1
  201. package/dist/types/decimal/arithmetic.d.ts +84 -0
  202. package/dist/types/decimal/arithmetic.d.ts.map +1 -0
  203. package/dist/types/decimal/big.d.ts +0 -2
  204. package/dist/types/decimal/big.d.ts.map +1 -1
  205. package/dist/types/decimal/compare.d.ts +45 -0
  206. package/dist/types/decimal/compare.d.ts.map +1 -0
  207. package/dist/types/decimal/convert.d.ts +62 -26
  208. package/dist/types/decimal/convert.d.ts.map +1 -1
  209. package/dist/types/decimal/decimalStringToBig.d.ts +26 -0
  210. package/dist/types/decimal/decimalStringToBig.d.ts.map +1 -0
  211. package/dist/types/decimal/format.d.ts +4 -1
  212. package/dist/types/decimal/format.d.ts.map +1 -1
  213. package/dist/types/decimal/parse.d.ts +26 -7
  214. package/dist/types/decimal/parse.d.ts.map +1 -1
  215. package/dist/types/index.d.ts +13 -10
  216. package/dist/types/index.d.ts.map +1 -1
  217. package/dist/types/math/fills.d.ts +6 -3
  218. package/dist/types/math/fills.d.ts.map +1 -1
  219. package/dist/types/math/order.d.ts +85 -65
  220. package/dist/types/math/order.d.ts.map +1 -1
  221. package/dist/types/math/position.d.ts +155 -98
  222. package/dist/types/math/position.d.ts.map +1 -1
  223. package/dist/types/services/getQuote.d.ts +2 -2
  224. package/dist/types/services/resolveSubscribeQuote.d.ts +1 -1
  225. package/dist/types/services/resolveSubscribeQuote.d.ts.map +1 -1
  226. package/dist/types/transport/request.d.ts +5 -0
  227. package/dist/types/transport/request.d.ts.map +1 -1
  228. package/dist/types/types/api.d.ts +9 -0
  229. package/dist/types/types/api.d.ts.map +1 -1
  230. package/dist/types/types/provider.d.ts +26 -13
  231. package/dist/types/types/provider.d.ts.map +1 -1
  232. package/dist/types/types/withdrawal.d.ts +32 -7
  233. package/dist/types/types/withdrawal.d.ts.map +1 -1
  234. package/dist/types/utils/createSafeFunction.d.ts +9 -0
  235. package/dist/types/utils/createSafeFunction.d.ts.map +1 -0
  236. package/dist/types/utils/timestampToIsoString.d.ts +15 -0
  237. package/dist/types/utils/timestampToIsoString.d.ts.map +1 -0
  238. package/dist/types/utils/warnOnce.d.ts +16 -0
  239. package/dist/types/utils/warnOnce.d.ts.map +1 -1
  240. package/dist/types/version.d.ts +1 -1
  241. package/dist/types/websocket/PerpsWsClient.d.ts +7 -0
  242. package/dist/types/websocket/PerpsWsClient.d.ts.map +1 -1
  243. package/dist/types/websocket/wsLog.d.ts +12 -0
  244. package/dist/types/websocket/wsLog.d.ts.map +1 -1
  245. package/dist/types/wire/orderAmounts.d.ts.map +1 -1
  246. package/dist/types/wire/refuel.d.ts +24 -14
  247. package/dist/types/wire/refuel.d.ts.map +1 -1
  248. package/dist/types/wire/withdraw.d.ts.map +1 -1
  249. package/package.json +2 -2
  250. package/src/client/PerpsClient.ts +48 -10
  251. package/src/decimal/arithmetic.ts +144 -0
  252. package/src/decimal/big.ts +0 -5
  253. package/src/decimal/compare.ts +67 -0
  254. package/src/decimal/convert.ts +123 -44
  255. package/src/decimal/decimalStringToBig.ts +47 -0
  256. package/src/decimal/format.ts +81 -68
  257. package/src/decimal/parse.ts +73 -36
  258. package/src/index.ts +76 -8
  259. package/src/math/fills.ts +32 -17
  260. package/src/math/order.ts +261 -216
  261. package/src/math/position.ts +277 -194
  262. package/src/services/getGasRecommendation.ts +1 -1
  263. package/src/services/getQuote.ts +2 -2
  264. package/src/services/resolveSubscribeQuote.ts +4 -3
  265. package/src/transport/request.ts +7 -2
  266. package/src/types/api.ts +10 -0
  267. package/src/types/provider.ts +35 -13
  268. package/src/types/withdrawal.ts +35 -8
  269. package/src/utils/createSafeFunction.ts +20 -0
  270. package/src/utils/timestampToIsoString.ts +31 -0
  271. package/src/utils/warnOnce.ts +44 -0
  272. package/src/version.ts +1 -1
  273. package/src/websocket/PerpsWsClient.ts +25 -2
  274. package/src/websocket/wsLog.ts +31 -0
  275. package/src/wire/orderAmounts.ts +3 -2
  276. package/src/wire/refuel.ts +56 -20
  277. package/src/wire/transferable.ts +3 -3
  278. package/src/wire/withdraw.ts +3 -5
  279. package/dist/cjs/decimal/requireDecimal.d.ts +0 -4
  280. package/dist/cjs/decimal/requireDecimal.d.ts.map +0 -1
  281. package/dist/cjs/decimal/requireDecimal.js +0 -17
  282. package/dist/cjs/decimal/requireDecimal.js.map +0 -1
  283. package/dist/esm/decimal/requireDecimal.d.ts +0 -11
  284. package/dist/esm/decimal/requireDecimal.d.ts.map +0 -1
  285. package/dist/esm/decimal/requireDecimal.js +0 -18
  286. package/dist/esm/decimal/requireDecimal.js.map +0 -1
  287. package/dist/types/decimal/requireDecimal.d.ts +0 -11
  288. package/dist/types/decimal/requireDecimal.d.ts.map +0 -1
  289. package/src/decimal/requireDecimal.ts +0 -21
@@ -1,44 +1,92 @@
1
- import { type DecimalString, PerpsErrorCode } from '@lifi/perps-types'
1
+ import { PerpsErrorCode } from '@lifi/perps-types'
2
2
  import Big from 'big.js'
3
3
  import { formatUnits } from 'viem'
4
4
  import { PerpsError } from '../errors/PerpsError.js'
5
- import { requireDecimal } from './requireDecimal.js'
5
+ import { createSafeFunction } from '../utils/createSafeFunction.js'
6
+ import { bigToDecimalString, decimalStringToBig } from './decimalStringToBig.js'
7
+ import { isDecimalString } from './parse.js'
6
8
 
7
9
  /**
8
- * Off-grid resolution for {@link decimalToBaseUnits}:
9
- * - `truncate` — toward zero, for sizes and collateral amounts. The wire
10
- * value never exceeds the caller's intent.
11
- * - `round` — half away from zero, for prices, which snap to the nearest
12
- * tick.
10
+ * Off-grid resolution for {@link roundDecimalString}:
11
+ * - `truncate`: toward zero, for sizes and collateral amounts. The result
12
+ * never exceeds the caller's intent.
13
+ * - `round`: half away from zero, for prices, which snap to the nearest tick.
14
+ * - `up`: away from zero, for an amount that must cover a target.
13
15
  * @public
14
16
  */
15
- export type BaseUnitsRounding = 'truncate' | 'round'
17
+ export type DecimalRounding = 'truncate' | 'round' | 'up'
18
+
19
+ const BIG_ROUNDING = {
20
+ truncate: Big.roundDown,
21
+ round: Big.roundHalfUp,
22
+ up: Big.roundUp,
23
+ } as const
16
24
 
17
25
  /**
18
- * Scale a decimal string to a scaled integer in exact decimal arithmetic — an
19
- * on-grid input maps to its exact scaled integer with no binary float
20
- * artifacts (`'0.29'` at 2 decimals is 29, never 28). Off-grid input resolves
21
- * per `rounding`; there is no silent default.
26
+ * Round a decimal string to `decimals` places with exact decimal arithmetic.
27
+ * The result has no trailing zeros: `roundDecimalString('1.50', 1, 'round')`
28
+ * is `'1.5'`.
22
29
  *
23
- * @throws {PerpsError} `ValidationError` when `value` is not a
24
- * {@link DecimalString}, `decimals` is not a non-negative integer, or the scaled
25
- * result's magnitude exceeds `Number.MAX_SAFE_INTEGER`.
30
+ * @throws {PerpsError} `ValidationError` when `value` is not a decimal string,
31
+ * or `decimals` is not a non-negative integer.
26
32
  * @public
27
33
  */
28
- export const decimalToBaseUnits = (
29
- value: DecimalString,
34
+ export function roundDecimalString(
35
+ value: string,
30
36
  decimals: number,
31
- rounding: BaseUnitsRounding
32
- ): number => {
37
+ rounding: DecimalRounding
38
+ ): string {
39
+ if (!Number.isInteger(decimals) || decimals < 0) {
40
+ throw new PerpsError(
41
+ PerpsErrorCode.ValidationError,
42
+ `Invalid decimals for rounding: ${decimals}`
43
+ )
44
+ }
45
+ return bigToDecimalString(
46
+ decimalStringToBig(value).round(decimals, BIG_ROUNDING[rounding])
47
+ )
48
+ }
49
+
50
+ /**
51
+ * {@link roundDecimalString}, or `undefined` with a warning when it throws.
52
+ *
53
+ * @public
54
+ */
55
+ export const safeRoundDecimalString = createSafeFunction(
56
+ 'roundDecimalString',
57
+ roundDecimalString
58
+ )
59
+
60
+ /**
61
+ * Scale a decimal string to an integer at `decimals` places in exact decimal
62
+ * arithmetic: `'0.29'` at 2 decimals is 29, never 28. Off-grid input resolves
63
+ * per `rounding`. No `$`, `%` or `,` is removed first.
64
+ *
65
+ * @throws {PerpsError} `ValidationError` when `value` is not a decimal string,
66
+ * `decimals` is not a non-negative integer, or the result's magnitude
67
+ * exceeds `Number.MAX_SAFE_INTEGER`.
68
+ * @public
69
+ */
70
+ export function decimalStringToScaledInteger(
71
+ value: string,
72
+ decimals: number,
73
+ rounding: DecimalRounding
74
+ ): number {
75
+ if (!isDecimalString(value)) {
76
+ throw new PerpsError(
77
+ PerpsErrorCode.ValidationError,
78
+ `'${value}' is not a decimal string.`
79
+ )
80
+ }
33
81
  if (!Number.isInteger(decimals) || decimals < 0) {
34
82
  throw new PerpsError(
35
83
  PerpsErrorCode.ValidationError,
36
84
  `Invalid decimals for integer scaling: ${decimals}`
37
85
  )
38
86
  }
39
- const scaled = requireDecimal(value, 'decimalToBaseUnits(value)')
87
+ const scaled = new Big(value)
40
88
  .times(new Big(10).pow(decimals))
41
- .round(0, rounding === 'truncate' ? Big.roundDown : Big.roundHalfUp)
89
+ .round(0, BIG_ROUNDING[rounding])
42
90
  if (scaled.abs().gt(Number.MAX_SAFE_INTEGER)) {
43
91
  throw new PerpsError(
44
92
  PerpsErrorCode.ValidationError,
@@ -48,40 +96,57 @@ export const decimalToBaseUnits = (
48
96
  return scaled.toNumber()
49
97
  }
50
98
 
51
- const BASE_UNITS_PATTERN = /^-?\d+$/
99
+ /**
100
+ * {@link decimalStringToScaledInteger}, or `undefined` with a warning when it
101
+ * throws.
102
+ *
103
+ * @public
104
+ */
105
+ export const safeDecimalStringToScaledInteger = createSafeFunction(
106
+ 'decimalStringToScaledInteger',
107
+ decimalStringToScaledInteger
108
+ )
109
+
110
+ export const BASE_UNITS_PATTERN = /^-?\d+$/
52
111
 
53
112
  /**
54
- * Convert a base-unit amount (integer string) to a decimal string.
113
+ * Convert a scaled integer string (a token amount in base units) to a decimal
114
+ * string: `scaledIntegerToDecimalString('1000000', 6)` is `'1'`.
55
115
  *
56
- * @param amount - Amount in base units (e.g. "1000000" for 1 USDC)
57
- * @param decimals - Token decimals (e.g. 6 for USDC)
58
116
  * @throws {PerpsError} `ValidationError` when `amount` is not an integer
59
117
  * string, or `decimals` is not a non-negative integer.
60
- * @example
61
- * ```ts
62
- * baseUnitsToDecimal('1000000', 6) // '1'
63
- * ```
64
118
  * @public
65
119
  */
66
- export function baseUnitsToDecimal(
67
- amount: DecimalString,
120
+ export function scaledIntegerToDecimalString(
121
+ amount: string,
68
122
  decimals: number
69
- ): DecimalString {
123
+ ): string {
70
124
  if (!Number.isInteger(decimals) || decimals < 0) {
71
125
  throw new PerpsError(
72
126
  PerpsErrorCode.ValidationError,
73
- `Invalid \`baseUnitsToDecimal(decimals)\`: ${decimals} is not a non-negative integer.`
127
+ `Invalid \`scaledIntegerToDecimalString(decimals)\`: ${decimals} is not a non-negative integer.`
74
128
  )
75
129
  }
76
130
  if (!BASE_UNITS_PATTERN.test(amount)) {
77
131
  throw new PerpsError(
78
132
  PerpsErrorCode.ValidationError,
79
- `Invalid \`baseUnitsToDecimal(amount)\`: '${amount}' is not an integer base-unit string.`
133
+ `Invalid \`scaledIntegerToDecimalString(amount)\`: '${amount}' is not an integer base-unit string.`
80
134
  )
81
135
  }
82
136
  return formatUnits(BigInt(amount), decimals)
83
137
  }
84
138
 
139
+ /**
140
+ * {@link scaledIntegerToDecimalString}, or `undefined` with a warning when it
141
+ * throws.
142
+ *
143
+ * @public
144
+ */
145
+ export const safeScaledIntegerToDecimalString = createSafeFunction(
146
+ 'scaledIntegerToDecimalString',
147
+ scaledIntegerToDecimalString
148
+ )
149
+
85
150
  /**
86
151
  * Round `value` down to `decimals` and pad the result to exactly that many
87
152
  * decimal places. The padding is the point: this seeds a fixed-decimal input,
@@ -94,27 +159,31 @@ export function baseUnitsToDecimal(
94
159
  * `'0.010000000000000000'`, which `Number#toFixed` cannot.
95
160
  *
96
161
  * @throws {PerpsError} `ValidationError` when `value` is not a
97
- * {@link DecimalString}, or `decimals` is not a non-negative integer.
162
+ * decimal string, or `decimals` is not a non-negative integer.
98
163
  * @public
99
164
  */
100
- export function truncateDecimal(
101
- value: DecimalString,
102
- decimals: number
103
- ): DecimalString {
165
+ export function truncateDecimal(value: string, decimals: number): string {
104
166
  if (!Number.isInteger(decimals) || decimals < 0) {
105
167
  throw new PerpsError(
106
168
  PerpsErrorCode.ValidationError,
107
169
  `Invalid decimals for truncation: ${decimals}`
108
170
  )
109
171
  }
110
- const truncated = requireDecimal(value, 'truncateDecimal(value)').round(
111
- decimals,
112
- Big.roundDown
113
- )
172
+ const truncated = decimalStringToBig(value).round(decimals, Big.roundDown)
114
173
  // big.js carries the sign through a round to zero; '-0.00' is not a spelling.
115
174
  return (truncated.eq(0) ? new Big(0) : truncated).toFixed(decimals)
116
175
  }
117
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
+
118
187
  /**
119
188
  * Spell a `number` as a {@link DecimalString} in plain notation: `1e-7`
120
189
  * becomes `'0.0000001'`, never `'1e-7'`. The boundary helper for a venue or
@@ -125,7 +194,7 @@ export function truncateDecimal(
125
194
  * @throws {PerpsError} `ValidationError` when `value` is `NaN` or infinite.
126
195
  * @public
127
196
  */
128
- export function numberToDecimalString(value: number): DecimalString {
197
+ export function numberToDecimalString(value: number): string {
129
198
  if (!Number.isFinite(value)) {
130
199
  throw new PerpsError(
131
200
  PerpsErrorCode.ValidationError,
@@ -135,3 +204,13 @@ export function numberToDecimalString(value: number): DecimalString {
135
204
  const parsed = new Big(value)
136
205
  return (parsed.eq(0) ? new Big(0) : parsed).toFixed()
137
206
  }
207
+
208
+ /**
209
+ * {@link numberToDecimalString}, or `undefined` with a warning when it throws.
210
+ *
211
+ * @public
212
+ */
213
+ export const safeNumberToDecimalString = createSafeFunction(
214
+ 'numberToDecimalString',
215
+ numberToDecimalString
216
+ )
@@ -0,0 +1,47 @@
1
+ import { DECIMAL_PATTERN, PerpsErrorCode } from '@lifi/perps-types'
2
+ import Big from 'big.js'
3
+ import { PerpsError } from '../errors/PerpsError.js'
4
+ import { DivBig } from './big.js'
5
+
6
+ const DISPLAY_CHARACTERS = /[\s$%,]/g
7
+
8
+ /**
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.
12
+ *
13
+ * @throws {PerpsError} `ValidationError` when `value` is not a string or the
14
+ * cleaned string is not a decimal string.
15
+ * @internal
16
+ */
17
+ export function decimalStringToBig(value: string): Big {
18
+ const cleaned =
19
+ typeof value === 'string' ? value.replace(DISPLAY_CHARACTERS, '') : ''
20
+ if (!DECIMAL_PATTERN.test(cleaned)) {
21
+ throw new PerpsError(
22
+ PerpsErrorCode.ValidationError,
23
+ `'${String(value)}' is not a decimal string.`
24
+ )
25
+ }
26
+ return new Big(cleaned)
27
+ }
28
+
29
+ /**
30
+ * Spell a `Big` as a canonical decimal string, with no exponent and no `-0`.
31
+ *
32
+ * @internal
33
+ */
34
+ export function bigToDecimalString(value: Big): string {
35
+ return value.eq(0) ? '0' : value.toFixed()
36
+ }
37
+
38
+ /**
39
+ * {@link decimalStringToBig} as a `DivBig`, so a later `div` keeps 40 decimal
40
+ * places.
41
+ *
42
+ * @throws {PerpsError} `ValidationError` as {@link decimalStringToBig}.
43
+ * @internal
44
+ */
45
+ export function decimalStringToDivBig(value: string): Big {
46
+ return new DivBig(decimalStringToBig(value))
47
+ }
@@ -1,4 +1,6 @@
1
1
  import type { DecimalString } from '@lifi/perps-types'
2
+ import Big from 'big.js'
3
+ import { decimalStringToBig } from './decimalStringToBig.js'
2
4
 
3
5
  /**
4
6
  * Options shared by the human-facing display formatters.
@@ -8,7 +10,10 @@ import type { DecimalString } from '@lifi/perps-types'
8
10
  export interface FormatOptions {
9
11
  /** Fixed number of decimal places. Defaults are per-formatter. */
10
12
  decimals?: number
11
- /** Rendered when the value is null/undefined/NaN/non-finite. Defaults to `'—'`. */
13
+ /**
14
+ * Rendered when the value is null, undefined, blank or a non-finite number.
15
+ * A string that is not a decimal is shown unchanged. Defaults to `'—'`.
16
+ */
12
17
  placeholder?: string
13
18
  /** BCP 47 locale controlling digit grouping and separators (e.g. `'en-US'`). */
14
19
  locale?: string
@@ -33,72 +38,62 @@ const DEFAULT_PLACEHOLDER = '—'
33
38
  type FormatInput = number | DecimalString | string | null | undefined
34
39
 
35
40
  /**
36
- * Extra decimal places used to normalise IEEE-754 representation noise before
37
- * truncating. `toFixed` at this higher precision cleans values like
38
- * `0.28999999…` back to `0.29`; truncation then chops the guard digits.
39
- */
40
- const FLOAT_GUARD_DIGITS = 9
41
-
42
- /**
43
- * Tolerantly coerce a display input to a finite number.
44
- *
45
- * Strips `$`, `,`, and surrounding whitespace from strings. Returns `null`
46
- * when the value is null/undefined/blank or does not resolve to a finite
47
- * number, so callers can render a placeholder instead of `$0.00` or `$NaN`.
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.
48
44
  */
49
- function toFiniteNumber(value: FormatInput): number | null {
50
- let n: number
51
- if (typeof value === 'string') {
52
- const cleaned = value.replace(/[$,\s]/g, '')
53
- if (!cleaned) {
54
- return null
55
- }
56
- n = Number(cleaned)
57
- } else if (typeof value === 'number') {
58
- n = value
59
- } else {
45
+ function toBig(value: FormatInput): Big | null | string {
46
+ if (typeof value === 'number') {
47
+ return Number.isFinite(value) ? new Big(value) : null
48
+ }
49
+ if (typeof value !== 'string' || value.trim() === '') {
60
50
  return null
61
51
  }
62
- return Number.isFinite(n) ? n : null
52
+ try {
53
+ return decimalStringToBig(value)
54
+ } catch {
55
+ return value
56
+ }
63
57
  }
64
58
 
65
- /**
66
- * Truncate a non-negative value toward zero to `decimals` places using its
67
- * decimal string, never float scaling. Representation noise is first
68
- * normalised by `toFixed` at {@link FLOAT_GUARD_DIGITS} extra precision, then
69
- * the guard digits are chopped: `99.999` at 2dp → `99.99`, `0.29` → `0.29`.
70
- */
71
- function truncateAbs(abs: number, decimals: number): number {
72
- const s = abs.toFixed(Math.min(decimals + FLOAT_GUARD_DIGITS, 100))
73
- const dot = s.indexOf('.')
74
- const cut = decimals === 0 ? dot : dot + 1 + decimals
75
- return Number(s.slice(0, cut))
59
+ function decimalSeparator(locale: string | undefined): string {
60
+ return (
61
+ new Intl.NumberFormat(locale)
62
+ .formatToParts(1.5)
63
+ .find((part) => part.type === 'decimal')?.value ?? '.'
64
+ )
76
65
  }
77
66
 
78
67
  /**
79
- * Reduce `n` to `decimals` places then split into a sign and the
68
+ * Reduce `value` to `decimals` places then split into a sign and the
80
69
  * locale-formatted absolute body. The sign is derived from the reduced value
81
70
  * so magnitudes that collapse to zero render without a spurious `-`/`+`.
82
71
  * `signed` emits `+` for positives; `rounding` selects half-up or truncation.
83
72
  */
84
73
  function signAndBody(
85
- n: number,
74
+ value: Big,
86
75
  decimals: number,
87
76
  locale: string | undefined,
88
77
  grouping: boolean,
89
78
  rounding: RoundingMode,
90
79
  signed: boolean
91
80
  ): { sign: '+' | '-' | ''; body: string } {
92
- const reduced =
93
- rounding === 'floor'
94
- ? (n < 0 ? -1 : 1) * truncateAbs(Math.abs(n), decimals)
95
- : Number(n.toFixed(decimals))
96
- const sign = reduced < 0 ? '-' : signed && reduced > 0 ? '+' : ''
97
- const body = Math.abs(reduced).toLocaleString(locale, {
98
- minimumFractionDigits: decimals,
99
- maximumFractionDigits: decimals,
100
- useGrouping: grouping,
101
- })
81
+ const reduced = value.round(
82
+ decimals,
83
+ rounding === 'floor' ? Big.roundDown : Big.roundHalfUp
84
+ )
85
+ const sign = reduced.lt(0) ? '-' : signed && reduced.gt(0) ? '+' : ''
86
+ const [integerPart = '0', fractionPart] = reduced
87
+ .abs()
88
+ .toFixed(decimals)
89
+ .split('.')
90
+ const integerBody = grouping
91
+ ? BigInt(integerPart).toLocaleString(locale)
92
+ : integerPart
93
+ const body =
94
+ fractionPart === undefined
95
+ ? integerBody
96
+ : `${integerBody}${decimalSeparator(locale)}${fractionPart}`
102
97
  return { sign, body }
103
98
  }
104
99
 
@@ -106,14 +101,14 @@ function signAndBody(
106
101
  * Auto-detect a sensible number of decimal places from a value's magnitude:
107
102
  * `>=1` → 2, `>=0.1` → 4, `>=0.01` → 5, otherwise 6.
108
103
  */
109
- function autoDecimals(abs: number): number {
110
- if (abs >= 1) {
104
+ function autoDecimals(abs: Big): number {
105
+ if (abs.gte(1)) {
111
106
  return 2
112
107
  }
113
- if (abs >= 0.1) {
108
+ if (abs.gte('0.1')) {
114
109
  return 4
115
110
  }
116
- if (abs >= 0.01) {
111
+ if (abs.gte('0.01')) {
117
112
  return 5
118
113
  }
119
114
  return 6
@@ -141,10 +136,13 @@ export function formatNumber(
141
136
  locale,
142
137
  rounding = 'halfUp',
143
138
  } = options
144
- const n = toFiniteNumber(value)
139
+ const n = toBig(value)
145
140
  if (n === null) {
146
141
  return placeholder
147
142
  }
143
+ if (typeof n === 'string') {
144
+ return n
145
+ }
148
146
  const { sign, body } = signAndBody(n, decimals, locale, true, rounding, false)
149
147
  return `${sign}${body}`
150
148
  }
@@ -167,10 +165,13 @@ export function formatUsd(
167
165
  locale,
168
166
  rounding = 'halfUp',
169
167
  } = options
170
- const n = toFiniteNumber(value)
168
+ const n = toBig(value)
171
169
  if (n === null) {
172
170
  return placeholder
173
171
  }
172
+ if (typeof n === 'string') {
173
+ return n
174
+ }
174
175
  const { sign, body } = signAndBody(n, decimals, locale, true, rounding, false)
175
176
  return `${sign}$${body}`
176
177
  }
@@ -194,10 +195,13 @@ export function formatSignedUsd(
194
195
  locale,
195
196
  rounding = 'halfUp',
196
197
  } = options
197
- const n = toFiniteNumber(value)
198
+ const n = toBig(value)
198
199
  if (n === null) {
199
200
  return placeholder
200
201
  }
202
+ if (typeof n === 'string') {
203
+ return n
204
+ }
201
205
  const { sign, body } = signAndBody(n, decimals, locale, true, rounding, true)
202
206
  return `${sign}$${body}`
203
207
  }
@@ -221,10 +225,13 @@ export function formatSignedPercent(
221
225
  locale,
222
226
  rounding = 'halfUp',
223
227
  } = options
224
- const n = toFiniteNumber(value)
228
+ const n = toBig(value)
225
229
  if (n === null) {
226
230
  return placeholder
227
231
  }
232
+ if (typeof n === 'string') {
233
+ return n
234
+ }
228
235
  const { sign, body } = signAndBody(n, decimals, locale, false, rounding, true)
229
236
  return `${sign}${body}%`
230
237
  }
@@ -248,17 +255,20 @@ export function formatPrice(
248
255
  locale,
249
256
  rounding = 'halfUp',
250
257
  } = options
251
- const n = toFiniteNumber(value)
258
+ const n = toBig(value)
252
259
  if (n === null) {
253
260
  return placeholder
254
261
  }
255
- const abs = Math.abs(n)
262
+ if (typeof n === 'string') {
263
+ return n
264
+ }
265
+ const abs = n.abs()
256
266
  const decimals = options.decimals ?? autoDecimals(abs)
257
267
  const { sign, body } = signAndBody(
258
268
  n,
259
269
  decimals,
260
270
  locale,
261
- abs >= 1000,
271
+ abs.gte(1000),
262
272
  rounding,
263
273
  false
264
274
  )
@@ -279,20 +289,23 @@ export function formatCompactUsd(
279
289
  options: FormatOptions = {}
280
290
  ): string {
281
291
  const { decimals = 2, placeholder = DEFAULT_PLACEHOLDER } = options
282
- const n = toFiniteNumber(value)
292
+ const n = toBig(value)
283
293
  if (n === null) {
284
294
  return placeholder
285
295
  }
286
- const sign = n < 0 ? '-' : ''
287
- const abs = Math.abs(n)
288
- if (abs >= 1_000_000_000) {
289
- return `${sign}$${(abs / 1_000_000_000).toFixed(decimals)}B`
296
+ if (typeof n === 'string') {
297
+ return n
298
+ }
299
+ const sign = n.lt(0) ? '-' : ''
300
+ const abs = n.abs()
301
+ if (abs.gte(1_000_000_000)) {
302
+ return `${sign}$${abs.div(1_000_000_000).toFixed(decimals)}B`
290
303
  }
291
- if (abs >= 1_000_000) {
292
- return `${sign}$${(abs / 1_000_000).toFixed(decimals)}M`
304
+ if (abs.gte(1_000_000)) {
305
+ return `${sign}$${abs.div(1_000_000).toFixed(decimals)}M`
293
306
  }
294
- if (abs >= 1_000) {
295
- return `${sign}$${(abs / 1_000).toFixed(decimals)}K`
307
+ if (abs.gte(1_000)) {
308
+ return `${sign}$${abs.div(1_000).toFixed(decimals)}K`
296
309
  }
297
310
  return `${sign}$${abs.toFixed(decimals)}`
298
311
  }