Skip to content

[pull] main from MetaMask:main#638

Merged
pull[bot] merged 1 commit intoReality2byte:mainfrom
MetaMask:main
Mar 28, 2026
Merged

[pull] main from MetaMask:main#638
pull[bot] merged 1 commit intoReality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Mar 28, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

## **Description**

The flip position confirmation sheet showed a fee estimate ~2x lower
than the actual fee charged. A flip order is 2x position size (close
current + open opposite), but the fee estimate was calculated on 1x
notional. A secondary bug in `TradingService` applied the combined fee
rate to 2x notional for the balance check, incorrectly blocking users
with sufficient balance.

## **Changelog**

CHANGELOG entry: Fixed flip position fee estimate being ~2x lower than
actual fee charged

## **Related issues**

Fixes:
[TAT-2418](https://consensyssoftware.atlassian.net/browse/TAT-2418)

## **Manual testing steps**

```gherkin
Feature: Flip position fee estimate accuracy

  Scenario: Fee estimate matches actual fee charged
    Given a wallet with an open BTC long position of ~$11
    When user taps Modify then selects Flip Position
    Then the fee shown on the confirmation sheet
      is approximately 0.09% of the full position value (2x notional)
      not 0.045% (1x notional)

  Scenario: User with sufficient balance is not blocked
    Given a wallet with an open BTC position
    And available balance is $30 (above 1x fee ~$22.50, below 2x fee ~$45)
    When user attempts to flip the position
    Then the flip proceeds successfully
```

## **Screenshots/Recordings**

_Evidence available in task artifacts — will be added by reviewer if
needed._

### **Before**

<!-- [screenshots/recordings] -->
<img width="1206" height="2622" alt="before-flip-fee-estimate"
src="https://github.com/user-attachments/assets/80b989b3-8ae2-4c87-ae03-610371a592e7"
/>

### **After**

<!-- [screenshots/recordings] -->
<img width="1206" height="2622" alt="after-flip-fee-estimate"
src="https://github.com/user-attachments/assets/5b9701b8-ab9d-4f82-88d5-08be1bf2c4b7"
/>

## **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 (14 steps)</summary>

```json
{
  "pr": "28013",
  "title": "Flip position fee estimate is ~2x lower than actual fee charged",
  "jira": "TAT-2418",
  "acceptance_criteria": [
    "Fee estimate shown on flip confirmation sheet matches actual fee charged (within normal rounding tolerance)",
    "Users with sufficient margin for the actual fee are not blocked from submitting a flip order",
    "No new TypeScript errors"
  ],
  "validate": {
    "static": ["yarn lint:tsc"],
    "runtime": {
      "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"],
      "steps": [
        {
          "id": "open_pos",
          "description": "Open a BTC long position to flip",
          "action": "flow_ref",
          "ref": "trade-open-market",
          "params": { "symbol": "BTC", "side": "long", "usdAmount": "11" }
        },
        {
          "id": "wait_position",
          "description": "Wait for BTC position to appear after opening",
          "action": "wait_for",
          "expression": "Engine.context.PerpsController.getPositions().then(function(positions){ var btc = positions.filter(function(p){ return p.symbol === 'BTC'; }); return JSON.stringify({positions: btc}); })",
          "timeout_ms": 20000,
          "assert": { "operator": "length_gt", "field": "positions", "value": 0 }
        },
        {
          "id": "nav_market_details",
          "description": "Navigate to BTC market details to access flip confirmation sheet",
          "action": "navigate",
          "target": "PerpsMarketDetails",
          "params": { "symbol": "BTC" }
        },
        {
          "id": "wait_market_details",
          "description": "Wait for market details to load",
          "action": "wait_for",
          "route": "PerpsMarketDetails"
        },
        {
          "id": "wait_modify_button",
          "description": "Wait for the Modify button to appear (position must be loaded)",
          "action": "wait_for",
          "test_id": "perps-market-details-modify-button"
        },
        {
          "id": "press_modify",
          "description": "Open the modify action sheet",
          "action": "press",
          "test_id": "perps-market-details-modify-button"
        },
        {
          "id": "wait_flip_option",
          "description": "Wait for the flip position option in the modify action sheet",
          "action": "wait_for",
          "test_id": "undefined-flip_position"
        },
        {
          "id": "press_flip_option",
          "description": "Select flip position to open flip confirmation sheet",
          "action": "press",
          "test_id": "undefined-flip_position"
        },
        {
          "id": "wait_flip_sheet",
          "description": "Wait for flip confirmation bottom sheet to appear",
          "action": "wait_for",
          "test_id": "perps-flip-position-confirm-sheet"
        },
        {
          "id": "check_fee_calculation",
          "description": "Assert the fee is calculated on 2x notional.",
          "action": "eval_async",
          "expression": "Engine.context.PerpsController.getPositions().then(function(positions) { var pos = null; for (var i = 0; i < positions.length; i++) { if (positions[i].symbol === 'BTC') { pos = positions[i]; break; } } if (!pos) return JSON.stringify({error: 'no BTC position'}); var size = Math.abs(parseFloat(pos.size)); var price = parseFloat(pos.entryPrice); var usdAmount1x = size * price; var usdAmount2x = size * 2 * price; return JSON.stringify({positionSize: size, entryPrice: price, fee_base_1x: usdAmount1x, fee_base_2x_correct: usdAmount2x}); })",
          "assert": { "operator": "not_null" }
        },
        {
          "id": "evidence_fee_sheet",
          "description": "Capture fee estimate shown on confirmation sheet",
          "action": "screenshot",
          "filename": "evidence-flip-fee-estimate.png"
        },
        {
          "id": "check_no_errors",
          "description": "Verify no errors in Metro logs during flip flow",
          "action": "log_watch",
          "window_seconds": 5,
          "must_not_appear": ["TypeError", "undefined is not an object"]
        },
        {
          "id": "cancel_flip",
          "description": "Cancel the flip sheet without submitting",
          "action": "press",
          "test_id": "perps-flip-position-cancel-button"
        },
        {
          "id": "cleanup_position",
          "description": "Close BTC position to leave clean state",
          "action": "flow_ref",
          "ref": "trade-close-position",
          "params": { "symbol": "BTC" }
        }
      ]
    }
  }
}
```

</details>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes fee/notional math used in the flip-position UI and the
`TradingService.flipPosition` balance gate; incorrect values could
affect whether users can submit flips and what fees they expect.
> 
> **Overview**
> Fixes flip-position fee math to align with how fees are actually
charged.
> 
> The flip confirmation sheet now estimates fees using **2x position
notional** (close + open) and adds stable `testID`s for the sheet and
its action buttons, with a unit test asserting the `usePerpsOrderFees`
amount.
> 
> `TradingService.flipPosition` updates its **available-balance fee
check** to apply the combined fee rate to **1x notional** (since the
rate already includes both legs), and adds a regression test ensuring a
flip is allowed when balance covers that corrected estimate.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
dcf9361. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@pull pull Bot locked and limited conversation to collaborators Mar 28, 2026
@pull pull Bot added the ⤵️ pull label Mar 28, 2026
@pull pull Bot merged commit b2ac9a2 into Reality2byte:main Mar 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant