Skip to content

[pull] main from MetaMask:main#296

Merged
pull[bot] merged 10 commits into
Reality2byte:mainfrom
MetaMask:main
Nov 5, 2025
Merged

[pull] main from MetaMask:main#296
pull[bot] merged 10 commits into
Reality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Nov 5, 2025

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 : )

matallui and others added 10 commits November 4, 2025 23:40
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->
Changes:
- invalidate eth query cache before checking balance
- invalidate eth query cache before placing an order (to avoid nonce
collisions)
- optimistic updates for balance, since balance changes take a couple of
seconds after placing an order successfully


## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry: null

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]
```

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->


https://github.com/user-attachments/assets/f3e0c4e3-c3c3-45fc-b5f8-d43f3c92b57e




## **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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds optimistic balance updates with short-lived caching and
invalidates network query cache before balance checks and order
placement; extends provider interface with name/chainId.
> 
> - **PredictController**:
> - Introduces `PredictBalance` and changes `state.balances` to cache `{
balance, validUntil }` per `providerId/address`.
> - Adds `invalidateQueryCache(chainId)` using
`NetworkController.blockTracker.checkForLatestBlock` and calls it before
`placeOrder` and fresh `getBalance` fetches.
> - Implements optimistic balance updates after successful `placeOrder`
(BUY decreases, SELL increases) with 5s validity; `getBalance` caches
for 1s.
> - **Providers**:
> - Extends `PredictProvider` interface with readonly `name` and
`chainId`.
> - `PolymarketProvider` exposes `name = "Polymarket"` and `chainId =
137`.
> - **Selectors/UI**:
> - Updates selectors to read nested
`balances[providerId][address].balance`.
>   - `usePredictPlaceOrder` removes post-order balance reload.
> - **Tests**:
> - Adds tests for query-cache invalidation, optimistic balance updates,
and balance caching.
> - Verifies provider interface properties and updates selector tests
accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
acb7c02. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->
Remove PPOM local validations. All validations now occur through the
security alerts API. We were relying on the local PPOM validations as a
fallback, but reality is that based on the data, it was not providing
any meaningful support (with only users rejecting 10 transactions that
were validated by the local PPOM implementation with a Malicious outcome
in the last 3 months)

Furthermore this was always the plan from the moment that we released
the security alerts API.

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry:

## **Related issues**

Fixes: MetaMask/mobile-planning#2300

## **Manual testing steps**

Test a malicious transaction (can use our test dapp) and see that
Blockaid warnings are still being displayed.

## **Screenshots/Recordings**
Nothing should change. PPOM validations should still be done through the
API (as they are right now)

## **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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Removes all local PPOM code and dependencies, switches confirmations
to use only the Security Alerts API, adds a migration to clear PPOM
storage, and cleans up related CI/config paths and tests.
> 
> - **Confirmations/Security Alerts**:
> - Replace PPOM fallback with API-only validation in `ppom-util`;
remove controller-based flow and error fallback.
>   - Drop `blockaidVersion` plumbing from alert reporting and tests.
> - **Engine/Core**:
> - Remove `PPOMController` (init, messenger, constants, types, wiring)
from `Engine`.
> - Delete PPOM storage backend and `PPOMView`; remove `<PPOMView />`
usage in `App.tsx`.
> - **Data/State**:
> - Strip `PPOMController` from engine state, Sentry filters, log
snapshots, and initial background state.
> - **Migrations**:
>   - Add migration `107` to clear MMKV `PPOMDB` storage; include tests.
> - **Build/CI/Tooling**:
> - Remove PPOM build step and paths from scripts; drop
`BLOCKAID_FILE_CDN` env in workflows.
> - Update cache key script to exclude `ppom/`; clean ignores and
CODEOWNERS.
> - **Dependencies**:
> - Remove PPOM-related packages (`@metamask/ppom-validator`,
`react-native-blob-jsi-helper`, `react-native-webview-invoke`,
`crypto-js` types) and Podfile entry; adjust yarn lock.
> - **Config**:
> - Remove PPOM env vars from `.js.env.example`; clean eslint/prettier
ignores.
> - **Tests**:
> - Update unit tests to rely on API validation and expectations; delete
PPOM-specific tests.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d21bfb1. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Curtis David <Curtis.David7@gmail.com>
#22078)

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

Added crypto currency metadata fields to mobile ramp event tracking
across Buy/Sell (Aggregator) and Deposit flows to improve analytics
insights.

## Changes

### Type Definitions
- **Aggregator Events**
(`app/components/UI/Ramp/Aggregator/types/analytics.ts`): Added optional
fields to 18 event interfaces:
- `currency_destination_symbol?`: Symbol of the destination
cryptocurrency (e.g., "ETH", "USDC")
- `currency_destination_network?`: Network name of the destination
(e.g., "Ethereum Mainnet", "Polygon")
- `currency_source_symbol?`: Symbol of the source cryptocurrency for
sell flows
- `currency_source_network?`: Network name of the source for sell flows

- **Deposit Events**
(`app/components/UI/Ramp/Deposit/types/analytics.ts`): Added optional
fields to 7 event interfaces:
- `currency_destination_symbol?`: Symbol of the destination
cryptocurrency
  - `currency_destination_network?`: Network name of the destination

### Implementation

#### Aggregator Flow
Updated event tracking in the following files to include new metadata
fields:
- `Quotes.tsx`: 10 events (quotes requested, expanded, provider
clicked/selected, quote errors)
- `BuildQuote.tsx`: 2 events (onramp/offramp quotes requested)
- `useHandleSuccessfulOrder.ts`: 2 events (purchase submitted)
- `OrderDetails.tsx`: 2 events (purchase details viewed)

Data source: `order?.cryptoCurrency?.network?.shortName` and
`order?.cryptoCurrency?.symbol`

#### Deposit Flow
Updated event tracking in the following files to include new metadata
fields:
- `BuildQuote.tsx`: 3 events (order proposed, selected, failed)
- `TokenSelectorModal.tsx`: 1 event (token selected)
- `useDepositRouting.ts`: 1 event (transaction confirmed) - reverted to
not include new fields
- `getDepositAnalyticsPayload.ts`: 2 events (transaction completed,
failed) - reverted to not include new fields

Data source: `selectedCryptoCurrency?.network?.name` or
`selectedToken?.network?.name` and corresponding symbol fields

## Impact
- Enhanced analytics data collection for ramp transactions
- No breaking changes - all new fields are optional
- Improved visibility into which networks and currencies users are
interacting with


<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry: added metadata to some analytics events

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/TRAM-2770

## **Manual testing steps**

```gherkin
Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]
```

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **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
- [ ] 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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds token symbol and network metadata to Buy/Sell Aggregator and
Deposit analytics events, with new hooks to resolve network names and
updated tests.
> 
> - **Analytics schema**:
> - Extend `Aggregator` and `Deposit` event interfaces to optionally
include `currency_*_symbol` and `currency_*_network`.
> - **Aggregator (Buy/Sell)**:
> - Emit new fields in `BuildQuote.tsx`, `Quotes.tsx`,
`OrderDetails.tsx`, and `useHandleSuccessfulOrder.ts` across quotes
requested/received/expanded, provider clicked/selected, quote error,
purchase submitted/details viewed.
> - Add `useAggregatorOrderNetworkName` (handles CAIP/EVM/hex) to derive
readable network names.
> - **Deposit**:
> - Emit new fields in `BuildQuote.tsx`, `TokenSelectorModal.tsx`,
`useDepositRouting.ts`, and `getDepositAnalyticsPayload.ts` for order
proposed/selected/failed, token selected, transaction
confirmed/completed/failed.
> - Add `useDepositCryptoCurrencyNetworkName` and
`useDepositOrderNetworkName` to resolve network names.
> - **Tests**:
> - Update unit tests across Aggregator and Deposit views/hooks to
validate presence of new analytics fields and network resolution logic.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
abe73ae. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Updated the Toast component background color from `background.muted`
(transparent) to `background.section` (opaque) to improve visibility and
match design specifications. This was introduced in
[21942](https://github.com/MetaMask/metamask-mobile/pull/21942/files#diff-3a77a02f68287c5db97fcab49cee7ca3303235de3cfc9ec9d7fd36e2c7853f11R23).
This change ensures toasts have proper contrast against different
backgrounds.

The transparent background was causing readability issues when toasts
appeared over various content.

## **Changelog**

CHANGELOG entry: Fixed toast background color to improve visibility

## **Related issues**

Fixes: N/A

## **Manual testing steps**

```gherkin
Feature: Toast visibility

  Scenario: User views a toast notification
    Given the app is open on any screen
    When a toast notification appears
    Then the toast should have an opaque background
    And the toast content should be clearly visible against any background content
