@metamask-previews/perps-controller 3.0.0-preview-e61cfa5 → 3.1.0-preview-548bdd1d9
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/CHANGELOG.md +19 -1
- package/dist/PerpsController-method-action-types.cjs.map +1 -1
- package/dist/PerpsController-method-action-types.d.cts +8 -0
- package/dist/PerpsController-method-action-types.d.cts.map +1 -1
- package/dist/PerpsController-method-action-types.d.mts +8 -0
- package/dist/PerpsController-method-action-types.d.mts.map +1 -1
- package/dist/PerpsController-method-action-types.mjs.map +1 -1
- package/dist/PerpsController.cjs +117 -29
- package/dist/PerpsController.cjs.map +1 -1
- package/dist/PerpsController.d.cts +14 -2
- package/dist/PerpsController.d.cts.map +1 -1
- package/dist/PerpsController.d.mts +14 -2
- package/dist/PerpsController.d.mts.map +1 -1
- package/dist/PerpsController.mjs +118 -30
- package/dist/PerpsController.mjs.map +1 -1
- package/dist/constants/eventNames.cjs +1 -0
- package/dist/constants/eventNames.cjs.map +1 -1
- package/dist/constants/eventNames.d.cts +1 -0
- package/dist/constants/eventNames.d.cts.map +1 -1
- package/dist/constants/eventNames.d.mts +1 -0
- package/dist/constants/eventNames.d.mts.map +1 -1
- package/dist/constants/eventNames.mjs +1 -0
- package/dist/constants/eventNames.mjs.map +1 -1
- package/dist/constants/perpsConfig.cjs +46 -1
- package/dist/constants/perpsConfig.cjs.map +1 -1
- package/dist/constants/perpsConfig.d.cts +35 -0
- package/dist/constants/perpsConfig.d.cts.map +1 -1
- package/dist/constants/perpsConfig.d.mts +35 -0
- package/dist/constants/perpsConfig.d.mts.map +1 -1
- package/dist/constants/perpsConfig.mjs +43 -0
- package/dist/constants/perpsConfig.mjs.map +1 -1
- package/dist/constants/transactionsHistoryConfig.cjs +23 -4
- package/dist/constants/transactionsHistoryConfig.cjs.map +1 -1
- package/dist/constants/transactionsHistoryConfig.d.cts +23 -4
- package/dist/constants/transactionsHistoryConfig.d.cts.map +1 -1
- package/dist/constants/transactionsHistoryConfig.d.mts +23 -4
- package/dist/constants/transactionsHistoryConfig.d.mts.map +1 -1
- package/dist/constants/transactionsHistoryConfig.mjs +23 -4
- package/dist/constants/transactionsHistoryConfig.mjs.map +1 -1
- package/dist/index.cjs +14 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/providers/HyperLiquidProvider.cjs +83 -27
- package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
- package/dist/providers/HyperLiquidProvider.mjs +83 -27
- package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.cjs +6 -0
- package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.mjs +6 -0
- package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +6 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.mts +6 -0
- package/dist/types/index.d.mts.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/index.cjs +2 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +2 -0
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +2 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +2 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/perpsDiskPersistence.cjs +252 -0
- package/dist/utils/perpsDiskPersistence.cjs.map +1 -0
- package/dist/utils/perpsDiskPersistence.d.cts +108 -0
- package/dist/utils/perpsDiskPersistence.d.cts.map +1 -0
- package/dist/utils/perpsDiskPersistence.d.mts +108 -0
- package/dist/utils/perpsDiskPersistence.d.mts.map +1 -0
- package/dist/utils/perpsDiskPersistence.mjs +244 -0
- package/dist/utils/perpsDiskPersistence.mjs.map +1 -0
- package/dist/utils/perpsFormatters.cjs +498 -0
- package/dist/utils/perpsFormatters.cjs.map +1 -0
- package/dist/utils/perpsFormatters.d.cts +202 -0
- package/dist/utils/perpsFormatters.d.cts.map +1 -0
- package/dist/utils/perpsFormatters.d.mts +202 -0
- package/dist/utils/perpsFormatters.d.mts.map +1 -0
- package/dist/utils/perpsFormatters.mjs +489 -0
- package/dist/utils/perpsFormatters.mjs.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable perps decimal formatters.
|
|
3
|
+
*
|
|
4
|
+
* These are the canonical implementations, exported from the controller so
|
|
5
|
+
* extension and any future consumer can import them directly.
|
|
6
|
+
* No mobile-specific imports — safe to sync to Core.
|
|
7
|
+
*
|
|
8
|
+
* Intl.NumberFormat instances are cached in a module-level Map keyed by
|
|
9
|
+
* serialized options, avoiding repeated construction costs.
|
|
10
|
+
*/
|
|
11
|
+
import { DECIMAL_PRECISION_CONFIG, FUNDING_RATE_CONFIG, PERPS_CONSTANTS } from "../constants/perpsConfig.mjs";
|
|
12
|
+
// Module-level Intl.NumberFormat cache (keyed by serialized options).
|
|
13
|
+
const _fmtCache = new Map();
|
|
14
|
+
function _formatCurrency(value, currency, opts) {
|
|
15
|
+
const key = `${currency}:${opts.minimumFractionDigits}:${opts.maximumFractionDigits}`;
|
|
16
|
+
let formatter = _fmtCache.get(key);
|
|
17
|
+
if (!formatter) {
|
|
18
|
+
formatter = new Intl.NumberFormat('en-US', {
|
|
19
|
+
style: 'currency',
|
|
20
|
+
currency,
|
|
21
|
+
currencyDisplay: 'narrowSymbol',
|
|
22
|
+
minimumFractionDigits: opts.minimumFractionDigits,
|
|
23
|
+
maximumFractionDigits: opts.maximumFractionDigits,
|
|
24
|
+
});
|
|
25
|
+
_fmtCache.set(key, formatter);
|
|
26
|
+
}
|
|
27
|
+
return formatter.format(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Internal equivalent of the mobile formatWithThreshold utility.
|
|
31
|
+
* Formats a currency value, returning "<$X.XX" for values below threshold.
|
|
32
|
+
*
|
|
33
|
+
* @param amount - The numeric amount to format.
|
|
34
|
+
* @param threshold - The threshold below which the "<" prefix is shown.
|
|
35
|
+
* @param options - Intl formatting options.
|
|
36
|
+
* @param options.currency - ISO 4217 currency code.
|
|
37
|
+
* @param options.minimumFractionDigits - Minimum decimal digits.
|
|
38
|
+
* @param options.maximumFractionDigits - Maximum decimal digits.
|
|
39
|
+
* @returns Formatted currency string.
|
|
40
|
+
*/
|
|
41
|
+
function _formatWithThreshold(amount, threshold, options) {
|
|
42
|
+
const formatOpts = {
|
|
43
|
+
minimumFractionDigits: options.minimumFractionDigits,
|
|
44
|
+
maximumFractionDigits: options.maximumFractionDigits,
|
|
45
|
+
currencyDisplay: 'narrowSymbol',
|
|
46
|
+
};
|
|
47
|
+
if (amount === 0) {
|
|
48
|
+
return _formatCurrency(0, options.currency, formatOpts);
|
|
49
|
+
}
|
|
50
|
+
return Math.abs(amount) < threshold
|
|
51
|
+
? `<${_formatCurrency(threshold, options.currency, formatOpts)}`
|
|
52
|
+
: _formatCurrency(amount, options.currency, formatOpts);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Price threshold constants for PRICE_RANGES_UNIVERSAL
|
|
56
|
+
* These define the boundaries between different formatting ranges
|
|
57
|
+
*/
|
|
58
|
+
export const PRICE_THRESHOLD = {
|
|
59
|
+
/** Very high values boundary (> $100k) */
|
|
60
|
+
VERY_HIGH: 100000,
|
|
61
|
+
/** High values boundary (> $10k) */
|
|
62
|
+
HIGH: 10000,
|
|
63
|
+
/** Large values boundary (> $1k) */
|
|
64
|
+
LARGE: 1000,
|
|
65
|
+
/** Medium values boundary (> $100) */
|
|
66
|
+
MEDIUM: 100,
|
|
67
|
+
/** Medium-low values boundary (> $10) */
|
|
68
|
+
MEDIUM_LOW: 10,
|
|
69
|
+
/** Low values boundary (>= $0.01) */
|
|
70
|
+
LOW: 0.01,
|
|
71
|
+
/**
|
|
72
|
+
* Very small values threshold (< $0.01)
|
|
73
|
+
* This is the minimum value for formatWithThreshold and should align with
|
|
74
|
+
* the 6 decimal maximum (0.000001 is the smallest representable value)
|
|
75
|
+
*/
|
|
76
|
+
VERY_SMALL: 0.000001,
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Formats a number to a specific number of significant digits
|
|
80
|
+
* Strips trailing zeros unless minDecimals requires them
|
|
81
|
+
*
|
|
82
|
+
* @param value - The numeric value to format
|
|
83
|
+
* @param significantDigits - Number of significant digits to maintain
|
|
84
|
+
* @param minDecimals - Minimum decimal places to show (may add zeros)
|
|
85
|
+
* @param maxDecimals - Maximum decimal places allowed
|
|
86
|
+
* @returns Formatted number with appropriate precision, trailing zeros removed
|
|
87
|
+
*/
|
|
88
|
+
export function formatWithSignificantDigits(value, significantDigits, minDecimals, maxDecimals) {
|
|
89
|
+
// Handle special cases
|
|
90
|
+
if (value === 0) {
|
|
91
|
+
// Return zero with no trailing decimals by default (matches stripTrailingZeros behavior)
|
|
92
|
+
// Can be overridden by explicit minDecimals if needed
|
|
93
|
+
return { value: 0, decimals: minDecimals ?? 0 };
|
|
94
|
+
}
|
|
95
|
+
const absValue = Math.abs(value);
|
|
96
|
+
// For numbers >= 1, calculate decimals based on magnitude to achieve target significant figures
|
|
97
|
+
// This ensures consistent precision across different price ranges:
|
|
98
|
+
// Examples with 4 significant figures:
|
|
99
|
+
// $123,456.78 → $123,456.78 (≥$1000: 2 decimals minimum, 8 sig figs)
|
|
100
|
+
// $456.12 → $456.12 (≥$10: 2 decimals minimum, 5 sig figs)
|
|
101
|
+
// $56.123 → $56.123 (≥$10: 2 decimals minimum, 5 sig figs)
|
|
102
|
+
// $5.123 → $5.123 ($1-$10: 3 decimals = 4 sig figs)
|
|
103
|
+
// $2.801 → $2.801 ($1-$10: 3 decimals = 4 sig figs)
|
|
104
|
+
// $1.234 → $1.234 ($1-$10: 3 decimals = 4 sig figs)
|
|
105
|
+
if (absValue >= 1) {
|
|
106
|
+
let targetDecimals;
|
|
107
|
+
// Calculate decimals needed based on integer digits to achieve target significant figures
|
|
108
|
+
// For $38.388 with 5 sig figs: 2 integer digits, need 3 decimals (3,8,3,8,8)
|
|
109
|
+
// For $123.45 with 5 sig figs: 3 integer digits, need 2 decimals (1,2,3,4,5)
|
|
110
|
+
const integerDigits = Math.floor(Math.log10(absValue)) + 1;
|
|
111
|
+
const decimalsNeeded = significantDigits - integerDigits;
|
|
112
|
+
targetDecimals = Math.max(decimalsNeeded, 0); // Can't have negative decimals
|
|
113
|
+
// Apply explicit minimum decimals constraint if provided (for special cases)
|
|
114
|
+
if (minDecimals !== undefined && targetDecimals < minDecimals) {
|
|
115
|
+
targetDecimals = minDecimals;
|
|
116
|
+
}
|
|
117
|
+
// Apply maximum decimals constraint if specified
|
|
118
|
+
const finalDecimals = maxDecimals === undefined
|
|
119
|
+
? targetDecimals
|
|
120
|
+
: Math.min(targetDecimals, maxDecimals);
|
|
121
|
+
// Round to prevent floating-point artifacts (e.g., 2.820000000000003 → 2.82)
|
|
122
|
+
const roundedValue = Number(value.toFixed(finalDecimals));
|
|
123
|
+
return {
|
|
124
|
+
value: roundedValue,
|
|
125
|
+
decimals: finalDecimals,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// For numbers < 1, use toPrecision to limit to significantDigits
|
|
129
|
+
// Examples: 0.1234, 0.01234 should show exactly 4 sig figs
|
|
130
|
+
const precisionStr = absValue.toPrecision(significantDigits);
|
|
131
|
+
const precisionNum = parseFloat(precisionStr);
|
|
132
|
+
// Convert to string to count actual decimals after trailing zeros are removed
|
|
133
|
+
const valueStr = precisionNum.toString();
|
|
134
|
+
const [, decPart = ''] = valueStr.split('.');
|
|
135
|
+
let actualDecimals = decPart.length;
|
|
136
|
+
// Apply min/max decimal constraints
|
|
137
|
+
if (minDecimals !== undefined && actualDecimals < minDecimals) {
|
|
138
|
+
actualDecimals = minDecimals; // Will add zeros if needed
|
|
139
|
+
}
|
|
140
|
+
if (maxDecimals !== undefined && actualDecimals > maxDecimals) {
|
|
141
|
+
actualDecimals = maxDecimals;
|
|
142
|
+
}
|
|
143
|
+
// Return the value with sign restored and decimal count
|
|
144
|
+
return {
|
|
145
|
+
value: value < 0 ? -precisionNum : precisionNum,
|
|
146
|
+
decimals: actualDecimals,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Minimal view fiat range configuration
|
|
151
|
+
* Uses fiat-style stripping for clean currency display
|
|
152
|
+
* Strips only .00 to avoid partial decimals like $1,250.1
|
|
153
|
+
*/
|
|
154
|
+
export const PRICE_RANGES_MINIMAL_VIEW = [
|
|
155
|
+
{
|
|
156
|
+
// Large values (>= $1000): Strip .00 only ($5,000 not $5,000.00, but $5,000.10 stays)
|
|
157
|
+
condition: (val) => Math.abs(val) >= PRICE_THRESHOLD.LARGE,
|
|
158
|
+
minimumDecimals: 2,
|
|
159
|
+
maximumDecimals: 2,
|
|
160
|
+
threshold: PRICE_THRESHOLD.LARGE,
|
|
161
|
+
stripTrailingZeros: true,
|
|
162
|
+
fiatStyleStripping: true,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
// Small values (< $1000): Also use fiat-style stripping ($100 not $100.00, but $13.40 stays)
|
|
166
|
+
condition: () => true,
|
|
167
|
+
minimumDecimals: 2,
|
|
168
|
+
maximumDecimals: 2,
|
|
169
|
+
threshold: PRICE_THRESHOLD.LOW,
|
|
170
|
+
stripTrailingZeros: true,
|
|
171
|
+
fiatStyleStripping: true,
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
/**
|
|
175
|
+
* Universal price range configuration following comprehensive rules from rules-decimals.md
|
|
176
|
+
*
|
|
177
|
+
* Rules:
|
|
178
|
+
* - Max 6 decimals across all ranges (Hyperliquid limit)
|
|
179
|
+
* - Strip trailing zeros by default
|
|
180
|
+
* - Use |v| (absolute value) for conditions
|
|
181
|
+
*
|
|
182
|
+
* Significant digits by range:
|
|
183
|
+
* - > $100,000: 6 sig digs
|
|
184
|
+
* - $100,000 > x > $0.01: 5 sig digs
|
|
185
|
+
* - < $0.01: 4 sig digs
|
|
186
|
+
*
|
|
187
|
+
* Decimal limits by price range:
|
|
188
|
+
* - |v| > 10,000: min 0, max 0 decimals; 5 sig digs (6 if >100k)
|
|
189
|
+
* - |v| > 1,000: min 0, max 1 decimal; 5 sig digs
|
|
190
|
+
* - |v| > 100: min 0, max 2 decimals; 5 sig digs
|
|
191
|
+
* - |v| > 10: min 0, max 4 decimals; 5 sig digs
|
|
192
|
+
* - |v| ≥ 0.01: 5 sig digs, min 2, max 6 decimals
|
|
193
|
+
* - |v| < 0.01: 4 sig digs, min 2, max 6 decimals
|
|
194
|
+
*
|
|
195
|
+
* Examples:
|
|
196
|
+
* - $123,456.78 → $123,457 (>$10k: 0 decimals, 6 sig figs)
|
|
197
|
+
* - $12,345.67 → $12,346 (>$10k: 0 decimals, 5 sig figs)
|
|
198
|
+
* - $1,234.56 → $1,234.6 ($1k-$10k: 1 decimal, 5 sig figs)
|
|
199
|
+
* - $123.456 → $123.46 ($100-$1k: 2 decimals, 5 sig figs)
|
|
200
|
+
* - $12.34567 → $12.346 ($10-$100: 4 decimals, 5 sig figs)
|
|
201
|
+
* - $1.3445555 → $1.3446 (≥$0.01: 5 sig figs)
|
|
202
|
+
* - $0.333333 → $0.33333 (≥$0.01: 5 sig figs)
|
|
203
|
+
* - $0.004236 → $0.004236 (<$0.01: 4 sig figs, max 6 decimals)
|
|
204
|
+
* - $0.0000006 → $0.000001 (<$0.01: 4 sig figs, rounds with max 6 decimals)
|
|
205
|
+
*/
|
|
206
|
+
export const PRICE_RANGES_UNIVERSAL = [
|
|
207
|
+
{
|
|
208
|
+
// Very high values (> $100,000): No decimals, 6 significant figures
|
|
209
|
+
// Ex: $123,456.78 → $123,457
|
|
210
|
+
condition: (val) => Math.abs(val) > PRICE_THRESHOLD.VERY_HIGH,
|
|
211
|
+
minimumDecimals: 0,
|
|
212
|
+
maximumDecimals: 0,
|
|
213
|
+
significantDigits: 6,
|
|
214
|
+
threshold: PRICE_THRESHOLD.VERY_HIGH,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
// High values ($10,000-$100,000]: No decimals, 5 significant figures
|
|
218
|
+
// Ex: $12,345.67 → $12,346
|
|
219
|
+
condition: (val) => Math.abs(val) > PRICE_THRESHOLD.HIGH,
|
|
220
|
+
minimumDecimals: 0,
|
|
221
|
+
maximumDecimals: 0,
|
|
222
|
+
significantDigits: 5,
|
|
223
|
+
threshold: PRICE_THRESHOLD.HIGH,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
// Large values ($1,000-$10,000]: Max 1 decimal, 5 significant figures
|
|
227
|
+
// Ex: $1,234.56 → $1,234.6
|
|
228
|
+
condition: (val) => Math.abs(val) > PRICE_THRESHOLD.LARGE,
|
|
229
|
+
minimumDecimals: 0,
|
|
230
|
+
maximumDecimals: 1,
|
|
231
|
+
significantDigits: 5,
|
|
232
|
+
threshold: PRICE_THRESHOLD.LARGE,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
// Medium values ($100-$1,000]: Max 2 decimals, 5 significant figures
|
|
236
|
+
// Ex: $123.456 → $123.46
|
|
237
|
+
condition: (val) => Math.abs(val) > PRICE_THRESHOLD.MEDIUM,
|
|
238
|
+
minimumDecimals: 0,
|
|
239
|
+
maximumDecimals: 2,
|
|
240
|
+
significantDigits: 5,
|
|
241
|
+
threshold: PRICE_THRESHOLD.MEDIUM,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
// Medium-low values ($10-$100]: Max 4 decimals, 5 significant figures
|
|
245
|
+
// Ex: $12.34567 → $12.346
|
|
246
|
+
condition: (val) => Math.abs(val) > PRICE_THRESHOLD.MEDIUM_LOW,
|
|
247
|
+
minimumDecimals: 0,
|
|
248
|
+
maximumDecimals: 4,
|
|
249
|
+
significantDigits: 5,
|
|
250
|
+
threshold: PRICE_THRESHOLD.MEDIUM_LOW,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
// Low values ($0.01-$10]: 5 significant figures, min 2 max MAX_PRICE_DECIMALS decimals
|
|
254
|
+
// Ex: $1.3445555 → $1.3446 | $0.333333 → $0.33333
|
|
255
|
+
condition: (val) => Math.abs(val) >= PRICE_THRESHOLD.LOW,
|
|
256
|
+
significantDigits: 5,
|
|
257
|
+
minimumDecimals: 2,
|
|
258
|
+
maximumDecimals: DECIMAL_PRECISION_CONFIG.MaxPriceDecimals,
|
|
259
|
+
threshold: PRICE_THRESHOLD.LOW,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
// Very small values (< $0.01): 4 significant figures, min 2 max MAX_PRICE_DECIMALS decimals
|
|
263
|
+
// Ex: $0.004236 → $0.004236 | $0.0000006 → $0.000001
|
|
264
|
+
condition: () => true,
|
|
265
|
+
significantDigits: 4,
|
|
266
|
+
minimumDecimals: 2,
|
|
267
|
+
maximumDecimals: DECIMAL_PRECISION_CONFIG.MaxPriceDecimals,
|
|
268
|
+
threshold: PRICE_THRESHOLD.VERY_SMALL,
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
/**
|
|
272
|
+
* Formats a balance value as USD currency with appropriate decimal places
|
|
273
|
+
*
|
|
274
|
+
* @param balance - Raw numeric balance value (e.g., 1234.56, not token minimal denomination)
|
|
275
|
+
* @param options - Optional formatting options
|
|
276
|
+
* @param options.minimumDecimals - Global minimum decimal places (overrides range configs)
|
|
277
|
+
* @param options.maximumDecimals - Global maximum decimal places (overrides range configs)
|
|
278
|
+
* @param options.significantDigits - Global significant digits (overrides decimal settings when set)
|
|
279
|
+
* @param options.ranges - Custom range configurations (defaults to PRICE_RANGES_MINIMAL_VIEW)
|
|
280
|
+
* @param options.currency - Currency code (default: 'USD')
|
|
281
|
+
* @param options.locale - Locale for formatting (default: 'en-US')
|
|
282
|
+
* @param options.stripTrailingZeros - Strip trailing zeros from output (default: false via PRICE_RANGES_MINIMAL_VIEW). When true, overrides minimumDecimals constraint.
|
|
283
|
+
* @returns Formatted currency string with variable decimals based on configured ranges
|
|
284
|
+
* @example
|
|
285
|
+
* // Using defaults (preserves trailing zeros for fiat)
|
|
286
|
+
* formatPerpsFiat(1234.56) => "$1,234.56"
|
|
287
|
+
* formatPerpsFiat(1250.00) => "$1,250.00" // Trailing zeros preserved
|
|
288
|
+
* formatPerpsFiat(50000) => "$50,000.00" // Trailing zeros preserved
|
|
289
|
+
*
|
|
290
|
+
* // Stripping trailing zeros when needed (e.g., for crypto)
|
|
291
|
+
* formatPerpsFiat(1250, { stripTrailingZeros: true }) => "$1,250"
|
|
292
|
+
*
|
|
293
|
+
* // With custom ranges
|
|
294
|
+
* formatPerpsFiat(0.00001, {
|
|
295
|
+
* ranges: [
|
|
296
|
+
* { condition: (v) => v < 0.001, minimumDecimals: 6, maximumDecimals: 8 },
|
|
297
|
+
* { condition: () => true, minimumDecimals: 2, maximumDecimals: 2 }
|
|
298
|
+
* ]
|
|
299
|
+
* }) => "$0.00001" // Trailing zero stripped
|
|
300
|
+
*
|
|
301
|
+
* // With significant digits
|
|
302
|
+
* formatPerpsFiat(1234.56789, { significantDigits: 5 }) => "$1,234.6"
|
|
303
|
+
* formatPerpsFiat(0.0001234, { significantDigits: 3 }) => "$0.000123"
|
|
304
|
+
*/
|
|
305
|
+
export const formatPerpsFiat = (balance, options) => {
|
|
306
|
+
const value = typeof balance === 'string' ? parseFloat(balance) : balance;
|
|
307
|
+
const currency = options?.currency ?? 'USD';
|
|
308
|
+
let formatted;
|
|
309
|
+
if (isNaN(value)) {
|
|
310
|
+
// Return placeholder for invalid values to avoid confusion with actual $0 values
|
|
311
|
+
return PERPS_CONSTANTS.FallbackPriceDisplay;
|
|
312
|
+
}
|
|
313
|
+
// Use custom ranges or defaults
|
|
314
|
+
const ranges = options?.ranges ?? PRICE_RANGES_MINIMAL_VIEW;
|
|
315
|
+
// Find the first matching range configuration
|
|
316
|
+
const rangeConfig = ranges.find((range) => range.condition(value));
|
|
317
|
+
if (rangeConfig) {
|
|
318
|
+
// Check for significant digits (global or range-specific)
|
|
319
|
+
const sigDigits = options?.significantDigits ?? rangeConfig.significantDigits;
|
|
320
|
+
// If significant digits are specified, use them
|
|
321
|
+
if (sigDigits) {
|
|
322
|
+
// Get min/max decimals (global overrides range, range overrides default)
|
|
323
|
+
const minDecimals = options?.minimumDecimals ?? rangeConfig.minimumDecimals;
|
|
324
|
+
const maxDecimals = options?.maximumDecimals ?? rangeConfig.maximumDecimals;
|
|
325
|
+
// Calculate appropriate formatting based on significant digits
|
|
326
|
+
const { value: formattedValue, decimals } = formatWithSignificantDigits(value, sigDigits, minDecimals, maxDecimals);
|
|
327
|
+
// Format with the calculated decimal places
|
|
328
|
+
formatted = _formatWithThreshold(formattedValue, rangeConfig.threshold ?? 0.01, {
|
|
329
|
+
currency,
|
|
330
|
+
minimumFractionDigits: decimals,
|
|
331
|
+
maximumFractionDigits: decimals,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Standard decimal-based formatting (existing logic)
|
|
336
|
+
const minDecimals = options?.minimumDecimals ?? rangeConfig.minimumDecimals;
|
|
337
|
+
const maxDecimals = options?.maximumDecimals ?? rangeConfig.maximumDecimals;
|
|
338
|
+
// Use custom formatting if provided
|
|
339
|
+
if (rangeConfig.customFormat) {
|
|
340
|
+
formatted = rangeConfig.customFormat(value, options?.locale ?? 'en-US', currency);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Use standard formatting with threshold
|
|
344
|
+
formatted = _formatWithThreshold(value, rangeConfig.threshold ?? 0.01, {
|
|
345
|
+
currency,
|
|
346
|
+
minimumFractionDigits: minDecimals,
|
|
347
|
+
maximumFractionDigits: maxDecimals,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// Fallback if no range matches (shouldn't happen with proper default config)
|
|
354
|
+
const fallbackMin = options?.minimumDecimals ?? 2;
|
|
355
|
+
const fallbackMax = options?.maximumDecimals ?? 2;
|
|
356
|
+
formatted = _formatWithThreshold(value, 0.01, {
|
|
357
|
+
currency,
|
|
358
|
+
minimumFractionDigits: fallbackMin,
|
|
359
|
+
maximumFractionDigits: fallbackMax,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// Post-process: strip trailing zeros unless explicitly disabled
|
|
363
|
+
// Priority: explicit options.stripTrailingZeros false > rangeConfig > options default > true
|
|
364
|
+
// If options.stripTrailingZeros is explicitly false, skip stripping entirely
|
|
365
|
+
if (options?.stripTrailingZeros === false) {
|
|
366
|
+
return formatted;
|
|
367
|
+
}
|
|
368
|
+
// Otherwise check range config or default to true
|
|
369
|
+
const shouldStrip = rangeConfig?.stripTrailingZeros ?? options?.stripTrailingZeros ?? true;
|
|
370
|
+
if (shouldStrip) {
|
|
371
|
+
// Check if fiat-style stripping is enabled (only strips .00)
|
|
372
|
+
const useFiatStyle = rangeConfig?.fiatStyleStripping ?? false;
|
|
373
|
+
if (useFiatStyle) {
|
|
374
|
+
// Fiat-style: Only strip .00 (no meaningful decimals), preserve 2-decimal format
|
|
375
|
+
// Examples: $1,250.00 → $1,250 | $1,000.10 → $1,000.10 | $13.40 → $13.40
|
|
376
|
+
return formatted.replace(/\.00$/u, '');
|
|
377
|
+
}
|
|
378
|
+
// Standard: Strip all trailing zeros after decimal point
|
|
379
|
+
// Examples: $1,250.00 → $1,250 | $100.0 → $100 | $10.5 → $10.5 | $1.234 → $1.234
|
|
380
|
+
return formatted.replace(/(\.\d*?)0+$/u, '$1').replace(/\.$/u, '');
|
|
381
|
+
}
|
|
382
|
+
return formatted;
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Formats position size with variable decimal precision based on magnitude or asset-specific decimals
|
|
386
|
+
* Removes trailing zeros to match task requirements
|
|
387
|
+
*
|
|
388
|
+
* @param size - Raw position size value
|
|
389
|
+
* @param szDecimals - Optional asset-specific decimal precision from Hyperliquid metadata (e.g., BTC=5, ETH=4, DOGE=1)
|
|
390
|
+
* @returns Format varies by size or uses asset-specific decimals, with trailing zeros removed:
|
|
391
|
+
* If szDecimals provided: Uses exact decimals (e.g., 0.00009 BTC with szDecimals=5 => "0.00009")
|
|
392
|
+
* Otherwise falls back to magnitude-based logic:
|
|
393
|
+
* - Size < 0.01: Up to 6 decimals (e.g., "0.00009" not "0.000090")
|
|
394
|
+
* - Size < 1: Up to 4 decimals (e.g., "0.0024" not "0.002400")
|
|
395
|
+
* - Size >= 1: Up to 2 decimals (e.g., "44" not "44.00")
|
|
396
|
+
* @example formatPositionSize(0.00009, 5) => "0.00009" (uses szDecimals)
|
|
397
|
+
* @example formatPositionSize(44.00, 1) => "44" (uses szDecimals, trailing zeros removed)
|
|
398
|
+
* @example formatPositionSize(0.0024) => "0.0024" (no szDecimals, uses magnitude logic)
|
|
399
|
+
* @example formatPositionSize(44.00) => "44" (no szDecimals, uses magnitude logic)
|
|
400
|
+
*/
|
|
401
|
+
export const formatPositionSize = (size, szDecimals) => {
|
|
402
|
+
const value = typeof size === 'string' ? parseFloat(size) : size;
|
|
403
|
+
if (isNaN(value) || value === 0) {
|
|
404
|
+
return '0';
|
|
405
|
+
}
|
|
406
|
+
// Use asset-specific decimals if provided (Hyperliquid metadata)
|
|
407
|
+
if (szDecimals !== undefined) {
|
|
408
|
+
return value.toFixed(szDecimals).replace(/\.?0+$/u, '');
|
|
409
|
+
}
|
|
410
|
+
// Fallback: magnitude-based decimal logic for backwards compatibility
|
|
411
|
+
const abs = Math.abs(value);
|
|
412
|
+
let formatted;
|
|
413
|
+
if (abs < 0.01) {
|
|
414
|
+
// For very small numbers, use more decimal places
|
|
415
|
+
formatted = value.toFixed(6);
|
|
416
|
+
}
|
|
417
|
+
else if (abs < 1) {
|
|
418
|
+
// For small numbers, use 4 decimal places
|
|
419
|
+
formatted = value.toFixed(4);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
// For normal numbers, use 2 decimal places
|
|
423
|
+
formatted = value.toFixed(2);
|
|
424
|
+
}
|
|
425
|
+
// Remove trailing zeros and unnecessary decimal point
|
|
426
|
+
return formatted.replace(/\.?0+$/u, '');
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Formats a PnL (Profit and Loss) value with sign prefix
|
|
430
|
+
*
|
|
431
|
+
* @param pnl - Raw numeric PnL value (positive for profit, negative for loss)
|
|
432
|
+
* @returns Format: "+$X,XXX.XX" or "-$X,XXX.XX" (always shows sign, 2 decimals)
|
|
433
|
+
* @example formatPnl(1234.56) => "+$1,234.56"
|
|
434
|
+
* @example formatPnl(-500) => "-$500.00"
|
|
435
|
+
* @example formatPnl(0) => "+$0.00"
|
|
436
|
+
*/
|
|
437
|
+
export const formatPnl = (pnl) => {
|
|
438
|
+
const value = typeof pnl === 'string' ? parseFloat(pnl) : pnl;
|
|
439
|
+
if (isNaN(value)) {
|
|
440
|
+
return PERPS_CONSTANTS.ZeroAmountDetailedDisplay;
|
|
441
|
+
}
|
|
442
|
+
const formatted = _formatCurrency(Math.abs(value), 'USD', {
|
|
443
|
+
minimumFractionDigits: 2,
|
|
444
|
+
maximumFractionDigits: 2,
|
|
445
|
+
});
|
|
446
|
+
return value >= 0 ? `+${formatted}` : `-${formatted}`;
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Formats a percentage value with sign prefix
|
|
450
|
+
*
|
|
451
|
+
* @param value - Raw percentage value (e.g., 5.25 for 5.25%, not 0.0525)
|
|
452
|
+
* @param decimals - Number of decimal places to show (default: 2)
|
|
453
|
+
* @returns Format: "+X.XX%" or "-X.XX%" (always shows sign, 2 decimals)
|
|
454
|
+
* @example formatPercentage(5.25) => "+5.25%"
|
|
455
|
+
* @example formatPercentage(-2.75) => "-2.75%"
|
|
456
|
+
* @example formatPercentage(0) => "+0.00%"
|
|
457
|
+
*/
|
|
458
|
+
export const formatPercentage = (value, decimals = 2) => {
|
|
459
|
+
const parsed = typeof value === 'string' ? parseFloat(value) : value;
|
|
460
|
+
if (isNaN(parsed)) {
|
|
461
|
+
return '0.00%';
|
|
462
|
+
}
|
|
463
|
+
return `${parsed >= 0 ? '+' : ''}${parsed.toFixed(decimals)}%`;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* Formats funding rate for display
|
|
467
|
+
*
|
|
468
|
+
* @param value - Raw funding rate value (decimal, not percentage)
|
|
469
|
+
* @param options - Optional formatting options
|
|
470
|
+
* @param options.showZero - Whether to return zero display value for zero/undefined (default: true)
|
|
471
|
+
* @returns Formatted funding rate as percentage string
|
|
472
|
+
* @example formatFundingRate(0.0005) => "0.0500%"
|
|
473
|
+
* @example formatFundingRate(-0.0001) => "-0.0100%"
|
|
474
|
+
* @example formatFundingRate(undefined) => "0.0000%"
|
|
475
|
+
*/
|
|
476
|
+
export const formatFundingRate = (value, options) => {
|
|
477
|
+
const showZero = options?.showZero ?? true;
|
|
478
|
+
if (value === undefined || value === null) {
|
|
479
|
+
return showZero ? FUNDING_RATE_CONFIG.ZeroDisplay : '';
|
|
480
|
+
}
|
|
481
|
+
const percentage = value * FUNDING_RATE_CONFIG.PercentageMultiplier;
|
|
482
|
+
const formatted = percentage.toFixed(FUNDING_RATE_CONFIG.Decimals);
|
|
483
|
+
// Check if the result is effectively zero
|
|
484
|
+
if (showZero && parseFloat(formatted) === 0) {
|
|
485
|
+
return FUNDING_RATE_CONFIG.ZeroDisplay;
|
|
486
|
+
}
|
|
487
|
+
return `${formatted}%`;
|
|
488
|
+
};
|
|
489
|
+
//# sourceMappingURL=perpsFormatters.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perpsFormatters.mjs","sourceRoot":"","sources":["../../src/utils/perpsFormatters.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Portable perps decimal formatters.\n *\n * These are the canonical implementations, exported from the controller so\n * extension and any future consumer can import them directly.\n * No mobile-specific imports — safe to sync to Core.\n *\n * Intl.NumberFormat instances are cached in a module-level Map keyed by\n * serialized options, avoiding repeated construction costs.\n */\nimport {\n DECIMAL_PRECISION_CONFIG,\n FUNDING_RATE_CONFIG,\n PERPS_CONSTANTS,\n} from '../constants/perpsConfig';\n\n// Module-level Intl.NumberFormat cache (keyed by serialized options).\nconst _fmtCache = new Map<string, Intl.NumberFormat>();\n\nfunction _formatCurrency(\n value: number,\n currency: string,\n opts: { minimumFractionDigits: number; maximumFractionDigits: number },\n): string {\n const key = `${currency}:${opts.minimumFractionDigits}:${opts.maximumFractionDigits}`;\n let formatter = _fmtCache.get(key);\n if (!formatter) {\n formatter = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n currencyDisplay: 'narrowSymbol',\n minimumFractionDigits: opts.minimumFractionDigits,\n maximumFractionDigits: opts.maximumFractionDigits,\n });\n _fmtCache.set(key, formatter);\n }\n return formatter.format(value);\n}\n\n/**\n * Internal equivalent of the mobile formatWithThreshold utility.\n * Formats a currency value, returning \"<$X.XX\" for values below threshold.\n *\n * @param amount - The numeric amount to format.\n * @param threshold - The threshold below which the \"<\" prefix is shown.\n * @param options - Intl formatting options.\n * @param options.currency - ISO 4217 currency code.\n * @param options.minimumFractionDigits - Minimum decimal digits.\n * @param options.maximumFractionDigits - Maximum decimal digits.\n * @returns Formatted currency string.\n */\nfunction _formatWithThreshold(\n amount: number,\n threshold: number,\n options: {\n currency: string;\n minimumFractionDigits: number;\n maximumFractionDigits: number;\n },\n): string {\n const formatOpts = {\n minimumFractionDigits: options.minimumFractionDigits,\n maximumFractionDigits: options.maximumFractionDigits,\n currencyDisplay: 'narrowSymbol' as const,\n };\n if (amount === 0) {\n return _formatCurrency(0, options.currency, formatOpts);\n }\n return Math.abs(amount) < threshold\n ? `<${_formatCurrency(threshold, options.currency, formatOpts)}`\n : _formatCurrency(amount, options.currency, formatOpts);\n}\n\n/**\n * Price threshold constants for PRICE_RANGES_UNIVERSAL\n * These define the boundaries between different formatting ranges\n */\nexport const PRICE_THRESHOLD = {\n /** Very high values boundary (> $100k) */\n VERY_HIGH: 100_000,\n /** High values boundary (> $10k) */\n HIGH: 10_000,\n /** Large values boundary (> $1k) */\n LARGE: 1_000,\n /** Medium values boundary (> $100) */\n MEDIUM: 100,\n /** Medium-low values boundary (> $10) */\n MEDIUM_LOW: 10,\n /** Low values boundary (>= $0.01) */\n LOW: 0.01,\n /**\n * Very small values threshold (< $0.01)\n * This is the minimum value for formatWithThreshold and should align with\n * the 6 decimal maximum (0.000001 is the smallest representable value)\n */\n VERY_SMALL: 0.000001,\n} as const;\n\n/**\n * Configuration for a specific number range formatting\n */\nexport type FiatRangeConfig = {\n /**\n * The condition to match for this range (e.g., < 0.0001, < 1, >= 1000)\n * Function should return true if this config should be applied\n */\n condition: (value: number) => boolean;\n /** Minimum decimal places for this range */\n minimumDecimals: number;\n /** Maximum decimal places for this range */\n maximumDecimals: number;\n /** Optional threshold for formatWithThreshold (defaults to the range boundary) */\n threshold?: number;\n /** Optional significant digits for this range (overrides decimal places when set) */\n significantDigits?: number;\n /** Optional custom formatting logic for this range */\n customFormat?: (value: number, locale: string, currency: string) => string;\n /** Optional flag to strip trailing zeros for this range (overrides global stripTrailingZeros option) */\n stripTrailingZeros?: boolean;\n /**\n * Optional flag for fiat-style stripping (only strips .00, preserves meaningful decimals like .10, .40)\n * When true, \"$1,250.00\" → \"$1,250\" but \"$1,250.10\" stays \"$1,250.10\"\n * When false (default), strips all trailing zeros: \"$1,250.10\" → \"$1,250.1\"\n */\n fiatStyleStripping?: boolean;\n};\n\n/**\n * Formats a number to a specific number of significant digits\n * Strips trailing zeros unless minDecimals requires them\n *\n * @param value - The numeric value to format\n * @param significantDigits - Number of significant digits to maintain\n * @param minDecimals - Minimum decimal places to show (may add zeros)\n * @param maxDecimals - Maximum decimal places allowed\n * @returns Formatted number with appropriate precision, trailing zeros removed\n */\nexport function formatWithSignificantDigits(\n value: number,\n significantDigits: number,\n minDecimals?: number,\n maxDecimals?: number,\n): { value: number; decimals: number } {\n // Handle special cases\n if (value === 0) {\n // Return zero with no trailing decimals by default (matches stripTrailingZeros behavior)\n // Can be overridden by explicit minDecimals if needed\n return { value: 0, decimals: minDecimals ?? 0 };\n }\n\n const absValue = Math.abs(value);\n\n // For numbers >= 1, calculate decimals based on magnitude to achieve target significant figures\n // This ensures consistent precision across different price ranges:\n // Examples with 4 significant figures:\n // $123,456.78 → $123,456.78 (≥$1000: 2 decimals minimum, 8 sig figs)\n // $456.12 → $456.12 (≥$10: 2 decimals minimum, 5 sig figs)\n // $56.123 → $56.123 (≥$10: 2 decimals minimum, 5 sig figs)\n // $5.123 → $5.123 ($1-$10: 3 decimals = 4 sig figs)\n // $2.801 → $2.801 ($1-$10: 3 decimals = 4 sig figs)\n // $1.234 → $1.234 ($1-$10: 3 decimals = 4 sig figs)\n if (absValue >= 1) {\n let targetDecimals: number;\n\n // Calculate decimals needed based on integer digits to achieve target significant figures\n // For $38.388 with 5 sig figs: 2 integer digits, need 3 decimals (3,8,3,8,8)\n // For $123.45 with 5 sig figs: 3 integer digits, need 2 decimals (1,2,3,4,5)\n const integerDigits = Math.floor(Math.log10(absValue)) + 1;\n const decimalsNeeded = significantDigits - integerDigits;\n targetDecimals = Math.max(decimalsNeeded, 0); // Can't have negative decimals\n\n // Apply explicit minimum decimals constraint if provided (for special cases)\n if (minDecimals !== undefined && targetDecimals < minDecimals) {\n targetDecimals = minDecimals;\n }\n\n // Apply maximum decimals constraint if specified\n const finalDecimals =\n maxDecimals === undefined\n ? targetDecimals\n : Math.min(targetDecimals, maxDecimals);\n\n // Round to prevent floating-point artifacts (e.g., 2.820000000000003 → 2.82)\n const roundedValue = Number(value.toFixed(finalDecimals));\n\n return {\n value: roundedValue,\n decimals: finalDecimals,\n };\n }\n\n // For numbers < 1, use toPrecision to limit to significantDigits\n // Examples: 0.1234, 0.01234 should show exactly 4 sig figs\n const precisionStr = absValue.toPrecision(significantDigits);\n const precisionNum = parseFloat(precisionStr);\n\n // Convert to string to count actual decimals after trailing zeros are removed\n const valueStr = precisionNum.toString();\n const [, decPart = ''] = valueStr.split('.');\n let actualDecimals = decPart.length;\n\n // Apply min/max decimal constraints\n if (minDecimals !== undefined && actualDecimals < minDecimals) {\n actualDecimals = minDecimals; // Will add zeros if needed\n }\n if (maxDecimals !== undefined && actualDecimals > maxDecimals) {\n actualDecimals = maxDecimals;\n }\n\n // Return the value with sign restored and decimal count\n return {\n value: value < 0 ? -precisionNum : precisionNum,\n decimals: actualDecimals,\n };\n}\n\n/**\n * Minimal view fiat range configuration\n * Uses fiat-style stripping for clean currency display\n * Strips only .00 to avoid partial decimals like $1,250.1\n */\nexport const PRICE_RANGES_MINIMAL_VIEW: FiatRangeConfig[] = [\n {\n // Large values (>= $1000): Strip .00 only ($5,000 not $5,000.00, but $5,000.10 stays)\n condition: (val: number) => Math.abs(val) >= PRICE_THRESHOLD.LARGE,\n minimumDecimals: 2,\n maximumDecimals: 2,\n threshold: PRICE_THRESHOLD.LARGE,\n stripTrailingZeros: true,\n fiatStyleStripping: true,\n },\n {\n // Small values (< $1000): Also use fiat-style stripping ($100 not $100.00, but $13.40 stays)\n condition: () => true,\n minimumDecimals: 2,\n maximumDecimals: 2,\n threshold: PRICE_THRESHOLD.LOW,\n stripTrailingZeros: true,\n fiatStyleStripping: true,\n },\n];\n\n/**\n * Universal price range configuration following comprehensive rules from rules-decimals.md\n *\n * Rules:\n * - Max 6 decimals across all ranges (Hyperliquid limit)\n * - Strip trailing zeros by default\n * - Use |v| (absolute value) for conditions\n *\n * Significant digits by range:\n * - > $100,000: 6 sig digs\n * - $100,000 > x > $0.01: 5 sig digs\n * - < $0.01: 4 sig digs\n *\n * Decimal limits by price range:\n * - |v| > 10,000: min 0, max 0 decimals; 5 sig digs (6 if >100k)\n * - |v| > 1,000: min 0, max 1 decimal; 5 sig digs\n * - |v| > 100: min 0, max 2 decimals; 5 sig digs\n * - |v| > 10: min 0, max 4 decimals; 5 sig digs\n * - |v| ≥ 0.01: 5 sig digs, min 2, max 6 decimals\n * - |v| < 0.01: 4 sig digs, min 2, max 6 decimals\n *\n * Examples:\n * - $123,456.78 → $123,457 (>$10k: 0 decimals, 6 sig figs)\n * - $12,345.67 → $12,346 (>$10k: 0 decimals, 5 sig figs)\n * - $1,234.56 → $1,234.6 ($1k-$10k: 1 decimal, 5 sig figs)\n * - $123.456 → $123.46 ($100-$1k: 2 decimals, 5 sig figs)\n * - $12.34567 → $12.346 ($10-$100: 4 decimals, 5 sig figs)\n * - $1.3445555 → $1.3446 (≥$0.01: 5 sig figs)\n * - $0.333333 → $0.33333 (≥$0.01: 5 sig figs)\n * - $0.004236 → $0.004236 (<$0.01: 4 sig figs, max 6 decimals)\n * - $0.0000006 → $0.000001 (<$0.01: 4 sig figs, rounds with max 6 decimals)\n */\nexport const PRICE_RANGES_UNIVERSAL: FiatRangeConfig[] = [\n {\n // Very high values (> $100,000): No decimals, 6 significant figures\n // Ex: $123,456.78 → $123,457\n condition: (val) => Math.abs(val) > PRICE_THRESHOLD.VERY_HIGH,\n minimumDecimals: 0,\n maximumDecimals: 0,\n significantDigits: 6,\n threshold: PRICE_THRESHOLD.VERY_HIGH,\n },\n {\n // High values ($10,000-$100,000]: No decimals, 5 significant figures\n // Ex: $12,345.67 → $12,346\n condition: (val) => Math.abs(val) > PRICE_THRESHOLD.HIGH,\n minimumDecimals: 0,\n maximumDecimals: 0,\n significantDigits: 5,\n threshold: PRICE_THRESHOLD.HIGH,\n },\n {\n // Large values ($1,000-$10,000]: Max 1 decimal, 5 significant figures\n // Ex: $1,234.56 → $1,234.6\n condition: (val) => Math.abs(val) > PRICE_THRESHOLD.LARGE,\n minimumDecimals: 0,\n maximumDecimals: 1,\n significantDigits: 5,\n threshold: PRICE_THRESHOLD.LARGE,\n },\n {\n // Medium values ($100-$1,000]: Max 2 decimals, 5 significant figures\n // Ex: $123.456 → $123.46\n condition: (val) => Math.abs(val) > PRICE_THRESHOLD.MEDIUM,\n minimumDecimals: 0,\n maximumDecimals: 2,\n significantDigits: 5,\n threshold: PRICE_THRESHOLD.MEDIUM,\n },\n {\n // Medium-low values ($10-$100]: Max 4 decimals, 5 significant figures\n // Ex: $12.34567 → $12.346\n condition: (val) => Math.abs(val) > PRICE_THRESHOLD.MEDIUM_LOW,\n minimumDecimals: 0,\n maximumDecimals: 4,\n significantDigits: 5,\n threshold: PRICE_THRESHOLD.MEDIUM_LOW,\n },\n {\n // Low values ($0.01-$10]: 5 significant figures, min 2 max MAX_PRICE_DECIMALS decimals\n // Ex: $1.3445555 → $1.3446 | $0.333333 → $0.33333\n condition: (val) => Math.abs(val) >= PRICE_THRESHOLD.LOW,\n significantDigits: 5,\n minimumDecimals: 2,\n maximumDecimals: DECIMAL_PRECISION_CONFIG.MaxPriceDecimals,\n threshold: PRICE_THRESHOLD.LOW,\n },\n {\n // Very small values (< $0.01): 4 significant figures, min 2 max MAX_PRICE_DECIMALS decimals\n // Ex: $0.004236 → $0.004236 | $0.0000006 → $0.000001\n condition: () => true,\n significantDigits: 4,\n minimumDecimals: 2,\n maximumDecimals: DECIMAL_PRECISION_CONFIG.MaxPriceDecimals,\n threshold: PRICE_THRESHOLD.VERY_SMALL,\n },\n];\n\n/**\n * Formats a balance value as USD currency with appropriate decimal places\n *\n * @param balance - Raw numeric balance value (e.g., 1234.56, not token minimal denomination)\n * @param options - Optional formatting options\n * @param options.minimumDecimals - Global minimum decimal places (overrides range configs)\n * @param options.maximumDecimals - Global maximum decimal places (overrides range configs)\n * @param options.significantDigits - Global significant digits (overrides decimal settings when set)\n * @param options.ranges - Custom range configurations (defaults to PRICE_RANGES_MINIMAL_VIEW)\n * @param options.currency - Currency code (default: 'USD')\n * @param options.locale - Locale for formatting (default: 'en-US')\n * @param options.stripTrailingZeros - Strip trailing zeros from output (default: false via PRICE_RANGES_MINIMAL_VIEW). When true, overrides minimumDecimals constraint.\n * @returns Formatted currency string with variable decimals based on configured ranges\n * @example\n * // Using defaults (preserves trailing zeros for fiat)\n * formatPerpsFiat(1234.56) => \"$1,234.56\"\n * formatPerpsFiat(1250.00) => \"$1,250.00\" // Trailing zeros preserved\n * formatPerpsFiat(50000) => \"$50,000.00\" // Trailing zeros preserved\n *\n * // Stripping trailing zeros when needed (e.g., for crypto)\n * formatPerpsFiat(1250, { stripTrailingZeros: true }) => \"$1,250\"\n *\n * // With custom ranges\n * formatPerpsFiat(0.00001, {\n * ranges: [\n * { condition: (v) => v < 0.001, minimumDecimals: 6, maximumDecimals: 8 },\n * { condition: () => true, minimumDecimals: 2, maximumDecimals: 2 }\n * ]\n * }) => \"$0.00001\" // Trailing zero stripped\n *\n * // With significant digits\n * formatPerpsFiat(1234.56789, { significantDigits: 5 }) => \"$1,234.6\"\n * formatPerpsFiat(0.0001234, { significantDigits: 3 }) => \"$0.000123\"\n */\nexport const formatPerpsFiat = (\n balance: string | number,\n options?: {\n minimumDecimals?: number;\n maximumDecimals?: number;\n significantDigits?: number;\n ranges?: FiatRangeConfig[];\n currency?: string;\n locale?: string;\n stripTrailingZeros?: boolean;\n },\n): string => {\n const value = typeof balance === 'string' ? parseFloat(balance) : balance;\n const currency = options?.currency ?? 'USD';\n\n let formatted: string;\n\n if (isNaN(value)) {\n // Return placeholder for invalid values to avoid confusion with actual $0 values\n return PERPS_CONSTANTS.FallbackPriceDisplay;\n }\n\n // Use custom ranges or defaults\n const ranges = options?.ranges ?? PRICE_RANGES_MINIMAL_VIEW;\n\n // Find the first matching range configuration\n const rangeConfig = ranges.find((range) => range.condition(value));\n\n if (rangeConfig) {\n // Check for significant digits (global or range-specific)\n const sigDigits =\n options?.significantDigits ?? rangeConfig.significantDigits;\n\n // If significant digits are specified, use them\n if (sigDigits) {\n // Get min/max decimals (global overrides range, range overrides default)\n const minDecimals =\n options?.minimumDecimals ?? rangeConfig.minimumDecimals;\n const maxDecimals =\n options?.maximumDecimals ?? rangeConfig.maximumDecimals;\n\n // Calculate appropriate formatting based on significant digits\n const { value: formattedValue, decimals } = formatWithSignificantDigits(\n value,\n sigDigits,\n minDecimals,\n maxDecimals,\n );\n\n // Format with the calculated decimal places\n formatted = _formatWithThreshold(\n formattedValue,\n rangeConfig.threshold ?? 0.01,\n {\n currency,\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n },\n );\n } else {\n // Standard decimal-based formatting (existing logic)\n const minDecimals =\n options?.minimumDecimals ?? rangeConfig.minimumDecimals;\n const maxDecimals =\n options?.maximumDecimals ?? rangeConfig.maximumDecimals;\n\n // Use custom formatting if provided\n if (rangeConfig.customFormat) {\n formatted = rangeConfig.customFormat(\n value,\n options?.locale ?? 'en-US',\n currency,\n );\n } else {\n // Use standard formatting with threshold\n formatted = _formatWithThreshold(value, rangeConfig.threshold ?? 0.01, {\n currency,\n minimumFractionDigits: minDecimals,\n maximumFractionDigits: maxDecimals,\n });\n }\n }\n } else {\n // Fallback if no range matches (shouldn't happen with proper default config)\n const fallbackMin = options?.minimumDecimals ?? 2;\n const fallbackMax = options?.maximumDecimals ?? 2;\n formatted = _formatWithThreshold(value, 0.01, {\n currency,\n minimumFractionDigits: fallbackMin,\n maximumFractionDigits: fallbackMax,\n });\n }\n\n // Post-process: strip trailing zeros unless explicitly disabled\n // Priority: explicit options.stripTrailingZeros false > rangeConfig > options default > true\n // If options.stripTrailingZeros is explicitly false, skip stripping entirely\n if (options?.stripTrailingZeros === false) {\n return formatted;\n }\n\n // Otherwise check range config or default to true\n const shouldStrip =\n rangeConfig?.stripTrailingZeros ?? options?.stripTrailingZeros ?? true;\n\n if (shouldStrip) {\n // Check if fiat-style stripping is enabled (only strips .00)\n const useFiatStyle = rangeConfig?.fiatStyleStripping ?? false;\n\n if (useFiatStyle) {\n // Fiat-style: Only strip .00 (no meaningful decimals), preserve 2-decimal format\n // Examples: $1,250.00 → $1,250 | $1,000.10 → $1,000.10 | $13.40 → $13.40\n return formatted.replace(/\\.00$/u, '');\n }\n // Standard: Strip all trailing zeros after decimal point\n // Examples: $1,250.00 → $1,250 | $100.0 → $100 | $10.5 → $10.5 | $1.234 → $1.234\n return formatted.replace(/(\\.\\d*?)0+$/u, '$1').replace(/\\.$/u, '');\n }\n\n return formatted;\n};\n\n/**\n * Formats position size with variable decimal precision based on magnitude or asset-specific decimals\n * Removes trailing zeros to match task requirements\n *\n * @param size - Raw position size value\n * @param szDecimals - Optional asset-specific decimal precision from Hyperliquid metadata (e.g., BTC=5, ETH=4, DOGE=1)\n * @returns Format varies by size or uses asset-specific decimals, with trailing zeros removed:\n * If szDecimals provided: Uses exact decimals (e.g., 0.00009 BTC with szDecimals=5 => \"0.00009\")\n * Otherwise falls back to magnitude-based logic:\n * - Size < 0.01: Up to 6 decimals (e.g., \"0.00009\" not \"0.000090\")\n * - Size < 1: Up to 4 decimals (e.g., \"0.0024\" not \"0.002400\")\n * - Size >= 1: Up to 2 decimals (e.g., \"44\" not \"44.00\")\n * @example formatPositionSize(0.00009, 5) => \"0.00009\" (uses szDecimals)\n * @example formatPositionSize(44.00, 1) => \"44\" (uses szDecimals, trailing zeros removed)\n * @example formatPositionSize(0.0024) => \"0.0024\" (no szDecimals, uses magnitude logic)\n * @example formatPositionSize(44.00) => \"44\" (no szDecimals, uses magnitude logic)\n */\nexport const formatPositionSize = (\n size: string | number,\n szDecimals?: number,\n): string => {\n const value = typeof size === 'string' ? parseFloat(size) : size;\n\n if (isNaN(value) || value === 0) {\n return '0';\n }\n\n // Use asset-specific decimals if provided (Hyperliquid metadata)\n if (szDecimals !== undefined) {\n return value.toFixed(szDecimals).replace(/\\.?0+$/u, '');\n }\n\n // Fallback: magnitude-based decimal logic for backwards compatibility\n const abs = Math.abs(value);\n let formatted: string;\n\n if (abs < 0.01) {\n // For very small numbers, use more decimal places\n formatted = value.toFixed(6);\n } else if (abs < 1) {\n // For small numbers, use 4 decimal places\n formatted = value.toFixed(4);\n } else {\n // For normal numbers, use 2 decimal places\n formatted = value.toFixed(2);\n }\n\n // Remove trailing zeros and unnecessary decimal point\n return formatted.replace(/\\.?0+$/u, '');\n};\n\n/**\n * Formats a PnL (Profit and Loss) value with sign prefix\n *\n * @param pnl - Raw numeric PnL value (positive for profit, negative for loss)\n * @returns Format: \"+$X,XXX.XX\" or \"-$X,XXX.XX\" (always shows sign, 2 decimals)\n * @example formatPnl(1234.56) => \"+$1,234.56\"\n * @example formatPnl(-500) => \"-$500.00\"\n * @example formatPnl(0) => \"+$0.00\"\n */\nexport const formatPnl = (pnl: string | number): string => {\n const value = typeof pnl === 'string' ? parseFloat(pnl) : pnl;\n\n if (isNaN(value)) {\n return PERPS_CONSTANTS.ZeroAmountDetailedDisplay;\n }\n\n const formatted = _formatCurrency(Math.abs(value), 'USD', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n\n return value >= 0 ? `+${formatted}` : `-${formatted}`;\n};\n\n/**\n * Formats a percentage value with sign prefix\n *\n * @param value - Raw percentage value (e.g., 5.25 for 5.25%, not 0.0525)\n * @param decimals - Number of decimal places to show (default: 2)\n * @returns Format: \"+X.XX%\" or \"-X.XX%\" (always shows sign, 2 decimals)\n * @example formatPercentage(5.25) => \"+5.25%\"\n * @example formatPercentage(-2.75) => \"-2.75%\"\n * @example formatPercentage(0) => \"+0.00%\"\n */\nexport const formatPercentage = (\n value: string | number,\n decimals: number = 2,\n): string => {\n const parsed = typeof value === 'string' ? parseFloat(value) : value;\n\n if (isNaN(parsed)) {\n return '0.00%';\n }\n\n return `${parsed >= 0 ? '+' : ''}${parsed.toFixed(decimals)}%`;\n};\n\n/**\n * Formats funding rate for display\n *\n * @param value - Raw funding rate value (decimal, not percentage)\n * @param options - Optional formatting options\n * @param options.showZero - Whether to return zero display value for zero/undefined (default: true)\n * @returns Formatted funding rate as percentage string\n * @example formatFundingRate(0.0005) => \"0.0500%\"\n * @example formatFundingRate(-0.0001) => \"-0.0100%\"\n * @example formatFundingRate(undefined) => \"0.0000%\"\n */\nexport const formatFundingRate = (\n value?: number | null,\n options?: { showZero?: boolean },\n): string => {\n const showZero = options?.showZero ?? true;\n\n if (value === undefined || value === null) {\n return showZero ? FUNDING_RATE_CONFIG.ZeroDisplay : '';\n }\n\n const percentage = value * FUNDING_RATE_CONFIG.PercentageMultiplier;\n const formatted = percentage.toFixed(FUNDING_RATE_CONFIG.Decimals);\n\n // Check if the result is effectively zero\n if (showZero && parseFloat(formatted) === 0) {\n return FUNDING_RATE_CONFIG.ZeroDisplay;\n }\n\n return `${formatted}%`;\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/perps-controller",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0-preview-548bdd1d9",
|
|
4
4
|
"description": "Controller for perpetual trading functionality in MetaMask",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@metamask/abi-utils": "^2.0.3",
|
|
60
|
-
"@metamask/base-controller": "^9.0
|
|
60
|
+
"@metamask/base-controller": "^9.1.0",
|
|
61
61
|
"@metamask/controller-utils": "^11.20.0",
|
|
62
62
|
"@metamask/messenger": "^1.1.1",
|
|
63
63
|
"@metamask/utils": "^11.9.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@metamask/auto-changelog": "^6.0.0",
|
|
72
72
|
"@metamask/geolocation-controller": "^0.1.2",
|
|
73
73
|
"@metamask/keyring-controller": "^25.2.0",
|
|
74
|
-
"@metamask/keyring-internal-api": "^10.
|
|
74
|
+
"@metamask/keyring-internal-api": "^10.1.0",
|
|
75
75
|
"@metamask/network-controller": "^30.0.1",
|
|
76
76
|
"@metamask/profile-sync-controller": "^28.0.2",
|
|
77
77
|
"@metamask/remote-feature-flag-controller": "^4.2.0",
|