Skip to content

Commit dee9a46

Browse files
authored
fix(perps): Missing 1 decimal on price input when using preset on limit price (#27907)
## **Description** Limit price presets (Mid, Bid, Ask, -1%, -2%) hardcoded `formatWithSignificantDigits(value, 4)` — 4 significant digits. For XRP-range prices (~$2.34), this truncated values to 3 decimal places ($2.342) instead of the expected 4 ($2.3418). Fixed by using `DECIMAL_PRECISION_CONFIG.MaxSignificantFigures` (= 5), matching the HyperLiquid API limit and the `PRICE_RANGES_UNIVERSAL` display config. Also added testIDs to all preset buttons for automated testing. ## **Changelog** CHANGELOG entry: Fixed limit price preset buttons (Mid, Bid, Ask, percentage) truncating one decimal place for low-price assets like XRP ## **Related issues** Fixes: [TAT-2399](https://consensyssoftware.atlassian.net/browse/TAT-2399) ## **Manual testing steps** ```gherkin Feature: Limit price preset decimal precision Scenario: Mid preset shows correct decimals for XRP Given I am on the XRP Long Limit order screen When I open the limit price bottom sheet And I press the Mid preset button Then the limit price shows 4 decimal places (e.g., $2.3418) Scenario: All presets show correct decimals Given I am on the XRP Long Limit order screen When I press each preset (Mid, Bid, -1%, -2%) Then each preset value has 4 decimal places Scenario: Ask preset works for short orders Given I am on the XRP Short Limit order screen When I press the Ask preset button Then the limit price shows 4 decimal places ``` ## **Screenshots/Recordings** ### **Before** Bug confirmed via CDP eval: `formatWithSignificantDigits(2.3418, 4)` → `2.342` (3 decimals instead of 4) ### **After** https://github.com/user-attachments/assets/72b3617e-afdf-49c1-bbbb-2b96e176668d ## **Validation Recipe** <details> <summary>Automated validation recipe (validate-recipe.sh)</summary> ```json { "pr": "27907", "title": "Limit price presets use correct decimal precision (5 sig figs)", "jira": "TAT-2399", "acceptance_criteria": [ "Tapping any limit price preset populates the value with market-correct decimal precision", "Validated on XRP (reported case) and SOL (different price range)", "All five presets covered: Mid, Bid, Ask, -1%, -2%", "No regression to manual limit price entry" ], "validate": { "static": ["yarn lint:tsc"], "runtime": { "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"], "steps": [ {"id": "nav_xrp", "description": "Navigate to XRP market details", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "XRP"}}, {"id": "press_long", "action": "press", "test_id": "perps-market-details-long-button"}, {"id": "wait_form", "action": "wait_for", "test_id": "perps-order-header-order-type-button"}, {"id": "press_order_type", "action": "press", "test_id": "perps-order-header-order-type-button"}, {"id": "wait_type_sheet", "action": "wait_for", "test_id": "perps-order-type-limit"}, {"id": "press_limit", "action": "press", "test_id": "perps-order-type-limit"}, {"id": "wait_limit_form", "action": "wait_for", "test_id": "perps-order-view-limit-price-row"}, {"id": "press_price_row", "action": "press", "test_id": "perps-order-view-limit-price-row"}, {"id": "wait_price_sheet", "action": "wait_for", "test_id": "keypad-delete-button"}, {"id": "press_mid_xrp", "action": "press", "test_id": "perps-limit-price-preset-mid"}, {"id": "wait_mid", "action": "wait", "ms": 500}, {"id": "check_mid_xrp", "description": "Assert Mid preset >= 4 decimals for XRP", "action": "eval_sync", "expression": "...", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_bid_xrp", "action": "press", "test_id": "perps-limit-price-preset-bid"}, {"id": "check_bid_xrp", "description": "Assert Bid >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_pct_minus1_xrp", "action": "press", "test_id": "perps-limit-price-preset--1"}, {"id": "check_pct_minus1_xrp", "description": "Assert -1% >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_pct_minus2_xrp", "action": "press", "test_id": "perps-limit-price-preset--2"}, {"id": "check_pct_minus2_xrp", "description": "Assert -2% >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "nav_sol", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "SOL"}}, {"id": "check_mid_sol", "description": "SOL no-regression check", "action": "eval_sync", "assert": {"operator": "eq", "field": "isValid", "value": true}}, {"id": "nav_short_xrp", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "XRP"}}, {"id": "check_ask_xrp", "description": "Assert Ask >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}} ] } } } ``` Full recipe: `.task/fix/tat-2399-0325-1840/artifacts/recipe.json` </details> ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and Coding Standards - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches perps limit order price entry; while the change is small, it affects how preset prices are computed and could impact order placement values if incorrect. > > **Overview** > Fixes limit-price preset buttons (Mid/Bid/Ask and +/- % presets) to format using `DECIMAL_PRECISION_CONFIG.MaxSignificantFigures` instead of hardcoded 4 significant digits, preventing truncation for low-priced assets (e.g., XRP). > > Adds `testID`s for each preset button (including dynamic % presets) and extends `PerpsLimitPriceBottomSheet` tests to assert the correct decimal precision for XRP-range prices. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ecb689b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3e1b3d4 commit dee9a46

3 files changed

Lines changed: 81 additions & 5 deletions

File tree

app/components/UI/Perps/Perps.testIds.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,10 @@ export const PerpsOrderViewSelectorsIDs = {
551551
export const PerpsLimitPriceBottomSheetSelectorsIDs = {
552552
PRICE_DISPLAY: 'perps-limit-price-display',
553553
CONFIRM_BUTTON: 'perps-limit-price-confirm-button',
554+
PRESET_MID: 'perps-limit-price-preset-mid',
555+
PRESET_BID: 'perps-limit-price-preset-bid',
556+
PRESET_ASK: 'perps-limit-price-preset-ask',
557+
PRESET_PERCENT: 'perps-limit-price-preset-',
554558
};
555559

556560
// ========================================

app/components/UI/Perps/components/PerpsLimitPriceBottomSheet/PerpsLimitPriceBottomSheet.test.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,69 @@ describe('PerpsLimitPriceBottomSheet', () => {
528528
});
529529
});
530530