```

## **Screenshots/Recordings**

### **Before**

Toast had transparent background (background.muted) which could cause
visibility issues

<img width="476" height="436" alt="Screenshot 2025-11-04 at 2 32 37 PM"
src="https://github.com/user-attachments/assets/d0611093-1a6b-49da-b0dc-e264ab0f6575"
/>


### **After**

Toast now has opaque background (background.section) ensuring consistent
visibility


https://github.com/user-attachments/assets/3720778b-7bd4-4386-a08c-7d0ecdf25c96

## **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.



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Switches Toast background to `colors.background.section` and updates
the Storybook story to use typed `ToastOptions` with a Tailwind-wrapped
container.
> 
> - **Toast styles (`Toast.styles.ts`)**:
> - Change background from `colors.background.muted` to
`colors.background.section`.
> - **Storybook (`Toast.stories.tsx`)**:
> - Use typed `ToastOptions` and pass a single `toastOptions` object
(includes `variant` and `hasNoTimeout`).
>   - Wrap story UI in a Tailwind-styled `View` container for layout.
> - Modernize story config: type with `Meta`, rename to `Default`, and
clean up decorators.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e2817a6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

This PR introduces a new reusable `ButtonFilter` component to the
component library.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes: N/A

## **Manual testing steps**

```gherkin
Feature: ButtonFilter component in Storybook

  Scenario: developer views ButtonFilter stories
    Given developer has started Storybook

    When developer navigates to "Components Temp / Buttons / ButtonFilter"
    Then developer should see "Default", "Active", and "FilterGroup" variants
    And should be able to toggle isActive state via controls
    And should be able to change label text via controls
