@provex/utils 1.0.16 → 1.2.4

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 (149) hide show
  1. package/dist/chain.d.ts +446 -0
  2. package/dist/chain.js +98 -0
  3. package/dist/chain.test.js +112 -0
  4. package/dist/contracts.d.ts +206 -0
  5. package/dist/contracts.js +292 -0
  6. package/dist/contracts.test.js +243 -0
  7. package/dist/conversionRates.d.ts +215 -0
  8. package/dist/{src/conversionRates.js → conversionRates.js} +74 -4
  9. package/dist/conversionRates.test.js +473 -0
  10. package/dist/currencies.d.ts +248 -0
  11. package/dist/{src/currencies.js → currencies.js} +119 -38
  12. package/dist/currencies.test.js +343 -0
  13. package/dist/data/countries.json +1099 -0
  14. package/dist/deposit.d.ts +69 -0
  15. package/dist/deposit.js +52 -0
  16. package/dist/depositKeys.d.ts +154 -0
  17. package/dist/depositKeys.js +104 -0
  18. package/dist/depositKeys.test.d.ts +1 -0
  19. package/dist/depositKeys.test.js +231 -0
  20. package/dist/events.d.ts +41 -0
  21. package/dist/events.js +92 -0
  22. package/dist/events.test.d.ts +1 -0
  23. package/dist/events.test.js +111 -0
  24. package/dist/fees.d.ts +136 -0
  25. package/dist/fees.js +178 -0
  26. package/dist/fees.test.d.ts +1 -0
  27. package/dist/fees.test.js +165 -0
  28. package/dist/ids.d.ts +175 -0
  29. package/dist/ids.js +231 -0
  30. package/dist/ids.test.d.ts +1 -0
  31. package/dist/ids.test.js +607 -0
  32. package/dist/index.d.ts +45 -0
  33. package/dist/index.js +45 -0
  34. package/dist/input.d.ts +26 -0
  35. package/dist/{src/input.js → input.js} +23 -4
  36. package/dist/intent.d.ts +50 -0
  37. package/dist/intent.js +28 -0
  38. package/dist/intent.test.d.ts +1 -0
  39. package/dist/intent.test.js +119 -0
  40. package/dist/json.d.ts +55 -0
  41. package/dist/json.js +80 -0
  42. package/dist/participants.d.ts +22 -0
  43. package/dist/participants.js +28 -0
  44. package/dist/payment.d.ts +481 -0
  45. package/dist/payment.js +1149 -0
  46. package/dist/payment.test.d.ts +1 -0
  47. package/dist/payment.test.js +272 -0
  48. package/dist/peer-types.d.ts +327 -0
  49. package/dist/peer-types.js +169 -0
  50. package/dist/profit.d.ts +49 -0
  51. package/dist/profit.js +57 -0
  52. package/dist/profit.test.d.ts +1 -0
  53. package/dist/profit.test.js +315 -0
  54. package/dist/reputation.d.ts +125 -0
  55. package/dist/reputation.js +188 -0
  56. package/dist/reputation.test.d.ts +1 -0
  57. package/dist/reputation.test.js +156 -0
  58. package/dist/sort.d.ts +75 -0
  59. package/dist/sort.js +70 -0
  60. package/dist/sort.test.d.ts +1 -0
  61. package/dist/sort.test.js +216 -0
  62. package/dist/stateKey.d.ts +35 -0
  63. package/dist/stateKey.js +36 -0
  64. package/dist/stats.d.ts +61 -0
  65. package/dist/stats.js +53 -0
  66. package/dist/stats.test.d.ts +1 -0
  67. package/dist/stats.test.js +104 -0
  68. package/dist/time.d.ts +104 -0
  69. package/dist/time.js +162 -0
  70. package/dist/time.test.d.ts +1 -0
  71. package/dist/time.test.js +231 -0
  72. package/dist/tokens.d.ts +15502 -0
  73. package/dist/{src/tokens.js → tokens.js} +98 -17
  74. package/dist/tokens.test.d.ts +1 -0
  75. package/dist/tokens.test.js +193 -0
  76. package/dist/units.d.ts +200 -0
  77. package/dist/units.js +279 -0
  78. package/dist/units.test.d.ts +1 -0
  79. package/dist/units.test.js +327 -0
  80. package/package.json +119 -29
  81. package/dist/scripts/convert-svg.js +0 -17
  82. package/dist/src/chain.d.ts +0 -14
  83. package/dist/src/chain.js +0 -18
  84. package/dist/src/contracts.d.ts +0 -30
  85. package/dist/src/contracts.js +0 -64
  86. package/dist/src/conversionRates.d.ts +0 -124
  87. package/dist/src/currencies.d.ts +0 -144
  88. package/dist/src/data/countries.json +0 -1099
  89. package/dist/src/deposit.d.ts +0 -1
  90. package/dist/src/events.d.ts +0 -3
  91. package/dist/src/events.js +0 -16
  92. package/dist/src/ids.d.ts +0 -72
  93. package/dist/src/ids.js +0 -81
  94. package/dist/src/index.d.ts +0 -16
  95. package/dist/src/index.js +0 -16
  96. package/dist/src/input.d.ts +0 -7
  97. package/dist/src/intent.d.ts +0 -14
  98. package/dist/src/json.d.ts +0 -18
  99. package/dist/src/json.js +0 -44
  100. package/dist/src/participants.d.ts +0 -7
  101. package/dist/src/participants.js +0 -13
  102. package/dist/src/payment.d.ts +0 -164
  103. package/dist/src/payment.js +0 -439
  104. package/dist/src/profit.d.ts +0 -28
  105. package/dist/src/profit.js +0 -34
  106. package/dist/src/sort.d.ts +0 -36
  107. package/dist/src/sort.js +0 -39
  108. package/dist/src/stats.d.ts +0 -7
  109. package/dist/src/stats.js +0 -8
  110. package/dist/src/time.d.ts +0 -54
  111. package/dist/src/time.js +0 -106
  112. package/dist/src/tokens.d.ts +0 -3675
  113. package/dist/src/units.d.ts +0 -17
  114. package/dist/src/units.js +0 -79
  115. package/dist/src/zkp2p-types.d.ts +0 -48
  116. package/dist/tsconfig.tsbuildinfo +0 -1
  117. package/scripts/convert-svg.ts +0 -22
  118. package/src/chain.ts +0 -27
  119. package/src/contracts.ts +0 -89
  120. package/src/conversionRates.ts +0 -356
  121. package/src/currencies.ts +0 -286
  122. package/src/data/countries.json +0 -1099
  123. package/src/deposit.ts +0 -2
  124. package/src/events.ts +0 -21
  125. package/src/flags/eur.svg +0 -4
  126. package/src/flags/usd.svg +0 -25
  127. package/src/flags.ts +0 -4
  128. package/src/global.d.ts +0 -9
  129. package/src/ids.ts +0 -137
  130. package/src/index.ts +0 -16
  131. package/src/input.ts +0 -35
  132. package/src/intent.ts +0 -15
  133. package/src/json.ts +0 -42
  134. package/src/participants.ts +0 -14
  135. package/src/payment.ts +0 -573
  136. package/src/profit.ts +0 -55
  137. package/src/sort.ts +0 -49
  138. package/src/stats.ts +0 -10
  139. package/src/time.ts +0 -123
  140. package/src/tokens.ts +0 -159
  141. package/src/units.ts +0 -120
  142. package/src/zkp2p-types.ts +0 -55
  143. package/tsconfig.json +0 -18
  144. /package/dist/{scripts/convert-svg.d.ts → chain.test.d.ts} +0 -0
  145. /package/dist/{src/deposit.js → contracts.test.d.ts} +0 -0
  146. /package/dist/{src/intent.js → conversionRates.test.d.ts} +0 -0
  147. /package/dist/{src/zkp2p-types.js → currencies.test.d.ts} +0 -0
  148. /package/dist/{src/flags.d.ts → flags.d.ts} +0 -0
  149. /package/dist/{src/flags.js → flags.js} +0 -0