531+
describe('Preset decimal precision (TAT-2399)', () => {
532+
const xrpProps = {
533+
...defaultProps,
534+
asset: 'XRP',
535+
currentPrice: 2.3418,
536+
};
537+
538+
beforeEach(() => {
539+
const { usePerpsTopOfBook } = jest.requireMock('../../hooks/stream');
540+
usePerpsTopOfBook.mockReturnValue({
541+
bestBid: '2.3412',
542+
bestAsk: '2.3426',
543+
});
544+
});
545+
546+
it('Mid preset uses 5 significant figures (not 4) for XRP-range prices', () => {
547+
render(<PerpsLimitPriceBottomSheet {...xrpProps} direction="long" />);
548+
549+
const midButton = screen.getByText(
550+
'perps.order.limit_price_modal.mid_price',
551+
);
552+
fireEvent.press(midButton);
553+
554+
// With 5 sig figs: 2.3418 → 4 decimals (intDig=1, dec=4)
555+
// With 4 sig figs (bug): 2.3418 → 3 decimals (2.342)
556+
expect(screen.getByTestId('keypad-value')).toHaveTextContent('2.3418');
557+
});
558+
559+
it('Bid preset uses 5 significant figures for XRP-range prices', () => {
560+
render(<PerpsLimitPriceBottomSheet {...xrpProps} direction="long" />);
561+
562+
const bidButton = screen.getByText(
563+
'perps.order.limit_price_modal.bid_price',
564+
);
565+
fireEvent.press(bidButton);
566+
567+
// bestBid='2.3412' → parseFloat → 2.3412 → 5 sig figs → 4 decimals
568+
expect(screen.getByTestId('keypad-value')).toHaveTextContent('2.3412');
569+
});
570+
571+
it('Ask preset uses 5 significant figures for XRP-range prices', () => {
572+
render(<PerpsLimitPriceBottomSheet {...xrpProps} direction="short" />);
573+
574+
const askButton = screen.getByText(
575+
'perps.order.limit_price_modal.ask_price',
576+
);
577+
fireEvent.press(askButton);
578+
579+
// bestAsk='2.3426' → parseFloat → 2.3426 → 5 sig figs → 4 decimals
580+
expect(screen.getByTestId('keypad-value')).toHaveTextContent('2.3426');
581+
});
582+
583+
it('Percentage preset uses 5 significant figures for XRP-range prices', () => {
584+
render(<PerpsLimitPriceBottomSheet {...xrpProps} direction="long" />);
585+
586+
const pctButton = screen.getByText('-1%');
587+
fireEvent.press(pctButton);
588+
589+
// BigNumber mock returns base price (2.3418) → 5 sig figs → 4 decimals
590+
expect(screen.getByTestId('keypad-value')).toHaveTextContent('2.3418');
591+
});
592+
});
593+
531594
describe('Validation and Error States', () => {
532595
it('shows muted text style for placeholder limit price', () => {
533596
// Act

app/components/UI/Perps/components/PerpsLimitPriceBottomSheet/PerpsLimitPriceBottomSheet.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
PRICE_RANGES_UNIVERSAL,
2323
} from '../../utils/formatUtils';
2424
import {
25+
DECIMAL_PRECISION_CONFIG,
2526
getPerpsDisplaySymbol,
2627
PERPS_CONSTANTS,
2728
PERPS_EVENT_PROPERTY,
@@ -374,11 +375,15 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
374375
<View style={styles.percentageButtonsRow}>
375376
{/* Mid price button - uses currentPrice which is the mid price from allMids stream */}
376377
<TouchableOpacity
378+
testID={PerpsLimitPriceBottomSheetSelectorsIDs.PRESET_MID}
377379
style={styles.percentageButton}
378380
onPress={() => {
379381
if (currentPrice) {
380382
setLimitPrice(
381-
formatWithSignificantDigits(currentPrice, 4).value.toString(),
383+
formatWithSignificantDigits(
384+
currentPrice,
385+
DECIMAL_PRECISION_CONFIG.MaxSignificantFigures,
386+
).value.toString(),
382387
);
383388
setInputMethod(PERPS_EVENT_VALUE.INPUT_METHOD.PRESET);
384389
}
@@ -394,14 +399,15 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
394399
<>
395400
{/* Bid price button */}
396401
<TouchableOpacity
402+
testID={PerpsLimitPriceBottomSheetSelectorsIDs.PRESET_BID}
397403
style={styles.percentageButton}
398404
onPress={() => {
399405
const price = bidPrice || currentPriceData?.price;
400406
if (price) {
401407
setLimitPrice(
402408
formatWithSignificantDigits(
403409
parseFloat(price),
404-
4,
410+
DECIMAL_PRECISION_CONFIG.MaxSignificantFigures,
405411
).value.toString(),
406412
);
407413
setInputMethod(PERPS_EVENT_VALUE.INPUT_METHOD.PRESET);
@@ -417,6 +423,7 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
417423
{LIMIT_PRICE_CONFIG.LongPresets.map((percentage) => (
418424
<TouchableOpacity
419425
key={percentage}
426+
testID={`${PerpsLimitPriceBottomSheetSelectorsIDs.PRESET_PERCENT}${percentage}`}
420427
style={styles.percentageButton}
421428
onPress={() => {
422429
const calculatedPrice =
@@ -425,7 +432,7 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
425432
setLimitPrice(
426433
formatWithSignificantDigits(
427434
parseFloat(calculatedPrice),
428-
4,
435+
DECIMAL_PRECISION_CONFIG.MaxSignificantFigures,
429436
).value.toString(),
430437
);
431438
setInputMethod(
@@ -446,14 +453,15 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
446453
<>
447454
{/* Ask price button */}
448455
<TouchableOpacity
456+
testID={PerpsLimitPriceBottomSheetSelectorsIDs.PRESET_ASK}
449457
style={styles.percentageButton}
450458
onPress={() => {
451459
const price = askPrice || currentPriceData?.price;
452460
if (price) {
453461
setLimitPrice(
454462
formatWithSignificantDigits(
455463
parseFloat(price),
456-
4,
464+
DECIMAL_PRECISION_CONFIG.MaxSignificantFigures,
457465
).value.toString(),
458466
);
459467
setInputMethod(PERPS_EVENT_VALUE.INPUT_METHOD.PRESET);
@@ -469,6 +477,7 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
469477
{LIMIT_PRICE_CONFIG.ShortPresets.map((percentage) => (
470478
<TouchableOpacity
471479
key={percentage}
480+
testID={`${PerpsLimitPriceBottomSheetSelectorsIDs.PRESET_PERCENT}${percentage}`}
472481
style={styles.percentageButton}
473482
onPress={() => {
474483
const calculatedPrice =
@@ -477,7 +486,7 @@ const PerpsLimitPriceBottomSheet: React.FC<PerpsLimitPriceBottomSheetProps> = ({
477486
setLimitPrice(
478487
formatWithSignificantDigits(
479488
parseFloat(calculatedPrice),
480-
4,
489+
DECIMAL_PRECISION_CONFIG.MaxSignificantFigures,
481490
).value.toString(),
482491
);
483492
setInputMethod(

0 commit comments

Comments
 (0)