```

## **Screenshots/Recordings**

### **Before**

### **After**

https://github.com/user-attachments/assets/bddb654d-b745-4e11-85cc-19fad493e258

## **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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a temporary `ButtonFilter` component with active/inactive
styling, Storybook stories, tests (with snapshots), and documentation.
> 
> - **Components**:
> - Add `ButtonFilter` (`ButtonFilter.tsx`, `ButtonFilter.types.ts`)
with active/inactive states using design system + Tailwind.
>   - Export via `components-temp/ButtonFilter/index.ts`.
> - **Storybook**:
> - Add `ButtonFilter.stories.tsx` with `Default`, `Active`, and
`FilterGroup` variants.
>   - Register story in `.storybook/storybook.requires.js`.
> - **Tests**:
> - Add `ButtonFilter.test.tsx` and snapshots validating render states,
press handling, disabled state, accessibility label, and style prop
spreading.
> - **Docs**:
>   - Add `README.md` with props, usage, and styling details.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1d18e8c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…22107)

## **Description**

Updated the Open Interest Cap warning design to match the latest design
specifications. The warning now uses a more prominent banner style with
improved visibility and positioning.

**Changes:**
1. Simplified warning message to single concise text
2. Updated styling to use dark background with light text/icon
3. Repositioned warning to appear directly below the chart in scrollable
content
4. Action buttons (Long/Short in Market Details, Place Order in Order
View) now completely hidden when market is at OI cap

## **Changelog**

CHANGELOG entry: Updated Open Interest Cap warning design with improved
styling and positioning

## **Related issues**

Fixes: [Issue number if available]

## **Manual testing steps**

```gherkin
Feature: Open Interest Cap Warning Display

  Scenario: User views market at OI cap
    Given user is on a market details screen where the market has reached its open interest cap

    When the page loads
    Then user sees a banner warning below the chart with text "Open interest cap reached. New positions cannot be opened at this time."
    And the Long/Short action buttons are hidden

  Scenario: User attempts to place order at OI cap
    Given user navigates to the order view for a market at OI cap

    When the order form loads
    Then user sees the OI cap warning banner
    And the Place Order button is hidden

  Scenario: User views market not at OI cap
    Given user is on a market details screen where the market is not at capacity

    When the page loads
    Then no OI cap warning is displayed
    And the Long/Short buttons are visible and functional
```

## **Screenshots/Recordings**

### **Before**

- Warning used inline variant with warning background color
- Warning text had title + description structure
- Action buttons were visible but disabled when at OI cap
- Warning positioned outside scrollable content
<img width="397" height="822" alt="image"
src="https://github.com/user-attachments/assets/370d9950-a35c-4650-a2b3-5301a2eaf595"
/>

### **After**

- Warning uses banner variant with dark background and light text/icon
- Single concise warning message
- Action buttons completely hidden when at OI cap
- Warning positioned below chart in scrollable content with proper
margins
<img width="539" height="1138" alt="image"
src="https://github.com/user-attachments/assets/7be19520-20ae-4722-b56b-4e4494d288ed"
/>

## **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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Redesigns the OI cap warning (banner style, simplified text) and hides
Market Hours and all trading actions (Long/Short, Place Order) when a
market is at capacity.
> 
> - **Perps UI/UX**:
> - **Market Details (`PerpsMarketDetailsView`)**: Adds
`PerpsOICapWarning` banner below chart; hides `PerpsMarketHoursBanner`
and Long/Short footer buttons when `isAtOICap`.
> - **Order View (`PerpsOrderView`)**: Shows `PerpsOICapWarning` banner
when at cap; hides fixed Place Order button when at cap; removes inline
warning.
> - **Component (`PerpsOICapWarning`)**:
> - Styling updates (banner background, larger default icon, centered
layout, rounded corners); remove title/description split and warning
colors; render single i18n message.
>   - Tests updated to assert i18n string and custom `testID` usage.
> - **Localization**:
> - Simplifies OI cap strings in `locales/languages/en.json` to a single
`perps.order.validation.oi_cap_reached` message; removes unused
description/market copy.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cb7cc07. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Fixes bug where positions and orders were not being passed into
watchlist press event as navigation params. This cause a buggy behavior
on the MarketDetails screen where positions/orders weren't always
present, and would delay or sometimes fail to load entirely on the
market detail view.

## **Changelog**

CHANGELOG entry: Pass order and position subscription data into
watchlist navigation params

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1982

## **Manual testing steps**

Navigation behavior should be the same from HomeScreen regardless of
whether it originates from Your Positions or Watchlist

## **Screenshots/Recordings**


https://github.com/user-attachments/assets/8afb5c25-8756-4c62-b2a9-e59c92b79837

## **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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Derives and passes `initialTab` to MarketDetails based on live
positions/orders when navigating from Watchlist, with tests updated to
mock hooks and assert new params.
> 
> - **Perps Watchlist navigation**:
> - Subscribe to `usePerpsLivePositions` and `usePerpsLiveOrders` and
derive `initialTab` (`'position'` | `'orders'` | `undefined`) in
`PerpsWatchlistMarkets.tsx`.
> - Pass `{ market, initialTab }` in navigation to `Routes.PERPS.ROOT ->
MARKET_DETAILS`.
> - **Tests**:
> - Mock Perps stream hooks and update expectations to include
`initialTab` in navigation params.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9306127. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

This PR fixes a crash that occurred when users tapped on the Polymarket
terms link in the Predict Unavailable bottom sheet. The crash was caused
by nested `Text` components where the inner `Text` had an `onPress`
handler, leading to deep recursion in React Native's layout measurement
(`facebook::react::progressState`).

**Changes:**
- Replaced external browser navigation with in-app WebView navigation
for Polymarket terms
- Refactored the terms link layout by wrapping the entire text block
with `TouchableOpacity` instead of having nested `Text` components with
`onPress`
- Added `InteractionManager.runAfterInteractions` to ensure smooth
navigation transitions
- Added new localization string `predict.unavailable.webview_title` for
the WebView title
- Updated all unit tests to match the correct localized strings

**Why in-app WebView?**
- Provides a consistent user experience by keeping users within the app
- Follows the pattern used for other terms/legal documents in the app
- Avoids potential issues with external browser availability or
configuration

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes: <!-- Add issue number if available -->

## **Manual testing steps**

```gherkin
Feature: Polymarket terms link in Predict Unavailable screen

  Scenario: user taps on Polymarket terms link
    Given user is in a region where Predict is unavailable
    And user sees the "Unavailable in your region" bottom sheet

    When user taps on the "See Polymarket terms" text
    Then the app should navigate to an in-app WebView
    And the WebView should display "https://polymarket.com/tos"
    And the WebView title should be "Polymarket Terms"
    And the app should not crash
