Skip to content

Commit 7362ef0

Browse files
authored
feat(perps): Export perps decimal formatters from UI utils to controller (#28538)
## **Description** Moves six perps decimal formatters out of the mobile UI layer and into `@metamask/perps-controller` so the extension (and any future consumer) can import them without duplicating logic. **What changed:** - `app/controllers/perps/utils/perpsFormatters.ts` — new canonical home for `formatPerpsFiat`, `formatPositionSize`, `formatPnl`, `formatPercentage`, `formatFundingRate`, `PRICE_RANGES_UNIVERSAL`, `PRICE_RANGES_MINIMAL_VIEW`, `PRICE_THRESHOLD`, `formatWithSignificantDigits`, and `FiatRangeConfig` - `FUNDING_RATE_CONFIG` promoted from UI constants to controller constants (required by `formatFundingRate`) - `app/components/UI/Perps/utils/formatUtils.ts` — implementations replaced with re-exports from `@metamask/perps-controller`; no call-site changes needed `Intl.NumberFormat` instances are cached via a module-level `Map` (keyed by serialized options) instead of the mobile-specific `getIntlNumberFormatter`. This avoids a transitive `@metamask/snaps-utils` import that crashes Jest when `@metamask/assets-controllers` is loaded in test environments. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2870 ## **Manual testing steps** ```gherkin Feature: Perps market data rendering after formatter refactor Scenario: user views BTC market detail Given the wallet is unlocked And the perps feature is enabled When user navigates to the Perps market list Then markets are displayed with prices When user selects BTC Then the BTC market detail screen loads with a formatted price ``` ## **Screenshots/Recordings** ### **Before** Formatters defined in `app/components/UI/Perps/utils/formatUtils.ts` — not accessible outside mobile. ### **After** Formatters exported from `@metamask/perps-controller`. Screenshot captured by validation recipe showing BTC market detail renders correctly post-refactor. ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. ## **Validation Recipe** <details> <summary>recipe.json — automated validation</summary> ```json { "title": "Verify perps formatters exported from controller — markets still render", "description": "Code refactor: formatPerpsFiat, formatPositionSize, formatPnl, formatPercentage, formatFundingRate, PRICE_RANGES_UNIVERSAL, PRICE_RANGES_MINIMAL_VIEW now exported from @metamask/perps-controller. Verifies the app still renders perps market data (uses these formatters) after the move.", "validate": { "workflow": { "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"], "entry": "check-markets", "nodes": { "check-markets": { "action": "eval_ref", "ref": "perps/markets", "assert": { "operator": "length_gt", "value": 0 }, "next": "navigate-to-btc" }, "navigate-to-btc": { "action": "call", "ref": "perps/market-discovery", "params": { "symbol": "BTC" }, "next": "screenshot-evidence" }, "screenshot-evidence": { "action": "screenshot", "filename": "evidence-market-detail.png", "next": "done" }, "done": { "action": "end", "status": "pass" } } } } } ``` Result: **3/3 nodes PASS** — markets loaded, BTC detail rendered with formatted prices. </details>
1 parent 68bf844 commit 7362ef0

10 files changed

Lines changed: 970 additions & 586 deletions

File tree

app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ jest.mock('../../../../../util/trace', () => ({
221221
}));
222222

223223
jest.mock('@metamask/perps-controller', () => ({
224+
...jest.requireActual('@metamask/perps-controller'),
224225
PERPS_EVENT_PROPERTY: {
225226
SCREEN_TYPE: 'screen_type',
226227
SOURCE: 'source',

app/components/UI/Perps/constants/perpsConfig.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,7 @@ export const LIMIT_PRICE_CONFIG = {
129129
ShortPresets: [1, 2], // Sell above market for short orders
130130
} as const;
131131

132-
/**
133-
* Funding rate display configuration
134-
* Controls how funding rates are formatted and displayed across the app
135-
*/
136-
export const FUNDING_RATE_CONFIG = {
137-
// Number of decimal places to display for funding rates
138-
Decimals: 4,
139-
// Default display value when funding rate is zero or unavailable
140-
ZeroDisplay: '0.0000%',
141-
// Multiplier to convert decimal funding rate to percentage
142-
PercentageMultiplier: 100,
143-
} as const;
132+
export { FUNDING_RATE_CONFIG } from '@metamask/perps-controller';
144133

145134
export const PERPS_GTM_WHATS_NEW_MODAL = 'perps-gtm-whats-new-modal';
146135
export const PERPS_GTM_MODAL_ENGAGE = 'engage';

app/components/UI/Perps/utils/formatUtils.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -571,20 +571,20 @@ describe('formatUtils', () => {
571571
formatPerpsFiat(0.0000012, { ranges: PRICE_RANGES_UNIVERSAL }),
572572
).toBe('$0.000001'); // 4 sig figs: 1,2,0,0 → rounds to $0.000001
573573

574-
// Very small value that rounds to 0 after 6 decimal cap
574+
// Very small value below VERY_SMALL threshold (0.000001) → <$0 prefix
575575
expect(
576576
formatPerpsFiat(0.00000045, { ranges: PRICE_RANGES_UNIVERSAL }),
577-
).toBe('$0'); // Rounds to 0 with 6 decimal cap
577+
).toBe('<$0'); // Below threshold: shows <$X prefix
578578

579579
// Edge case: exactly at 6 decimal boundary
580580
expect(
581581
formatPerpsFiat(0.000001, { ranges: PRICE_RANGES_UNIVERSAL }),
582582
).toBe('$0.000001'); // 1 sig fig at boundary
583583

584-
// Example from rules-decimals.md: 0.0000004 → 0 (rounds down with cap)
584+
// Example from rules-decimals.md: 0.0000004 → below threshold → <$0 prefix
585585
expect(
586586
formatPerpsFiat(0.0000004, { ranges: PRICE_RANGES_UNIVERSAL }),
587-
).toBe('$0');
587+
).toBe('<$0');
588588
});
589589

590590
describe('PRICE_RANGES_UNIVERSAL boundary testing', () => {
@@ -1212,23 +1212,23 @@ describe('formatUtils', () => {
12121212
priceRange: '$0.00001-$0.0001',
12131213
expected4SF: '$0.000012',
12141214
expectedDetailed: '$0.00001234',
1215-
expectedMinimal: '$0',
1215+
expectedMinimal: '<$0.01',
12161216
expectedUserInput: '$0.000012345',
12171217
},
12181218
{
12191219
value: 0.000098765,
12201220
priceRange: '$0.00001-$0.0001',
12211221
expected4SF: '$0.000099',
12221222
expectedDetailed: '$0.00009876',
1223-
expectedMinimal: '$0',
1223+
expectedMinimal: '<$0.01',
12241224
expectedUserInput: '$0.000098765',
12251225
},
12261226
{
12271227
value: 0.00005,
12281228
priceRange: '$0.00001-$0.0001',
12291229
expected4SF: '$0.00005',
12301230
expectedDetailed: '$0.00005',
1231-
expectedMinimal: '$0',
1231+
expectedMinimal: '<$0.01',
12321232
expectedUserInput: '$0.00005',
12331233
},
12341234
// <$0.00001 range (<$0.01: 4 sig figs)
@@ -1237,15 +1237,15 @@ describe('formatUtils', () => {
12371237
priceRange: '<$0.00001',
12381238
expected4SF: '$0.000001',
12391239
expectedDetailed: '$0.00000123',
1240-
expectedMinimal: '$0',
1240+
expectedMinimal: '<$0.01',
12411241
expectedUserInput: '$0.00000123',
12421242
},
12431243
{
12441244
value: 0.00004321,
12451245
priceRange: '<$0.00001',
12461246
expected4SF: '$0.000043',
12471247
expectedDetailed: '$0.00004321',
1248-
expectedMinimal: '$0',
1248+
expectedMinimal: '<$0.01',
12491249
expectedUserInput: '$0.00004321',
12501250
},
12511251
// Edge cases

0 commit comments

Comments
 (0)