package/dist/units.js ADDED
@@ -0,0 +1,279 @@
1
+ /**
2
+ * @fileoverview Unit conversion and formatting utilities for token amounts.
3
+ *
4
+ * This module provides utilities for converting between human-readable amounts
5
+ * and blockchain-native bigint values, with support for:
6
+ * - Decimal precision (6 for USDC, 18 for ETH)
7
+ * - Thousands separators for display
8
+ * - Truncation and padding options
9
+ * - Number abbreviation (k, M, B, T)
10
+ *
11
+ * @remarks
12
+ * All amounts in smart contracts are stored as bigints without decimals.
13
+ * For example, 100 USDC = 100_000000n (6 decimals).
14
+ *
15
+ * @see {@link tokens} for token decimal configurations
16
+ * @see {@link currencies} for fiat currency decimals
17
+ */
18
+ import { formatUnits as viemFormatUnits, parseUnits as viemParseUnits } from 'viem';
19
+ /**
20
+ * Constant representing 1 ether in wei (10^18).
21
+ * Used for 18-decimal precision calculations.
22
+ */
23
+ export const oneEther = 10n ** 18n;
24
+ /**
25
+ * Add thousands separators to a number string for improved readability.
26
+ *
27
+ * @param numberString - The number string to format
28
+ * @param delimiter - The separator character (default: ',')
29
+ * @returns Formatted string with separators
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * addThousandsSeparator('1234567.89') // '1,234,567.89'
34
+ * addThousandsSeparator('1000', ' ') // '1 000'
35
+ * ```
36
+ */
37
+ export function addThousandsSeparator(numberString, delimiter = ',') {
38
+ let [integerPart, fractionalPart] = numberString.split('.');
39
+ integerPart = integerPart || '0';
40
+ fractionalPart = fractionalPart || '';
41
+ // Add thousands separators to integer part
42
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, delimiter);
43
+ // Return with fractional part if it exists
44
+ return fractionalPart ? `${formattedInteger}.${fractionalPart}` : formattedInteger;
45
+ }
46
+ /**
47
+ * Parse a human-readable amount string into a bigint.
48
+ * Handles thousands separators and decimal places.
49
+ *
50
+ * @param amount - Human-readable amount (e.g., '1,234.56')
51
+ * @param options - Parsing options with decimals and optional delimiter
52
+ * @returns BigInt representation of the amount
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Parse USDC amount (6 decimals)
57
+ * parseUnits('100.50', { decimals: 6 }) // 100_500000n
58
+ *
59
+ * // Parse with thousands separator
60
+ * parseUnits('1,234.56', { decimals: 6 }) // 1_234_560000n
61
+ * ```
62
+ */
63
+ export const parseUnits = (amount, options) => {
64
+ return viemParseUnits(amount.split(options.delimiter ?? ',').join(''), options.decimals);
65
+ };
66
+ /**
67
+ * Format a bigint amount as a human-readable string with configurable precision.
68
+ *
69
+ * @param amount - BigInt amount to format
70
+ * @param options - Formatting options
71
+ * @returns Formatted string with decimal point and thousands separators
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * // Basic formatting
76
+ * formatUnits(100_500000n, { decimals: 6, padDecimals: 2 }) // '100.50'
77
+ *
78
+ * // With truncation
79
+ * formatUnits(100_123456n, { decimals: 6, truncateDecimals: 2 }) // '100.12'
80
+ *
81
+ * // With cutoff for small amounts
82
+ * formatUnits(100n, { decimals: 6, cutoff: 1000n }) // '<0.001'
83
+ * ```
84
+ */
85
+ export function formatUnits(amount, options) {
86
+ const { decimals, truncateDecimals, padDecimals = 0, delimiter = ',', cutoff, roundUp = false } = options;
87
+ if (cutoff && amount < cutoff) {
88
+ return `<${formatUnits(cutoff, { ...options, truncateDecimals: 0, cutoff: undefined })}`;
89
+ }
90
+ if (roundUp && truncateDecimals !== undefined) {
91
+ // const minimumDecimal = decimals - truncateDecimals
92
+ const minimumDecimal = BigInt(decimals - truncateDecimals);
93
+ const minimalMod = amount % (10n ** minimumDecimal);
94
+ const halfMinimalDecimal = (10n ** minimumDecimal) / 2n;
95
+ if (minimalMod >= halfMinimalDecimal) {
96
+ amount = amount + (10n ** minimumDecimal) - minimalMod;
97
+ }
98
+ }
99
+ const formatted = viemFormatUnits(amount, decimals);
100
+ const [integerPart, fractionalPart = ''] = formatted.split('.');
101
+ // If truncateDecimals is specified, limit decimal places
102
+ let finalFractionalPart = fractionalPart;
103
+ if (truncateDecimals !== undefined) {
104
+ finalFractionalPart = fractionalPart.slice(0, truncateDecimals);
105
+ }
106
+ // Always pad to at least 2 decimal places (or padDecimals)
107
+ if (finalFractionalPart.length < padDecimals) {
108
+ finalFractionalPart = finalFractionalPart.padEnd(padDecimals, '0');
109
+ }
110
+ // If there are no decimals, still show .00
111
+ const numberString = `${integerPart}.${finalFractionalPart}`;
112
+ return addThousandsSeparator(numberString, delimiter);
113
+ }
114
+ /**
115
+ * Format a token amount for display with 2 decimal places.
116
+ * Convenience wrapper around formatUnits with truncateDecimals: 2.
117
+ *
118
+ * @param amount - BigInt amount to format
119
+ * @param options - Formatting options (truncateDecimals defaults to 2)
120
+ * @returns Formatted string with 2 decimal places
121
+ */
122
+ export function formatTokenAmountForDisplay(amount, options) {
123
+ return formatUnits(amount, { truncateDecimals: 2, ...options });
124
+ }
125
+ /**
126
+ * Safely parse a string to an integer without using Number() constructor.
127
+ * Uses parseInt which is more predictable for integer-only parsing.
128
+ *
129
+ * @param value - String value to parse
130
+ * @returns Parsed integer or null if invalid
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * parseIntegerSafe('123') // 123
135
+ * parseIntegerSafe('123.45') // 123 (truncates decimal)
136
+ * parseIntegerSafe('abc') // null
137
+ * ```
138
+ */
139
+ export function parseIntegerSafe(value) {
140
+ const parsed = parseInt(value, 10);
141
+ return isNaN(parsed) ? null : parsed;
142
+ }
143
+ // // Get display title for limits (shows original user limits on hover)
144
+ // export function getLimitsDisplayTitle(deposit: { formattedMinAmount: string; formattedMaxAmount: string }): string {
145
+ // return `User set: ${deposit.formattedMinAmount} - ${deposit.formattedMaxAmount}`
146
+ // }
147
+ /**
148
+ * Convert a blockchain timestamp (Unix seconds) to a JavaScript Date object.
149
+ *
150
+ * @param timestamp - Unix timestamp in seconds (string, number, or bigint)
151
+ * @returns Date object, or null if timestamp is falsy
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * timestampToDate(1704067200) // Date: 2024-01-01T00:00:00.000Z
156
+ * timestampToDate('1704067200') // Date: 2024-01-01T00:00:00.000Z
157
+ * timestampToDate(1704067200n) // Date: 2024-01-01T00:00:00.000Z
158
+ * timestampToDate(null) // null
159
+ * ```
160
+ */
161
+ export function timestampToDate(timestamp) {
162
+ if (!timestamp)
163
+ return null;
164
+ // Convert seconds to milliseconds for Date constructor
165
+ const timestampMs = Number(timestamp) * 1000;
166
+ return new Date(timestampMs);
167
+ }
168
+ // export function formatTokenAmountForDisplay(amount: bigint, tokenInfo: TokenInfo): string {
169
+ // return formatUnits(amount, { decimals: tokenInfo.decimals })
170
+ // }
171
+ /**
172
+ * Format a token amount using the token's decimal configuration.
173
+ *
174
+ * @param token - Token info with decimals
175
+ * @param amount - BigInt amount to format, or null
176
+ * @param options - Optional formatting overrides
177
+ * @returns Formatted string, or null if amount is null
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const usdc = { decimals: 6, ... }
182
+ * formatAmount(usdc, 100_500000n) // '100.50'
183
+ * formatAmount(usdc, null) // null
184
+ * ```
185
+ */
186
+ export const formatAmount = (token, amount, options = {}) => {
187
+ if (!amount)
188
+ return null;
189
+ const baseDecimals = Math.min(token.decimals - 4, 8);
190
+ // Show 4 decimal places for single-digit amounts so small differences are visible
191
+ const integerDigits = amount / (10n ** BigInt(token.decimals));
192
+ const truncateDecimals = integerDigits < 10n ? Math.min(baseDecimals + 2, token.decimals) : baseDecimals;
193
+ return formatUnits(amount, {
194
+ decimals: token.decimals,
195
+ truncateDecimals,
196
+ padDecimals: 2,
197
+ ...options,
198
+ });
199
+ };
200
+ /**
201
+ * Numeric abbreviation suffixes and their divisors.
202
+ * Used by abbreviateNumber() for compact number display.
203
+ */
204
+ export const numericAbbreviations = [
205
+ { divisor: 1000000000000n, suffix: 'T' }, // Trillion
206
+ { divisor: 1000000000n, suffix: 'B' }, // Billion
207
+ { divisor: 1000000n, suffix: 'M' }, // Million
208
+ { divisor: 1000n, suffix: 'k' }, // Thousand
209
+ ];
210
+ /**
211
+ * Formats a bigint value with a specific numeric abbreviation suffix
212
+ * @param value - The bigint value to format
213
+ * @param options - Configuration for formatting
214
+ * @returns Formatted string with suffix (e.g., "1.23k", "400M")
215
+ *
216
+ * @example
217
+ * formatWithAbbreviation(1234567n, { decimals: 2, divisor: 1_000n, suffix: 'k' }) // "1234.57k"
218
+ * formatWithAbbreviation(400000000n, { decimals: 2, divisor: 1_000_000n, suffix: 'M' }) // "400M"
219
+ */
220
+ export function formatWithAbbreviation(value, options) {
221
+ const { decimals, divisor, suffix, maxDecimals = 2 } = options;
222
+ const isNegative = value < 0n;
223
+ const sign = isNegative ? '-' : '';
224
+ const absValue = isNegative ? -value : value;
225
+ // Convert to decimal first to preserve fractional parts, then divide
226
+ const valueAsDecimal = viemFormatUnits(absValue, decimals);
227
+ const divisorAsDecimal = Number(divisor);
228
+ const abbreviatedValue = parseFloat(valueAsDecimal) / divisorAsDecimal;
229
+ // Reduce decimal places for large abbreviated values unless caller set minDecimals
230
+ const minDec = options.minDecimals ?? 0;
231
+ let decimalPlaces = maxDecimals;
232
+ if (abbreviatedValue >= 100) {
233
+ decimalPlaces = Math.max(minDec, 0);
234
+ }
235
+ else if (abbreviatedValue >= 10) {
236
+ decimalPlaces = Math.max(minDec, Math.min(1, maxDecimals));
237
+ }
238
+ // Format with appropriate decimal places, removing trailing zeros after decimal point
239
+ let formatted = abbreviatedValue.toFixed(decimalPlaces);
240
+ // Only remove trailing zeros if there's a decimal point
241
+ if (formatted.includes('.')) {
242
+ formatted = formatted.replace(/\.?0+$/, '');
243
+ }
244
+ return `${sign}${formatted}${suffix}`;
245
+ }
246
+ /**
247
+ * Abbreviates large numbers using k, M, B, T suffixes
248
+ * @param value - The bigint value to abbreviate
249
+ * @param options - Optional configuration for decimal places and decimals precision
250
+ * @returns Abbreviated number string (e.g., "1.5k", "200k", "1.2M")
251
+ *
252
+ * @example
253
+ * abbreviateNumber(1500n, { decimals: 0 }) // "1.5k"
254
+ * abbreviateNumber(200000n, { decimals: 0 }) // "200k"
255
+ * abbreviateNumber(1234567n, { decimals: 0 }) // "1.23M"
256
+ * abbreviateNumber(1000000000n, { decimals: 0 }) // "1B"
257
+ * abbreviateNumber(123456n, { decimals: 2 }) // "1.23k" (if input is 1234.56 with 2 decimals)
258
+ */
259
+ export function abbreviateNumber(value, options) {
260
+ const { decimals, maxDecimals = 2, minDecimals } = options;
261
+ const isNegative = value < 0n;
262
+ const absValue = isNegative ? -value : value;
263
+ const scale = 10n ** BigInt(decimals);
264
+ for (const { divisor, suffix } of numericAbbreviations) {
265
+ const threshold = divisor * scale;
266
+ if (absValue >= threshold) {
267
+ return formatWithAbbreviation(value, { decimals, divisor, suffix, maxDecimals, minDecimals });
268
+ }
269
+ }
270
+ // For values less than 1000, format normally without padding
271
+ // Only pad decimals if the value actually has fractional parts
272
+ const formattedValue = viemFormatUnits(absValue, decimals);
273
+ const [integerPart, fractionalPart = ''] = formattedValue.split('.');
274
+ // Remove trailing zeros from fractional part
275
+ const trimmedFractional = fractionalPart.replace(/0+$/, '');
276
+ // If no fractional part remains, return just the integer
277
+ const result = trimmedFractional ? `${integerPart}.${trimmedFractional}` : integerPart;
278
+ return isNegative ? `-${result}` : result;
279
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,327 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { oneEther, addThousandsSeparator, parseUnits, formatUnits, formatTokenAmountForDisplay, parseIntegerSafe, timestampToDate, formatAmount, abbreviateNumber, formatWithAbbreviation, numericAbbreviations, } from './units.js';
4
+ describe('abbreviateNumber', () => {
5
+ describe('with decimals: 0 (whole numbers)', () => {
6
+ it('should abbreviate thousands with k suffix', () => {
7
+ assert.equal(abbreviateNumber(1000n, { decimals: 0 }), '1k');
8
+ assert.equal(abbreviateNumber(1500n, { decimals: 0 }), '1.5k');
9
+ assert.equal(abbreviateNumber(5000n, { decimals: 0 }), '5k');
10
+ assert.equal(abbreviateNumber(10000n, { decimals: 0 }), '10k');
11
+ assert.equal(abbreviateNumber(15000n, { decimals: 0 }), '15k');
12
+ assert.equal(abbreviateNumber(100000n, { decimals: 0 }), '100k');
13
+ assert.equal(abbreviateNumber(200000n, { decimals: 0 }), '200k');
14
+ assert.equal(abbreviateNumber(400000n, { decimals: 0 }), '400k');
15
+ assert.equal(abbreviateNumber(999000n, { decimals: 0 }), '999k');
16
+ });
17
+ it('should abbreviate millions with M suffix', () => {
18
+ assert.equal(abbreviateNumber(1000000n, { decimals: 0 }), '1M');
19
+ assert.equal(abbreviateNumber(1234567n, { decimals: 0 }), '1.23M');
20
+ assert.equal(abbreviateNumber(10000000n, { decimals: 0 }), '10M');
21
+ assert.equal(abbreviateNumber(15500000n, { decimals: 0 }), '15.5M');
22
+ assert.equal(abbreviateNumber(100000000n, { decimals: 0 }), '100M');
23
+ assert.equal(abbreviateNumber(999000000n, { decimals: 0 }), '999M');
24
+ });
25
+ it('should abbreviate billions with B suffix', () => {
26
+ assert.equal(abbreviateNumber(1000000000n, { decimals: 0 }), '1B');
27
+ assert.equal(abbreviateNumber(1234567890n, { decimals: 0 }), '1.23B');
28
+ assert.equal(abbreviateNumber(10000000000n, { decimals: 0 }), '10B');
29
+ assert.equal(abbreviateNumber(100000000000n, { decimals: 0 }), '100B');
30
+ });
31
+ it('should abbreviate trillions with T suffix', () => {
32
+ assert.equal(abbreviateNumber(1000000000000n, { decimals: 0 }), '1T');
33
+ assert.equal(abbreviateNumber(1234567890123n, { decimals: 0 }), '1.23T');
34
+ assert.equal(abbreviateNumber(10000000000000n, { decimals: 0 }), '10T');
35
+ });
36
+ it('should handle values less than 1000 without abbreviation', () => {
37
+ assert.equal(abbreviateNumber(0n, { decimals: 0 }), '0');
38
+ assert.equal(abbreviateNumber(1n, { decimals: 0 }), '1');
39
+ assert.equal(abbreviateNumber(10n, { decimals: 0 }), '10');
40
+ assert.equal(abbreviateNumber(100n, { decimals: 0 }), '100');
41
+ assert.equal(abbreviateNumber(999n, { decimals: 0 }), '999');
42
+ });
43
+ it('should handle negative values', () => {
44
+ assert.equal(abbreviateNumber(-1000n, { decimals: 0 }), '-1k');
45
+ assert.equal(abbreviateNumber(-1500n, { decimals: 0 }), '-1.5k');
46
+ assert.equal(abbreviateNumber(-1000000n, { decimals: 0 }), '-1M');
47
+ assert.equal(abbreviateNumber(-100n, { decimals: 0 }), '-100');
48
+ });
49
+ });
50
+ describe('with decimals: 2 (e.g., USD cents)', () => {
51
+ it('should abbreviate thousands with k suffix', () => {
52
+ // 100000n with decimals:2 represents $1,000.00
53
+ assert.equal(abbreviateNumber(100000n, { decimals: 2 }), '1k');
54
+ assert.equal(abbreviateNumber(150000n, { decimals: 2 }), '1.5k');
55
+ assert.equal(abbreviateNumber(1000000n, { decimals: 2 }), '10k');
56
+ assert.equal(abbreviateNumber(10000000n, { decimals: 2 }), '100k');
57
+ assert.equal(abbreviateNumber(20000000n, { decimals: 2 }), '200k');
58
+ assert.equal(abbreviateNumber(40000000n, { decimals: 2 }), '400k');
59
+ });
60
+ it('should abbreviate millions with M suffix', () => {
61
+ // 100000000n with decimals:2 represents $1,000,000.00
62
+ assert.equal(abbreviateNumber(100000000n, { decimals: 2 }), '1M');
63
+ assert.equal(abbreviateNumber(123456700n, { decimals: 2 }), '1.23M');
64
+ assert.equal(abbreviateNumber(1000000000n, { decimals: 2 }), '10M');
65
+ });
66
+ it('should handle values less than 1000 without abbreviation', () => {
67
+ assert.equal(abbreviateNumber(0n, { decimals: 2 }), '0');
68
+ assert.equal(abbreviateNumber(100n, { decimals: 2 }), '1');
69
+ assert.equal(abbreviateNumber(1000n, { decimals: 2 }), '10');
70
+ assert.equal(abbreviateNumber(10000n, { decimals: 2 }), '100');
71
+ assert.equal(abbreviateNumber(99900n, { decimals: 2 }), '999');
72
+ });
73
+ });
74
+ describe('with decimals: 6 (e.g., USDC)', () => {
75
+ it('should abbreviate thousands with k suffix', () => {
76
+ // 1000000000n with decimals:6 represents 1,000 USDC
77
+ assert.equal(abbreviateNumber(1000000000n, { decimals: 6 }), '1k');
78
+ assert.equal(abbreviateNumber(1500000000n, { decimals: 6 }), '1.5k');
79
+ assert.equal(abbreviateNumber(10000000000n, { decimals: 6 }), '10k');
80
+ assert.equal(abbreviateNumber(100000000000n, { decimals: 6 }), '100k');
81
+ });
82
+ it('should abbreviate millions with M suffix', () => {
83
+ assert.equal(abbreviateNumber(1000000000000n, { decimals: 6 }), '1M');
84
+ assert.equal(abbreviateNumber(1234567000000n, { decimals: 6 }), '1.23M');
85
+ });
86
+ });
87
+ describe('maxDecimals option', () => {
88
+ it('should respect maxDecimals for small abbreviated values', () => {
89
+ assert.equal(abbreviateNumber(1234n, { decimals: 0, maxDecimals: 3 }), '1.234k');
90
+ assert.equal(abbreviateNumber(1234n, { decimals: 0, maxDecimals: 1 }), '1.2k');
91
+ assert.equal(abbreviateNumber(1234n, { decimals: 0, maxDecimals: 0 }), '1k');
92
+ });
93
+ it('should use fewer decimals for larger abbreviated values', () => {
94
+ // >= 100: no decimals
95
+ assert.equal(abbreviateNumber(123456n, { decimals: 0, maxDecimals: 2 }), '123k');
96
+ // >= 10: max 1 decimal
97
+ assert.equal(abbreviateNumber(12345n, { decimals: 0, maxDecimals: 2 }), '12.3k');
98
+ // < 10: max 2 decimals (default)
99
+ assert.equal(abbreviateNumber(1234n, { decimals: 0, maxDecimals: 2 }), '1.23k');
100
+ });
101
+ });
102
+ });
103
+ describe('formatWithAbbreviation', () => {
104
+ it('should format with specific divisor and suffix', () => {
105
+ assert.equal(formatWithAbbreviation(100000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '100k');
106
+ assert.equal(formatWithAbbreviation(1234567n, { decimals: 0, divisor: 1000000n, suffix: 'M' }), '1.23M');
107
+ assert.equal(formatWithAbbreviation(5000000n, { decimals: 0, divisor: 1000000n, suffix: 'M' }), '5M');
108
+ });
109
+ it('should allow forcing a specific abbreviation', () => {
110
+ // Force display as thousands even though it's a million
111
+ assert.equal(formatWithAbbreviation(1000000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '1000k');
112
+ });
113
+ it('should handle negative values', () => {
114
+ assert.equal(formatWithAbbreviation(-100000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '-100k');
115
+ });
116
+ it('should use 1 decimal for values between 10-99', () => {
117
+ // 50_000 / 1000 = 50, which is between 10-99
118
+ assert.equal(formatWithAbbreviation(50000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '50k');
119
+ // 15_500 / 1000 = 15.5
120
+ assert.equal(formatWithAbbreviation(15500n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '15.5k');
121
+ });
122
+ it('should use 0 decimals for values >= 100', () => {
123
+ // 150_000 / 1000 = 150
124
+ assert.equal(formatWithAbbreviation(150000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '150k');
125
+ });
126
+ it('should handle values without decimal point', () => {
127
+ // When result has no fractional part
128
+ assert.equal(formatWithAbbreviation(1000n, { decimals: 0, divisor: 1000n, suffix: 'k' }), '1k');
129
+ });
130
+ });
131
+ describe('numericAbbreviations', () => {
132
+ it('should export the abbreviations array', () => {
133
+ assert.equal(numericAbbreviations.length, 4);
134
+ assert.equal(numericAbbreviations[0].suffix, 'T');
135
+ assert.equal(numericAbbreviations[1].suffix, 'B');
136
+ assert.equal(numericAbbreviations[2].suffix, 'M');
137
+ assert.equal(numericAbbreviations[3].suffix, 'k');
138
+ });
139
+ it('should have correct divisors', () => {
140
+ assert.equal(numericAbbreviations[0].divisor, 1000000000000n);
141
+ assert.equal(numericAbbreviations[1].divisor, 1000000000n);
142
+ assert.equal(numericAbbreviations[2].divisor, 1000000n);
143
+ assert.equal(numericAbbreviations[3].divisor, 1000n);
144
+ });
145
+ });
146
+ describe('oneEther', () => {
147
+ it('should equal 10^18', () => {
148
+ assert.equal(oneEther, 10n ** 18n);
149
+ assert.equal(oneEther, 1000000000000000000n);
150
+ });
151
+ });
152
+ describe('addThousandsSeparator', () => {
153
+ it('should add commas to large numbers', () => {
154
+ assert.equal(addThousandsSeparator('1000'), '1,000');
155
+ assert.equal(addThousandsSeparator('1000000'), '1,000,000');
156
+ assert.equal(addThousandsSeparator('1234567890'), '1,234,567,890');
157
+ });
158
+ it('should handle small numbers without separators', () => {
159
+ assert.equal(addThousandsSeparator('0'), '0');
160
+ assert.equal(addThousandsSeparator('1'), '1');
161
+ assert.equal(addThousandsSeparator('100'), '100');
162
+ assert.equal(addThousandsSeparator('999'), '999');
163
+ });
164
+ it('should preserve decimal parts', () => {
165
+ assert.equal(addThousandsSeparator('1000.50'), '1,000.50');
166
+ assert.equal(addThousandsSeparator('1234567.89'), '1,234,567.89');
167
+ });
168
+ it('should handle numbers with only decimal part', () => {
169
+ assert.equal(addThousandsSeparator('.50'), '0.50');
170
+ });
171
+ it('should use custom delimiter', () => {
172
+ assert.equal(addThousandsSeparator('1000000', ' '), '1 000 000');
173
+ assert.equal(addThousandsSeparator('1000000', '.'), '1.000.000');
174
+ });
175
+ it('should handle empty integer part', () => {
176
+ assert.equal(addThousandsSeparator(''), '0');
177
+ });
178
+ });
179
+ describe('parseUnits', () => {
180
+ it('should parse string amount to bigint', () => {
181
+ assert.equal(parseUnits('1', { decimals: 6 }), 1000000n);
182
+ assert.equal(parseUnits('1.5', { decimals: 6 }), 1500000n);
183
+ assert.equal(parseUnits('100', { decimals: 6 }), 100000000n);
184
+ });
185
+ it('should handle comma-separated values', () => {
186
+ assert.equal(parseUnits('1,000', { decimals: 6 }), 1000000000n);
187
+ assert.equal(parseUnits('1,234,567', { decimals: 6 }), 1234567000000n);
188
+ });
189
+ it('should handle custom delimiter', () => {
190
+ assert.equal(parseUnits('1.000', { decimals: 6, delimiter: '.' }), 1000000000n);
191
+ });
192
+ it('should handle zero', () => {
193
+ assert.equal(parseUnits('0', { decimals: 6 }), 0n);
194
+ });
195
+ });
196
+ describe('formatUnits', () => {
197
+ it('should format bigint to string with decimals', () => {
198
+ const result = formatUnits(1000000n, { decimals: 6 });
199
+ assert.equal(result, '1');
200
+ });
201
+ it('should truncate decimals', () => {
202
+ const result = formatUnits(1234567n, { decimals: 6, truncateDecimals: 2 });
203
+ assert.equal(result, '1.23');
204
+ });
205
+ it('should pad decimals', () => {
206
+ const result = formatUnits(1000000n, { decimals: 6, padDecimals: 2 });
207
+ assert.equal(result, '1.00');
208
+ });
209
+ it('should show cutoff indicator for small values', () => {
210
+ const result = formatUnits(100n, { decimals: 6, cutoff: 1000n });
211
+ assert.ok(result.startsWith('<'));
212
+ });
213
+ it('should not show cutoff indicator when value equals cutoff', () => {
214
+ const result = formatUnits(1000n, { decimals: 6, cutoff: 1000n });
215
+ assert.ok(!result.startsWith('<'));
216
+ });
217
+ it('should not show cutoff indicator when value exceeds cutoff', () => {
218
+ const result = formatUnits(2000n, { decimals: 6, cutoff: 1000n });
219
+ assert.ok(!result.startsWith('<'));
220
+ });
221
+ it('should round up when enabled', () => {
222
+ const result = formatUnits(1555000n, { decimals: 6, truncateDecimals: 2, roundUp: true });
223
+ assert.equal(result, '1.56');
224
+ });
225
+ it('should not round up values below halfway', () => {
226
+ const result = formatUnits(1544000n, { decimals: 6, truncateDecimals: 2, roundUp: true });
227
+ assert.equal(result, '1.54');
228
+ });
229
+ it('should not round when roundUp is true but truncateDecimals is undefined', () => {
230
+ const result = formatUnits(1555555n, { decimals: 6, roundUp: true });
231
+ // Without truncateDecimals, rounding is skipped
232
+ assert.ok(result.includes('1.555555') || result === '1.555555');
233
+ });
234
+ it('should use default padDecimals of 0', () => {
235
+ // Don't pass padDecimals - should default to 0
236
+ const result = formatUnits(1000000n, { decimals: 6 });
237
+ assert.equal(result, '1');
238
+ });
239
+ it('should use default delimiter of comma', () => {
240
+ // Don't pass delimiter - should default to ','
241
+ const result = formatUnits(1234567000000n, { decimals: 6 });
242
+ assert.ok(result.includes(','));
243
+ });
244
+ it('should use default roundUp of false', () => {
245
+ // Don't pass roundUp - should default to false (no rounding)
246
+ const result = formatUnits(1559000n, { decimals: 6, truncateDecimals: 2 });
247
+ assert.equal(result, '1.55'); // Truncated, not rounded
248
+ });
249
+ it('should not round when roundUp is false', () => {
250
+ const result = formatUnits(1559000n, { decimals: 6, truncateDecimals: 2, roundUp: false });
251
+ assert.equal(result, '1.55');
252
+ });
253
+ it('should use custom delimiter', () => {
254
+ const result = formatUnits(1234567000000n, { decimals: 6, delimiter: ' ' });
255
+ assert.ok(result.includes(' '));
256
+ });
257
+ });
258
+ describe('formatTokenAmountForDisplay', () => {
259
+ it('should format with 2 decimal truncation by default', () => {
260
+ const result = formatTokenAmountForDisplay(1234567n, { decimals: 6 });
261
+ assert.equal(result, '1.23');
262
+ });
263
+ it('should allow overriding truncateDecimals', () => {
264
+ const result = formatTokenAmountForDisplay(1234567n, { decimals: 6, truncateDecimals: 4 });
265
+ assert.equal(result, '1.2345');
266
+ });
267
+ });
268
+ describe('parseIntegerSafe', () => {
269
+ it('should parse valid integers', () => {
270
+ assert.equal(parseIntegerSafe('0'), 0);
271
+ assert.equal(parseIntegerSafe('1'), 1);
272
+ assert.equal(parseIntegerSafe('123'), 123);
273
+ assert.equal(parseIntegerSafe('-456'), -456);
274
+ });
275
+ it('should return null for invalid inputs', () => {
276
+ assert.equal(parseIntegerSafe('abc'), null);
277
+ assert.equal(parseIntegerSafe(''), null);
278
+ assert.equal(parseIntegerSafe('NaN'), null);
279
+ });
280
+ it('should truncate decimal values to integer', () => {
281
+ assert.equal(parseIntegerSafe('123.456'), 123);
282
+ assert.equal(parseIntegerSafe('99.99'), 99);
283
+ });
284
+ });
285
+ describe('timestampToDate', () => {
286
+ it('should convert seconds timestamp to Date', () => {
287
+ const timestamp = 1704067200; // 2024-01-01 00:00:00 UTC
288
+ const date = timestampToDate(timestamp);
289
+ assert.ok(date instanceof Date);
290
+ assert.equal(date?.getUTCFullYear(), 2024);
291
+ assert.equal(date?.getUTCMonth(), 0); // January
292
+ assert.equal(date?.getUTCDate(), 1);
293
+ });
294
+ it('should handle string timestamps', () => {
295
+ const date = timestampToDate('1704067200');
296
+ assert.ok(date instanceof Date);
297
+ assert.equal(date?.getUTCFullYear(), 2024);
298
+ });
299
+ it('should handle bigint timestamps', () => {
300
+ const date = timestampToDate(1704067200n);
301
+ assert.ok(date instanceof Date);
302
+ assert.equal(date?.getUTCFullYear(), 2024);
303
+ });
304
+ it('should return null for null/undefined input', () => {
305
+ assert.equal(timestampToDate(null), null);
306
+ assert.equal(timestampToDate(undefined), null);
307
+ });
308
+ it('should return null for zero timestamp', () => {
309
+ assert.equal(timestampToDate(0), null);
310
+ });
311
+ });
312
+ describe('formatAmount', () => {
313
+ const mockToken = { decimals: 6, symbol: 'USDC' };
314
+ it('should format amount with token decimals', () => {
315
+ const result = formatAmount(mockToken, 1234567n);
316
+ assert.ok(result);
317
+ assert.equal(result, '1.23');
318
+ });
319
+ it('should return null for null amount', () => {
320
+ assert.equal(formatAmount(mockToken, null), null);
321
+ });
322
+ it('should allow overriding options', () => {
323
+ const result = formatAmount(mockToken, 1234567n, { truncateDecimals: 4 });
324
+ assert.ok(result);
325
+ assert.equal(result, '1.2345');
326
+ });
327
+ });