```

## **Screenshots/Recordings**


https://github.com/user-attachments/assets/b8c0cc3c-0d5d-4bd8-b214-c6f90df8e324

## **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.



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Opens Polymarket terms in an in-app WebView with deferred navigation
and refactors the link to a touchable element; updates tests and i18n.
> 
> - **PredictUnavailable
(`app/components/UI/Predict/components/PredictUnavailable/PredictUnavailable.tsx`)**:
> - Replace external `Linking.openURL` with in-app navigation to
`Webview > SimpleWebview` via `useNavigation` and
`InteractionManager.runAfterInteractions`.
> - Refactor terms link: wrap content in `TouchableOpacity`, add
`testID` `polymarket-terms-link`, remove nested `Text` onPress.
> - Minor cleanup: remove `Linking` import; keep bottom sheet behavior
unchanged.
> - **Tests (`PredictUnavailable.test.tsx`)**:
> - Update to mock `InteractionManager.runAfterInteractions` and
navigation; assert deferred callback and navigation params (`url`,
`title`).
> - Adjust expectations to new localized strings and presence of
`polymarket-terms-link`.
>   - Remove external Linking assertions.
> - **Localization (`locales/languages/en.json`)**:
> - Add `predict.unavailable.webview_title`="Polymarket Terms"; confirm
updated copy for title/description/link.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
63c0b10. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…#22086)

## **Description**

This PR implements market-level whitelisting and blacklisting for Perps
HIP-3 markets, providing granular control over which perpetual trading
markets are shown to users.

### What is the reason for the change?

Improvement from the initial remote feature flag implementation in PR
#21823. While the initial PR established the remote feature flag
infrastructure for Perps, we needed more granular control beyond simple
DEX-level filtering. Specifically:

1. Users need the ability to enable/disable specific markets (e.g.,
"BTC", "xyz:XYZ100") rather than all markets from a DEX
2. Operators need both whitelist (enable only specific markets) and
blacklist (block specific markets) capabilities
3. The system must support wildcard patterns for efficient bulk
operations (e.g., "xyz:*" to enable/block all markets from a DEX)

### What is the improvement/solution?

**Migration from DEX-level to Market-level Filtering:**

- **Before**: Single `MM_PERPS_HIP3_ENABLED_DEXS` variable for
whitelisting entire DEXs
- **After**: Two variables for granular market control:
- `MM_PERPS_HIP3_ALLOWLIST_MARKETS` - Whitelist (empty = enable all,
non-empty = only show these)
- `MM_PERPS_HIP3_BLOCKLIST_MARKETS` - Blacklist (empty = block none,
non-empty = hide these)

**Key Features:**

1. **Wildcard Pattern Support**
   - `"xyz:*"` or `"xyz"` - All markets from xyz DEX
   - `"xyz:TSLA"` - Specific market
   - `"BTC"` - Main DEX market

2. **Remote Feature Flag Integration**
   - LaunchDarkly provides remote configuration
   - Local env variables serve as fallback
- "Sticky remote" pattern: once remote config loads, never downgrade to
fallback

3. **Automatic Cache Invalidation & Reconnection**
   - `hip3ConfigVersion` increments when config changes
   - ConnectionManager monitors version via Redux
   - Automatically triggers cache clearing and provider reconnection
   - No manual intervention required

4. **Performance Optimizations**
   - Pattern caching (pre-compiled regex stored in Map)
   - Separate cache keys for filtered vs unfiltered data
   - `skipFilters` parameter for administrative queries

**Architecture:**

```
LaunchDarkly � RemoteFeatureFlagController � PerpsController
                                                   � (hip3ConfigVersion++)
                                           ConnectionManager
                                                   � (monitors version change)
                                           Clear caches + Reconnect
                                                   �
                                           HyperLiquidProvider
                                                   � (applies filters)
                                           Market list with patterns applied
