@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.
- package/dist/chain.d.ts +446 -0
- package/dist/chain.js +98 -0
- package/dist/chain.test.js +112 -0
- package/dist/contracts.d.ts +206 -0
- package/dist/contracts.js +292 -0
- package/dist/contracts.test.js +243 -0
- package/dist/conversionRates.d.ts +215 -0
- package/dist/{src/conversionRates.js → conversionRates.js} +74 -4
- package/dist/conversionRates.test.js +473 -0
- package/dist/currencies.d.ts +248 -0
- package/dist/{src/currencies.js → currencies.js} +119 -38
- package/dist/currencies.test.js +343 -0
- package/dist/data/countries.json +1099 -0
- package/dist/deposit.d.ts +69 -0
- package/dist/deposit.js +52 -0
- package/dist/depositKeys.d.ts +154 -0
- package/dist/depositKeys.js +104 -0
- package/dist/depositKeys.test.d.ts +1 -0
- package/dist/depositKeys.test.js +231 -0
- package/dist/events.d.ts +41 -0
- package/dist/events.js +92 -0
- package/dist/events.test.d.ts +1 -0
- package/dist/events.test.js +111 -0
- package/dist/fees.d.ts +136 -0
- package/dist/fees.js +178 -0
- package/dist/fees.test.d.ts +1 -0
- package/dist/fees.test.js +165 -0
- package/dist/ids.d.ts +175 -0
- package/dist/ids.js +231 -0
- package/dist/ids.test.d.ts +1 -0
- package/dist/ids.test.js +607 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +45 -0
- package/dist/input.d.ts +26 -0
- package/dist/{src/input.js → input.js} +23 -4
- package/dist/intent.d.ts +50 -0
- package/dist/intent.js +28 -0
- package/dist/intent.test.d.ts +1 -0
- package/dist/intent.test.js +119 -0
- package/dist/json.d.ts +55 -0
- package/dist/json.js +80 -0
- package/dist/participants.d.ts +22 -0
- package/dist/participants.js +28 -0
- package/dist/payment.d.ts +481 -0
- package/dist/payment.js +1149 -0
- package/dist/payment.test.d.ts +1 -0
- package/dist/payment.test.js +272 -0
- package/dist/peer-types.d.ts +327 -0
- package/dist/peer-types.js +169 -0
- package/dist/profit.d.ts +49 -0
- package/dist/profit.js +57 -0
- package/dist/profit.test.d.ts +1 -0
- package/dist/profit.test.js +315 -0
- package/dist/reputation.d.ts +125 -0
- package/dist/reputation.js +188 -0
- package/dist/reputation.test.d.ts +1 -0
- package/dist/reputation.test.js +156 -0
- package/dist/sort.d.ts +75 -0
- package/dist/sort.js +70 -0
- package/dist/sort.test.d.ts +1 -0
- package/dist/sort.test.js +216 -0
- package/dist/stateKey.d.ts +35 -0
- package/dist/stateKey.js +36 -0
- package/dist/stats.d.ts +61 -0
- package/dist/stats.js +53 -0
- package/dist/stats.test.d.ts +1 -0
- package/dist/stats.test.js +104 -0
- package/dist/time.d.ts +104 -0
- package/dist/time.js +162 -0
- package/dist/time.test.d.ts +1 -0
- package/dist/time.test.js +231 -0
- package/dist/tokens.d.ts +15502 -0
- package/dist/{src/tokens.js → tokens.js} +98 -17
- package/dist/tokens.test.d.ts +1 -0
- package/dist/tokens.test.js +193 -0
- package/dist/units.d.ts +200 -0
- package/dist/units.js +279 -0
- package/dist/units.test.d.ts +1 -0
- package/dist/units.test.js +327 -0
- package/package.json +119 -29
- package/dist/scripts/convert-svg.js +0 -17
- package/dist/src/chain.d.ts +0 -14
- package/dist/src/chain.js +0 -18
- package/dist/src/contracts.d.ts +0 -30
- package/dist/src/contracts.js +0 -64
- package/dist/src/conversionRates.d.ts +0 -124
- package/dist/src/currencies.d.ts +0 -144
- package/dist/src/data/countries.json +0 -1099
- package/dist/src/deposit.d.ts +0 -1
- package/dist/src/events.d.ts +0 -3
- package/dist/src/events.js +0 -16
- package/dist/src/ids.d.ts +0 -72
- package/dist/src/ids.js +0 -81
- package/dist/src/index.d.ts +0 -16
- package/dist/src/index.js +0 -16
- package/dist/src/input.d.ts +0 -7
- package/dist/src/intent.d.ts +0 -14
- package/dist/src/json.d.ts +0 -18
- package/dist/src/json.js +0 -44
- package/dist/src/participants.d.ts +0 -7
- package/dist/src/participants.js +0 -13
- package/dist/src/payment.d.ts +0 -164
- package/dist/src/payment.js +0 -439
- package/dist/src/profit.d.ts +0 -28
- package/dist/src/profit.js +0 -34
- package/dist/src/sort.d.ts +0 -36
- package/dist/src/sort.js +0 -39
- package/dist/src/stats.d.ts +0 -7
- package/dist/src/stats.js +0 -8
- package/dist/src/time.d.ts +0 -54
- package/dist/src/time.js +0 -106
- package/dist/src/tokens.d.ts +0 -3675
- package/dist/src/units.d.ts +0 -17
- package/dist/src/units.js +0 -79
- package/dist/src/zkp2p-types.d.ts +0 -48
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/scripts/convert-svg.ts +0 -22
- package/src/chain.ts +0 -27
- package/src/contracts.ts +0 -89
- package/src/conversionRates.ts +0 -356
- package/src/currencies.ts +0 -286
- package/src/data/countries.json +0 -1099
- package/src/deposit.ts +0 -2
- package/src/events.ts +0 -21
- package/src/flags/eur.svg +0 -4
- package/src/flags/usd.svg +0 -25
- package/src/flags.ts +0 -4
- package/src/global.d.ts +0 -9
- package/src/ids.ts +0 -137
- package/src/index.ts +0 -16
- package/src/input.ts +0 -35
- package/src/intent.ts +0 -15
- package/src/json.ts +0 -42
- package/src/participants.ts +0 -14
- package/src/payment.ts +0 -573
- package/src/profit.ts +0 -55
- package/src/sort.ts +0 -49
- package/src/stats.ts +0 -10
- package/src/time.ts +0 -123
- package/src/tokens.ts +0 -159
- package/src/units.ts +0 -120
- package/src/zkp2p-types.ts +0 -55
- package/tsconfig.json +0 -18
- /package/dist/{scripts/convert-svg.d.ts → chain.test.d.ts} +0 -0
- /package/dist/{src/deposit.js → contracts.test.d.ts} +0 -0
- /package/dist/{src/intent.js → conversionRates.test.d.ts} +0 -0
- /package/dist/{src/zkp2p-types.js → currencies.test.d.ts} +0 -0
- /package/dist/{src/flags.d.ts → flags.d.ts} +0 -0
- /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
|
+
});
|