```

## **Changelog**

CHANGELOG entry: Added market-level whitelisting and blacklisting for
Perps HIP-3 markets with wildcard pattern support and automatic
reconnection on configuration changes

## **Related issues**

Related to: #21823 (Initial remote feature flag implementation for
Perps)

This PR splits out the market filtering functionality (Task #2) from the
original feature flag work.

## **Manual testing steps**

```gherkin
Feature: Perps HIP-3 Market Filtering

  Scenario: Whitelist enables all markets when empty
    Given MM_PERPS_HIP3_ENABLED is true
    And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
    And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty

    When user opens the Perps tab
    Then all available markets should be visible
    And markets from both main DEX and HIP-3 DEXs should appear

  Scenario: Whitelist restricts to specific markets
    Given MM_PERPS_HIP3_ENABLED is true
    And MM_PERPS_HIP3_ALLOWLIST_MARKETS is "BTC,ETH,xyz:TSLA"
    And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty

    When user opens the Perps tab
    Then only BTC, ETH, and xyz:TSLA markets should be visible
    And all other markets should be hidden

  Scenario: Whitelist with wildcard enables all markets from DEX
    Given MM_PERPS_HIP3_ENABLED is true
    And MM_PERPS_HIP3_ALLOWLIST_MARKETS is "xyz:*,BTC"
    And MM_PERPS_HIP3_BLOCKLIST_MARKETS is empty

    When user opens the Perps tab
    Then BTC market should be visible
    And all markets from xyz DEX should be visible
    And markets from other HIP-3 DEXs should be hidden

  Scenario: Blacklist blocks specific markets
    Given MM_PERPS_HIP3_ENABLED is true
    And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
    And MM_PERPS_HIP3_BLOCKLIST_MARKETS is "xyz:TSLA,BTC"

    When user opens the Perps tab
    Then xyz:TSLA market should be hidden
    And BTC market should be hidden
    And all other markets should be visible

  Scenario: Blacklist with wildcard blocks all markets from DEX
    Given MM_PERPS_HIP3_ENABLED is true
    And MM_PERPS_HIP3_ALLOWLIST_MARKETS is empty
    And MM_PERPS_HIP3_BLOCKLIST_MARKETS is "xyz:*"

    When user opens the Perps tab
    Then all markets from xyz DEX should be hidden
    And markets from main DEX and other HIP-3 DEXs should be visible

  Scenario: Remote feature flag overrides local configuration
    Given local env has MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC"
    And LaunchDarkly is configured with perpsAllowlistMarkets="ETH,SOL"

    When app initializes and fetches remote feature flags
    Then only ETH and SOL markets should be visible
    And BTC market should be hidden (remote overrides local)

  Scenario: Automatic reconnection on configuration change
    Given user has Perps tab open with markets loaded
    And LaunchDarkly configuration is "BTC,ETH"

    When LaunchDarkly configuration changes to "SOL,AVAX"
    Then hip3ConfigVersion should increment
    And ConnectionManager should detect the change
    And app should automatically clear caches
    And app should reconnect to providers
    And only SOL and AVAX markets should be visible without manual refresh

  Scenario: Fallback to local configuration when remote unavailable
    Given LaunchDarkly is unreachable
    And local env has MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC,ETH"

    When user opens the Perps tab
    Then local configuration should be used as fallback
    And only BTC and ETH markets should be visible

  Scenario: Shorthand wildcard notation
    Given MM_PERPS_HIP3_ALLOWLIST_MARKETS is "xyz" (without :*)

    When user opens the Perps tab
    Then "xyz" should be interpreted as "xyz:*"
    And all markets from xyz DEX should be visible
```

## **Screenshots/Recordings**

### **Before**

N/A - This is a configuration-based feature without UI changes

### **After**

N/A - This is a configuration-based feature without UI changes

## **Pre-merge author checklist**

- [ ] 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).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] 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.

---

## Technical Implementation Details

### Environment Variables

**New variables added:**
```bash
# HIP-3 Feature Flags (remote override with local fallback)
export MM_PERPS_HIP3_ENABLED="true"
export MM_PERPS_HIP3_ALLOWLIST_MARKETS=""  # Whitelist: Empty = enable all markets
export MM_PERPS_HIP3_BLOCKLIST_MARKETS=""  # Blacklist: Empty = no blocking
```

**Removed variables:**
```bash
# OLD - Removed
export MM_PERPS_HIP3_ENABLED_DEXS=""  # Replaced by market-level filtering
export MM_PERPS_ENABLED_DEXS=""       # Replaced by market-level filtering
```

### Pattern Matching Examples

| Pattern | Matches | Description |
|---------|---------|-------------|
| `"BTC"` | `BTC` (main DEX) | Exact match on main DEX market |
| `"xyz:TSLA"` | `xyz:TSLA` | Exact match on HIP-3 market |
| `"xyz:*"` | `xyz:TSLA`, `xyz:AAPL`, etc. | All markets from xyz DEX |
| `"xyz"` | `xyz:TSLA`, `xyz:AAPL`, etc. | Shorthand for `"xyz:*"` |
| `""` (empty) | All markets | Discovery mode (whitelist) or no blocking
(blacklist) |

### Pattern Matching Implementation

**Type Safety:**
- `CompiledPatternMatcher` - Type alias for pattern matchers (string for
exact match, RegExp for wildcards)
- `CompiledPattern` - Interface combining original pattern with compiled
matcher

**Compilation Strategy:**
```typescript
// Pattern → Compiled Matcher
"xyz:*"      → /^xyz:/         (prefix regex)
"xyz"        → /^xyz:/         (shorthand → prefix regex)
"xyz:TSLA"   → "xyz:TSLA"      (exact string match - fastest)
```

**Performance Optimization:**
1. All patterns pre-compiled at provider initialization via
`recompileAllPatterns()`
2. Stored in typed arrays (`CompiledPattern[]`) for direct iteration
3. Eliminates repeated `compilePattern()` function call overhead during
filtering
4. Better code clarity and type safety with pre-compiled matchers
5. Exact matches use string equality (fastest), wildcards use
RegExp.test()

**Filtering Logic in `shouldIncludeMarket()`:**
```typescript
// Main DEX markets always included
if (dex === null) return true;

// Apply whitelist (if non-empty)
if (compiledEnabledPatterns.length > 0) {
  if (!compiledEnabledPatterns.some(p => matches(symbol, p.matcher))) {
    return false; // Not whitelisted
  }
}

// Apply blacklist (if non-empty)
if (compiledBlockedPatterns.length > 0) {
  if (compiledBlockedPatterns.some(p => matches(symbol, p.matcher))) {
    return false; // Blacklisted
  }
}

return true;
```

### New Selectors

- `selectHip3ConfigVersion()` - Returns version number for cache
invalidation used by ConnectionManager to detect config changes

### Files Modified

**Core Logic (258 lines added):**
- `app/components/UI/Perps/controllers/PerpsController.ts`
- `refreshHip3ConfigFromRemote()` - Extracts and validates remote config
  - Version tracking and increment on config change
  - "Sticky remote" pattern implementation

**Provider Implementation (411 lines added):**
- `app/components/UI/Perps/controllers/providers/HyperLiquidProvider.ts`
  - `CompiledPatternMatcher` type - Type alias for pattern matchers
  - `CompiledPattern` interface - Type for pre-compiled patterns
- `recompileAllPatterns()` - Pre-compiles all patterns at initialization
- `shouldIncludeMarket()` - Pattern matching logic using pre-compiled
patterns
- `matchesCompiledPattern()` - Fast pattern matching without Map lookups
  - `compilePattern()` - Regex compilation and caching
  - `skipFilters` parameter support
  - Separate cache keys for filtered/unfiltered data

**Selectors:**
- `app/components/UI/Perps/selectors/featureFlags/index.ts`
- `selectHip3ConfigVersion` - Version selector for monitoring config
changes

**Connection Management (22 lines added):**
- `app/components/UI/Perps/services/PerpsConnectionManager.ts`
  - Monitors `hip3ConfigVersion` changes via Redux
  - Triggers automatic cache clearing and reconnection

**Tests:**
- `app/components/UI/Perps/selectors/featureFlags/index.test.ts`
  - `selectHip3ConfigVersion` (3 tests) - Validates version tracking
- Removed unused selector tests for unused allowlist/blocklist selectors

**Configuration:**
- `.js.env.example` - Environment variable documentation
- `bitrise.yml` - CI/CD environment configuration
- `app/core/Engine/controllers/perps-controller/index.ts` - Fallback
parsing

**Types:**
- `app/components/UI/Perps/controllers/types/index.ts`
  - Updated `PerpsControllerConfig` interface
  - Added `skipFilters` to `GetMarketsParams`

### Performance Considerations

1. **Pattern Pre-Compilation**: All filter patterns are compiled once at
initialization and stored in typed arrays (`CompiledPattern[]`),
eliminating repeated `compilePattern()` function calls during market
filtering
2. **Type-Safe Pattern Matchers**: Uses `CompiledPatternMatcher` type
alias and `CompiledPattern` interface for better documentation and type
safety
3. **Optimized Filtering**: `shouldIncludeMarket()` iterates
pre-compiled arrays directly with compiled matchers readily available,
improving code clarity and maintainability
4. **Separate Cache Keys**: Filtered and unfiltered data cached
separately (`"${dex}_raw"` vs `"${dex}_filtered"`)
5. **StreamManager Integration**: Market metadata cached for 5 minutes,
prices from WebSocket (real-time)

### Migration Guide

**From DEX-level to Market-level Filtering**

This PR replaces DEX-level filtering with more granular market-level
filtering:

**Removed Environment Variables:**
```bash
# OLD - No longer used
export MM_PERPS_HIP3_ENABLED_DEXS="xyz,abc"  # Whitelist entire DEXs
export MM_PERPS_ENABLED_DEXS="xyz,abc"       # Alternative naming (removed)
```

**New Environment Variables:**
```bash
# NEW - Market-level control
export MM_PERPS_HIP3_ALLOWLIST_MARKETS=""  # Whitelist: empty = all markets (discovery mode)
export MM_PERPS_HIP3_BLOCKLIST_MARKETS=""  # Blacklist: empty = block nothing
```

**Common Migration Scenarios:**

1. **Enable all markets from specific DEXs (most common):**
   ```bash
   # Before: Enable all markets from xyz and abc DEXs
   MM_PERPS_HIP3_ENABLED_DEXS="xyz,abc"

   # After: Use wildcard patterns
   MM_PERPS_HIP3_ALLOWLIST_MARKETS="xyz:*,abc:*"
   # Or use shorthand (equivalent)
   MM_PERPS_HIP3_ALLOWLIST_MARKETS="xyz,abc"
   ```

2. **Enable specific markets only:**
   ```bash
   # Before: Not possible (DEX-level only)

   # After: List specific markets
   MM_PERPS_HIP3_ALLOWLIST_MARKETS="BTC,ETH,xyz:TSLA,xyz:AAPL"
   ```

3. **Enable all markets except specific ones:**
   ```bash
   # Before: Not possible (no blacklist support)

   # After: Use blacklist (whitelist empty = all markets)
   MM_PERPS_HIP3_ALLOWLIST_MARKETS=""
   MM_PERPS_HIP3_BLOCKLIST_MARKETS="xyz:SCAM,abc:RISKY"
   ```

4. **Block entire DEX:**
   ```bash
   # Before: Omit from ENABLED_DEXS list
   MM_PERPS_HIP3_ENABLED_DEXS="xyz"  # abc implicitly blocked

   # After: Use blacklist wildcard
   MM_PERPS_HIP3_ALLOWLIST_MARKETS=""     # Enable all
   MM_PERPS_HIP3_BLOCKLIST_MARKETS="abc:*"  # Block abc DEX
   # Or use shorthand
   MM_PERPS_HIP3_BLOCKLIST_MARKETS="abc"
   ```

5. **Enable all markets (discovery mode):**
   ```bash
   # Before: Leave ENABLED_DEXS empty or set to all known DEXs
   MM_PERPS_HIP3_ENABLED_DEXS=""

   # After: Leave ALLOWLIST_MARKETS empty
   MM_PERPS_HIP3_ALLOWLIST_MARKETS=""
   MM_PERPS_HIP3_BLOCKLIST_MARKETS=""
   ```

**LaunchDarkly Remote Feature Flags:**

The same patterns apply to LaunchDarkly configuration:
- `perpsAllowlistMarkets` - Array of market patterns (empty = all
markets)
- `perpsBlocklistMarkets` - Array of market patterns to block (empty =
block nothing)

**Example LaunchDarkly Config:**
```json
{
  "perpsHip3Enabled": true,
  "perpsAllowlistMarkets": ["BTC", "ETH", "xyz:*"],
  "perpsBlocklistMarkets": ["xyz:SCAM"]
}
```

### Breaking Changes

None - This is an additive change with backward-compatible fallback
behavior.

**Note:** While `MM_PERPS_HIP3_ENABLED_DEXS` is no longer used, removing
it from your configuration will not break anything. The new market-level
filtering is more flexible and supersedes DEX-level filtering.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Implements market-level allowlist/blocklist filtering for HIP-3 perps
with remote-flag overrides, caching, and automatic reconnection via
hip3ConfigVersion.
> 
> - **Perps HIP-3 market filtering (allowlist/blocklist)**:
> - Add pattern-based filtering (exact, wildcard, DEX shorthand) via
`shouldIncludeMarket`, `compileMarketPattern`, etc. in
`utils/marketUtils`.
> - New parsing util `parseCommaSeparatedString` for LaunchDarkly/env
values.
> - New env vars in `.js.env.example` and CI (`bitrise.yml`):
`MM_PERPS_HIP3_ENABLED`, `MM_PERPS_HIP3_ALLOWLIST_MARKETS`,
`MM_PERPS_HIP3_BLOCKLIST_MARKETS` (replace old DEX-level flags).
> - **Controller & reconnection**:
> - `PerpsController`: ingest remote flags for HIP-3
(enabled/allowlist/blocklist), maintain `hip3ConfigVersion`, and
propagate config to provider.
> - `PerpsConnectionManager`: monitor `selectHip3ConfigVersion` to clear
caches and reconnect on config changes.
> - Add `selectHip3ConfigVersion` selector; default state updated to
include `hip3ConfigVersion`.
> - **Provider & subscriptions**:
> - `HyperLiquidProvider`: apply market filtering, add market metadata
caching (filtered/unfiltered), support `skipFilters`, and pass HIP-3
config to `HyperLiquidSubscriptionService`.
> - `HyperLiquidSubscriptionService`: support HIP-3 on/off, map webData2
(main DEX) vs webData3 (multi-DEX), update feature flags without
teardown; remove redundant clearinghouseState path.
> - **Types & tests**:
> - Extend `GetMarketsParams` with `skipFilters`; update
`PerpsControllerConfig` fallbacks.
> - Extensive unit tests added/updated for HIP-3 config parsing,
filtering, subscriptions, selectors, and connection manager.
> - **Misc**:
>   - Remove unused debug styles in `PerpsTabView.styles.ts`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d93ca77. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

**Problem:**
Fee discount was not being displayed in the "Close all positions" view,
despite working correctly in the single position close view. This caused
users to not see their rewards discount (e.g., -65%) when closing all
positions.

**Solution:**
Added account-level fee discount fetching to
`usePerpsCloseAllCalculations` hook, matching the implementation in the
single position close flow. The fix includes:

1. **Fee Discount Fetching**: Added
`RewardsController.getPerpsDiscountForAccount()` call to fetch user's
discount
2. **Discount Application**: Applied discount to MetaMask fees using
formula: `adjusted_rate = original_rate * (1 - discount_bips/10000)`
3. **Batch API Migration**: Migrated to batch points estimation API for
better performance (N+1 API calls � 2 total calls)
4. **Error Handling**: Preserved `undefined` state for unavailable fees
instead of defaulting to 0

**Technical Details:**
- Account-level discount applies uniformly to all positions
- Per-position fee calculations for accuracy (coin-specific parameters)
- Freeze mechanism prevents repeated calculations on WebSocket position
updates
- Removed `feeDiscountBips` from effect dependencies to prevent
recalculation on every update

## **Changelog**

CHANGELOG entry: Fixed fee discount not displaying in close all
positions view

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1966

## **Manual testing steps**

```gherkin
Feature: Fee discount display in close all positions

  Scenario: user views fee discount when closing all positions
    Given user has an account with fee discount (e.g., 65%)
    And user has multiple open positions

    When user navigates to "Close all positions" view
    Then fee discount badge should display (e.g., "-65%")
    And fees breakdown should show discounted MetaMask fee rate
    And estimated points should be calculated correctly
```

## **Screenshots/Recordings**

### **Before**

Fee discount was hardcoded to 0%, not showing user's actual rewards
discount.

### **After**

Fee discount now displays correctly (e.g., -65%) in the close all
positions view, matching the single position close behavior.


https://github.com/user-attachments/assets/76fa2bbc-bc1f-4b15-8655-82df18df8f3f


## **Implementation Details**

### Files Changed
- `app/components/UI/Perps/hooks/usePerpsCloseAllCalculations.ts`

### Key Changes

1. **Added Fee Discount State** (lines 108-109):
```typescript
const [feeDiscountBips, setFeeDiscountBips] = useState<number>(0);
```

2. **Fetch Account-Level Discount** (lines 137-166):
```typescript
useEffect(() => {
  async function fetchFeeDiscount() {
    const discountBips = await Engine.context.RewardsController.getPerpsDiscountForAccount(caipAccountId);
    setFeeDiscountBips(discountBips);
  }
  fetchFeeDiscount();
}, [selectedAddress, currentChainId]);
```

3. **Apply Discount to Fees** (lines 226-252):
```typescript
const discountMultiplier = feeDiscountBips > 0 ? 1 - feeDiscountBips / 10000 : 1;
const adjustedMetamaskFeeRate = baseFees.metamaskFeeRate * discountMultiplier;
const adjustedMetamaskFeeAmount = baseFees.metamaskFeeAmount !== undefined
  ? baseFees.metamaskFeeAmount * discountMultiplier
  : undefined;
```

4. **Batch Points Estimation** (lines 274-295):
```typescript
const batchEstimateBody: EstimatePointsDto = {
  activityType: 'PERPS',
  account: caipAccountId,
  activityContext: {
    perpsContext: perpsContextArray, // Array of all positions
  },
};
batchPoints = await Engine.context.RewardsController.estimatePoints(batchEstimateBody);
```

5. **Calculate Discount Percentage** (lines 387-395):
```typescript
const avgOriginalMetamaskFeeRate = feeDiscountBips > 0 && avgMetamaskFeeRate > 0
  ? avgMetamaskFeeRate / (1 - feeDiscountBips / 10000)
  : avgMetamaskFeeRate;
const avgFeeDiscountPercentage = feeDiscountBips > 0 ? feeDiscountBips / 100 : 0;
```

6. **Optimized Dependencies** (line 333):
- Removed `feeDiscountBips` from effect dependencies to prevent
recalculation on every WebSocket position update
- Uses ref-based freeze mechanism (`hasValidResultsRef`) to prevent
repeated calculations

### Performance Improvements
- **Batch API**: Reduced API calls from N+1 (one per position + one for
discount) to 2 total calls
- **Freeze Mechanism**: Prevents slow points computation from
retriggering on WebSocket position updates

## **Known Limitations**

There is a separate UI issue with tooltip z-index in the close position
views where the "Close position" button appears on top of tooltip
modals. This will be addressed in a follow-up PR.

## **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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds account-level fee discount to close-all flow, switches rewards to
batch estimation, and makes fee rates/price-driven calculations
resilient with UI fallbacks.
> 
> - **Perps Close All (hook `usePerpsCloseAllCalculations`)**:
> - Fetches and applies account-level discount
(`RewardsController.getPerpsDiscountForAccount`) to MetaMask fees;
computes original vs discounted rates and avg discount.
> - Migrates rewards to batch points estimation; aggregates
totals/averages and guards errors; introduces `isFetchingInBackground`.
> - Freezes results to avoid WS-induced recomputes; resets on
positions/account/discount changes; robust cleanup/race guards.
> - Treats fee rates/amounts as optional (`undefined`) and propagates
through aggregates; `receiveAmount` falls back when fees unavailable.
> - **Perps Close Single (`PerpsClosePositionView`)**:
> - Stabilizes calculations using price refs; uses limit price when
valid; updates P&L/position value derivations; expands tests for limit
validation and price parsing.
> - **UI Summary (`PerpsCloseSummary`)**:
> - Accepts optional fees/rates; shows `"--"` when unavailable; adds
tooltip interactions; updated tests.
> - **Types & Fees Hook**:
> - Marks `FeeCalculationResult` fee fields as optional;
`usePerpsOrderFees` handles undefined rates (no misleading $0), updates
caching checks, and tests edge cases.
> - **Styling**:
> - Ensures close-all footer stays behind overlays
(`zIndex`/`elevation`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
bf388c8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Ramon AC <36987446+racitores@users.noreply.github.com>
Co-authored-by: Christopher Ferreira <104831203+christopherferreira9@users.noreply.github.com>
Co-authored-by: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com>
Co-authored-by: Nick Gambino <35090461+gambinish@users.noreply.github.com>
Co-authored-by: George Weiler <georgejweiler@gmail.com>
Co-authored-by: Luis Taniça <matallui@gmail.com>
Co-authored-by: AxelGes <34173844+AxelGes@users.noreply.github.com>
Co-authored-by: Pedro Pablo Aste Kompen <wachunei@gmail.com>
Co-authored-by: Kylan Hurt <6249205+smilingkylan@users.noreply.github.com>
Co-authored-by: SteP-n-s <stylianos.panagakos@consensys.net>
Co-authored-by: Brian August Nguyen <brianacnguyen@gmail.com>
Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com>
Co-authored-by: Kevin Bluer <kevin@bluer.com>
Co-authored-by: Vince Howard <vincenguyenhoward@gmail.com>
Co-authored-by: Curtis David <Curtis.David7@gmail.com>
Co-authored-by: Caainã Jeronimo <caainaje@gmail.com>
@pull pull Bot locked and limited conversation to collaborators Nov 5, 2025
@pull pull Bot added the ⤵️ pull label Nov 5, 2025
@pull pull Bot merged commit 84da92c into Reality2byte:main Nov 5, 2025
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.

8 participants