Skip to content

[pull] main from MetaMask:main#581

Merged
pull[bot] merged 9 commits intoReality2byte:mainfrom
MetaMask:main
Mar 6, 2026
Merged

[pull] main from MetaMask:main#581
pull[bot] merged 9 commits intoReality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Mar 6, 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 : )

vinistevam and others added 9 commits March 6, 2026 06:51
<!--
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**

If the user is on a send confirmation and the app goes idle until the
device and MetaMask lock, after unlocking they no longer see that
confirmation. If they then start a **new** send, the UI can show the
**previous** confirmation instead of the new one, because the old
approval was never rejected and remains first in the pending list.

**Solution**  
When the app locks, reject all pending approvals by calling
`ApprovalController.clear(providerErrors.userRejectedRequest())` in the
lock saga, before navigating to the lock screen. That way there are no
stale confirmations after unlock, and any new send shows the correct
confirmation.

**Changes**
- **`app/store/sagas/index.ts`**: In `appLockStateMachine`, after
handling `LOCKED_APP`, clear pending approvals via
`Engine.context.ApprovalController.clear(...)` inside try/catch, then
navigate to `LOCK_SCREEN`. Log and ignore errors so navigation still
runs.
- **`app/store/sagas/sagas.test.ts`**: Add `ApprovalController` with
`clear` to the Engine mock; add tests that clear is called with
`userRejectedRequest()` when the app locks and that navigation to
`LOCK_SCREEN` still happens when `clear` throws.
- 
<!--
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: Fixed issue of confirmation not rejecting when app
locks

## **Related issues**

Fixes: #26320

## **Manual testing steps**

```gherkin
Feature: Transaction Confirmation Persistence After Lock

  Scenario: Stale confirmation displayed after device lock timeout and new transaction
    Given the user has MetaMask open and unlocked on the home screen
    # First transaction
    When user initiates a send transaction
    And user reaches the confirmation screen
    # Lock timeout
    And user allows the phone to idle until device and MetaMask lock
    And user unlocks the phone
    And user unlocks MetaMask
    Then the confirmation screen should no longer be open
    # Second transaction - bug occurs
    When user initiates a different send transaction
    And user reaches the confirmation screen
    Then the confirmation shown should be for the previous transaction instead of the current one
```

## **Screenshots/Recordings**


[reject-approval-app-locks.webm](https://github.com/user-attachments/assets/ed331559-bf7a-452b-8688-7014dd4bff34)


<!-- 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
- [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]
> **Medium Risk**
> Changes approval/confirmation lifecycle by clearing all pending
approvals on app lock, which could inadvertently reject legitimate
in-flight requests if triggered unexpectedly. Guarded with try/catch and
covered by new saga tests, but behavior impacts transaction
confirmations.
> 
> **Overview**
> Prevents stale transaction/permission confirmations after unlocking by
clearing any pending approvals when `UserActionType.LOCKED_APP` fires,
rejecting them with `providerErrors.userRejectedRequest()` before
navigating to `Routes.LOCK_SCREEN`.
> 
> Updates saga tests to mock `ApprovalController.clear` and assert it is
invoked on lock, and that navigation to the lock screen still occurs
even if clearing approvals throws.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2f1c2d3. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Adds a `session_summary` analytics event that fires when a user
navigates away from the homepage. This completes the homepage sections
analytics suite alongside the existing `section_viewed` event.

The event reuses the `HOMEPAGE_SECTION_VIEWED` Segment event with
`interaction_type: 'session_summary'` and captures:

- `total_sections_viewed`— how many sections reached ≥50% visibility
this visit
- `total_sections_loaded` — how many sections were enabled via feature
flags
- `entry_point` — how the user arrived (app_opened, home_tab,
navigated_back)
- `session_time` — seconds spent on the homepage
location: 'home'

Implementation details:

- New `useHomepageSessionSummary` hook owns all session tracking. All
state lives in refs — zero re-renders on scroll or blur path.
- Reacts to visitId increments from `useHomepageEntryPoint` to detect
focus and reset per-visit state.
- Fires on navigation blur via a stable ref-wrapped callback to avoid
stale closures.
- `notifySectionViewed` added to `HomepageScrollContext` so sections
self-report views when their `section_viewed` event fires.

Segment Event PR: Consensys/segment-schema#477

This PR needs to be merged first before review:
#26529

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: Homepage session summary analytics

  Scenario: user navigates away from the homepage
    Given the homepage sections feature flag is enabled
    And the user is on the homepage

    When user navigates to another screen (e.g. sends a transaction)
    Then a session_summary event fires with interaction_type "session_summary"
    And session_time reflects time spent on the homepage
    And total_sections_viewed reflects sections that entered the viewport

  Scenario: feature flag is disabled
    Given the homepage sections feature flag is disabled
    When user navigates away from the homepage
    Then no session_summary event is fired
```

## **Screenshots/Recordings**


https://github.com/user-attachments/assets/573caf7a-3afe-4cee-a1e1-5f447f877bac

### **Before**

`~`

### **After**

`~`

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

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] 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**
> Adds new analytics firing on homepage blur and expands
`HomepageScrollContext` contract, which could affect event
volume/accuracy if focus/visit tracking is wrong. Runtime risk is
limited since it uses refs/sets and doesn’t change wallet transaction or
account logic.
> 
> **Overview**
> Adds a new homepage *session summary* analytics emission:
`useHomeSessionSummary` fires `MetaMetricsEvents.HOME_VIEWED` with
`interaction_type: 'session_summary'` when the homepage blurs, including
`session_time`, `entry_point`, `total_sections_loaded`, and
`total_sections_viewed`.
> 
> Extends `HomepageScrollContext` with
`notifySectionViewed`/`getViewedSectionCount`; `Wallet` now tracks
distinct viewed sections per `visitId` in a ref-backed `Set`, and
`useHomeViewedEvent` reports section views into this aggregator.
Includes new unit tests for the session-summary hook and updates
existing section-view tests for the new context fields.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d91715f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Fixes a bug in the bridge token selector where Polygon's native token
(POL) appeared twice in the list — once with balance at the top and once
without balance at the bottom. Selecting the balance entry as a
destination token caused the quote to show 0 and the rate to display
"--".

**Root cause**: Polygon's native token uses address
`0x0000000000000000000000000000000000001010` in wallet state (from
`getNativeTokenAddress`), but the bridge API expects `AddressZero`
(`0x0000...0000`) for all native assets. The bridge-controller's
`isNativeAddress()` does not recognize `0x...1010` as native, so:
1. `tokenToIncludeAsset` sent the wrong asset ID (`erc20:0x...1010`
instead of `slip44:966`) to the API, which couldn't deduplicate it with
its own native POL entry.
2. When the user selected POL with `0x...1010`, quote matching in
`useBridgeQuoteData` failed because the returned quote used
`AddressZero` for `destAsset.address`.

**Fix**: Extracted the existing normalization logic from
`useTokenAddress` into a reusable pure function `normalizeTokenAddress`,
and applied it in `useTokensWithBalance` when building `BridgeToken`
objects from wallet state. This ensures POL enters the bridge flow with
`AddressZero` from the start, fixing both the duplicate listing and the
quote/rate mismatch.

## **Changelog**

CHANGELOG entry: Fixed a bug where Polygon's native token (POL) appeared
twice in the bridge token selector and selecting it showed incorrect
quote data.

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: Bridge token selector - Polygon native token

  Scenario: user selects POL as destination token on Polygon
    Given user has a POL balance on Polygon
    And user opens the bridge token selector for destination

    When user filters by Polygon network
    Then POL appears only once in the token list with balance displayed

  Scenario: user gets a valid quote after selecting POL destination
    Given user has a source token with balance
    And user has selected POL on Polygon as the destination token

    When the quote loads
    Then the destination input shows a non-zero amount
    And the rate displays a valid exchange rate (not "--")
```

## **Screenshots/Recordings**

### **Before**


### **After**


## **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]
> **Medium Risk**
> Medium risk because it changes how token addresses are represented in
the bridge token list and downstream API/quote matching, which could
affect token identification on Polygon. Scope is small and isolated to
bridge token normalization.
> 
> **Overview**
> Fixes Polygon native token (POL) handling in the bridge UI by
normalizing Polygon’s non-zero native token address to the zero address
the bridge API expects.
> 
> Extracts the Polygon-specific normalization from `useTokenAddress`
into a reusable `normalizeTokenAddress` utility and applies it when
constructing tokens in `useTokensWithBalance`, preventing duplicate POL
entries and quote mismatches caused by inconsistent native-address
representations.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b0deef0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

This PR implements the mobile Swap zero-state Trending Tokens experience
for Bridge and hardens related Bridge rendering behavior.

Key updates:
- Added `BridgeTrendingTokensSection` to render Trending tokens only in
Swap zero state.
- Added filter controls (Sort by / Network / Time) and list chunking
with a centered "Load more" action while preserving single-screen scroll
behavior.
- Refined `BridgeView` content-mode precedence so
loading/error/quote/zero states render deterministically.
- Preserved quote + confirm visibility during quote refresh (`isLoading
&& activeQuote`) and only show skeleton when loading without an active
quote.
- Updated/expanded Bridge tests and removed brittle snapshot dependency
in `BridgeView` tests.

## **Changelog**

CHANGELOG entry: Added Trending tokens to the mobile Swap zero state
with filter controls and improved Bridge quote/loading state handling.

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/SWAPS-4038

## **Manual testing steps**

```gherkin
Feature: Swap zero-state trending list on mobile

  Scenario: Trending list visibility follows zero state
    Given user is on the Swap screen
    When no source amount is entered
    Then Trending tokens are visible below the swap form
    When user enters a non-zero source amount
    Then Trending tokens are hidden

  Scenario: Numpad hidden on initial load
    Given user opens Swap for the first time
    When the screen is rendered
    Then numpad is hidden and swap form is visible

  Scenario: Quote loading and refresh behavior
    Given user has entered a non-zero amount
    When quote is loading with no active quote
    Then quote skeleton is shown and trending list is hidden
    When quote is refreshing with an active quote
    Then quote content and confirm button remain visible

  Scenario: Single scroll behavior
    Given user is in zero state with Trending tokens visible
    When user scrolls
    Then swap form and trending list scroll together in one vertical scroll area

  Scenario: Filters update results
    Given user is in zero state with Trending tokens visible
    When user changes Sort by, Network, or Time filters
    Then list content updates to match selected filters
    And default sort is Price change high to low
```

## **Screenshots/Recordings**

### **Before**

N/A

### **After**



https://github.com/user-attachments/assets/e55f04c5-6190-4c26-a15a-2e0c00a8b879


## **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]
> **Medium Risk**
> Changes Bridge/Swap screen rendering precedence
(loading/error/quote/zero) and scroll behavior, which could affect quote
visibility and confirm UX during refreshes. Mostly UI/state-driven with
good test coverage but touches a core transaction entry flow.
> 
> **Overview**
> Adds a **Swap zero-state Trending Tokens** section to `BridgeView`,
gated behind the temporary `swapsTrendingTokens` remote feature flag,
with filter bottom sheets and incremental “show more” loading triggered
by button or near-bottom scroll.
> 
> Refactors `BridgeView` to render deterministically via a `contentMode`
state machine: shows a `QuoteDetailsCardSkeleton` only when *loading
without an active quote*, preserves quote + confirm UI while refreshing
(`isLoading && activeQuote`), and keeps error banners/zero-state
separate from quote content.
> 
> Updates styles to support a single unified scroll area (inputs +
dynamic content), introduces new `testID`s, and rewrites/expands tests
to avoid brittle snapshots and to assert the new
loading/error/quote/zero behaviors (including mocking the trending
section).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ab8ffe2. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…laim timeline (#27097)

<!--
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**
This PR updates mUSD conversion copy to reflect annualized bonus and
claim timeline.
<!--
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: updated mUSD conversion copy to reflect annualized
bonus and claim timeline

## **Related issues**

Fixes:
- [MUSD-392: Annual bonus
copy](https://consensyssoftware.atlassian.net/browse/MUSD-392)
- [MUSD-393: Communicate the timeframe of the
bonus](https://consensyssoftware.atlassian.net/browse/MUSD-393)

## **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] -->
### Education screen
<img width="489" height="1022" alt="image"
src="https://github.com/user-attachments/assets/e4435213-193b-4fc8-9212-4c797a5a3fc1"
/>

### Custom convert navbar tooltip
<img width="489" height="1022" alt="image"
src="https://github.com/user-attachments/assets/b26a4251-4c2e-4aa0-b044-279894e609ce"
/>

### Claimable bonus tooltip

Custom convert
<img width="489" height="1022" alt="image"
src="https://github.com/user-attachments/assets/8e706082-165b-455e-835b-f17c555fd313"
/>

Quick conver
<img width="489" height="1022" alt="image"
src="https://github.com/user-attachments/assets/842dfe5f-fb8c-40dd-ba71-6609b2fbfb2f"
/>

### Asset details CTA
<img width="489" height="1022" alt="image"
src="https://github.com/user-attachments/assets/10d66946-64e2-4bb4-8d9a-915f64cef29a"
/>

## **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]
> **Low Risk**
> Low risk: primarily copy/translation key updates plus minor UI
tooltip/toast rendering changes (adds a terms link and an extra
success-toast description) with no changes to conversion logic or data
handling.
> 
> **Overview**
> Updates mUSD conversion user-facing messaging to consistently describe
the incentive as an *annualized bonus* and to communicate that the bonus
becomes claimable within about a day.
> 
> This refreshes strings across the education screen, quick convert
header, asset overview CTA, claimable bonus tooltip, and conversion
success toast (now includes a secondary description line), and adjusts
the confirmation `PercentageRow` tooltip to include a tappable “Terms
apply” link to the bonus terms URL. Tests are updated to match the new
copy and label formatting.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
388fcc0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

This PR is the Rewards-only split of the `color-no-hex` batch work,
extracted from the original umbrella PR #26651.

Scope:
- Rewards files only (`app/components/UI/Rewards/**`)
- temporary eslint rollout override for
`app/components/UI/Rewards/**/*.{js,jsx,ts,tsx}`
- includes replacing straightforward mock color suppressions with
`mockTheme` in a subset of Rewards tests

Reference PR: #26651

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: color-no-hex rewards batch

  Scenario: validate rewards lint and tests
    Given this branch is checked out
    When running eslint for Rewards scope
    Then there are no lint errors

    When running jest for Rewards scope with snapshot updates
    Then tests pass
```

## **Screenshots/Recordings**

### **Before**
N/A (test/lint/config updates only)

### **After**
N/A (test/lint/config updates only)

## **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
- [ ] 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]
> **Low Risk**
> Low runtime risk since changes are limited to ESLint configuration
plus test/story updates; main risk is CI/dev friction if any remaining
Rewards hex literals trigger the newly-enforced lint rule.
> 
> **Overview**
> **Enforces `@metamask/design-tokens/color-no-hex` for Rewards UI
code.** Updates `.eslintrc.js` to include
`app/components/UI/Rewards/**/*` in the folders where hex colors are
treated as lint errors.
> 
> **Aligns Rewards tests/stories with the rule.** Rewards tests now mock
`useTheme` by reusing the shared `mockTheme` (and remove a local
onboarding `mockTheme` helper), and a Rewards Storybook story
(`RewardPointsAnimation`) is refactored to use Tailwind/design-system
`Button`s instead of inline styles/hex colors, with a couple of tests
explicitly scoping hex-only mock API colors behind lint disables.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3a5d5c1. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

As part of Tron's staking experience improvements we will be sending
more special assets from the Snap to the Extension. These special assets
are not tradeable tokens and should be filtered out from selectors like
we already do for Staked TRX for example.

This PR:
- Adds the new special assets that should be ignored by the selectors
- Renames the variables that deal with this logic to be more inclusive
of assets that are not resources (only Energy and Bandwidth are
resources)

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Closes:
[NEB-582](https://consensyssoftware.atlassian.net/browse/NEB-582),
[NEB-584](https://consensyssoftware.atlassian.net/browse/NEB-584),
[NEB-586](https://consensyssoftware.atlassian.net/browse/NEB-586)

## **Manual testing steps**

All existing Tron functionality should remain unchanged

## **Screenshots/Recordings**

As you can see, the new assets being loaded from the preview build of
MetaMask/snap-tron-wallet#226 are not being
shown here.

### **Before**

n/a

### **After**

n/a

## **Pre-merge author checklist**

- [x] I've followed MetaMask Contributor Docs and MetaMask Mobile Coding
Standards.
- [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 format if applicable
- [x] I've applied the right labels on the PR

[NEB-582]:
https://consensyssoftware.atlassian.net/browse/NEB-582?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[NEB-584]:
https://consensyssoftware.atlassian.net/browse/NEB-584?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[NEB-586]:
https://consensyssoftware.atlassian.net/browse/NEB-586?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes token/asset filtering for Tron by excluding additional
Snap-provided “special assets” from sorted asset lists and unified
multichain token lists, which could inadvertently hide tokens if symbols
collide or filtering is misapplied. Scope is contained to Tron
selectors/utilities and related UI consumers, with broad test updates.
> 
> **Overview**
> Introduces a broader Tron *“special assets”* concept (resources +
staking lifecycle assets) and filters these virtual tokens out of
user-facing asset/token lists.
> 
> Renames and expands the Tron selector from
`selectTronResourcesBySelectedAccountGroup` to
`selectTronSpecialAssetsBySelectedAccountGroup` (and `TronResourcesMap`
to `TronSpecialAssetsMap`), adding mappings for `trxReadyForWithdrawal`,
`trxStakingRewards`, and `trxInLockPeriod` while preserving
`totalStakedTrx` computation.
> 
> Centralizes special-asset detection in `core/Multichain/utils` via
`isTronSpecialAsset` and reuses it in
`selectSortedAssetsBySelectedAccountGroup`,
`selectAccountTokensAcrossChainsUnified`, and Bridge `isTradableToken`;
updates related Earn/TokenDetails/AssetOverview hooks and tests
accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
893e98a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Previously, the price impact value color was driven by a warning boolean
and effectively rendered as default or red. This change updates the
value color by threshold so the UI communicates risk levels more
clearly:
- Price impact `>= 5%` shows warning color (yellow)
- Price impact `>= 25%` shows error color (red)
- Otherwise it remains the default alternative text color

## **Changelog**

CHANGELOG entry: Updated swap price impact text coloring.

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/SWAPS-4020
https://consensyssoftware.atlassian.net/browse/SWAPS-4024

## **Manual testing steps**

```gherkin
Ensure acceptance criteria pass.
```

## **Screenshots/Recordings**

### **Before**

Price impact value color only switched between default and red.

### **After**

Price impact value color now maps to thresholds:
- default: `< 5%`
- yellow: `>= 5%`
- red: `>= 25%`

## **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]
> **Medium Risk**
> Changes the Bridge confirmation flow to conditionally gate execution
behind a new price-impact modal and adds navigation resets on quote
expiry, which can affect swap completion and modal routing. Risk is
mitigated by extensive new unit tests but touches user-critical
transaction submission UX.
> 
> **Overview**
> Adds a new `PriceImpactModal` (with header/description/footer) and
wires it into Bridge modal routes, including new i18n copy for *info*,
*warning*, and *high price impact* states.
> 
> Updates `QuoteDetailsCard` to always show a **Price impact** row with
an info button that opens the modal, and switches price-impact
coloring/iconography to threshold-based view data (warning at `>=5%`,
error at `>=25%`) plus safer formatting for missing/invalid/negative
values.
> 
> Refactors swap submission by introducing `useBridgeConfirm` and
updating `SwapsConfirmButton` to accept/forward an explicit analytics
`location`; if price impact meets the error threshold it now navigates
to the modal (`Execution` type) instead of submitting immediately. Adds
`useModalCloseOnQuoteExpiry` and applies it across Bridge modals to
reset the modal stack to `QuoteExpiredModal` when quotes expire.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3941641. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: GeorgeGkas <georgegkas@gmail.com>
<!--
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**

# Perpetuals section – performance and stability

Performance audit follow-up: fewer re-renders, no redundant
subscriptions on the homepage, and safer selectors/hooks.

## Homepage Perps

- **Carousel: static data only** – Removed live price subscription from
`PerpsMarketTileCard`. Tiles use the market snapshot from
`usePerpsMarkets()` (price, change24hPercent). No WebSocket per symbol
on the homepage; fewer subscriptions and re-renders.
- **Tile card** – Dropped `livePrices` / `disableLivePrices`; component
always uses static market data. Removed `TileCardWithLivePrices` and
`usePerpsLivePrices` usage there.
- **Position rows** – `PositionCardItem` with a `positionDisplayKey`
(symbol, entryPrice, size, unrealizedPnl, takeProfitPrice,
stopLossPrice) and custom `React.memo` compare so only cards whose
display data changed re-render on stream updates.
- **Defensive defaults** – `?? []` for watchlist/carousel arrays and
`carouselSymbols` so selectors or partial state (e.g. E2E/minimal
fixtures) never pass `undefined` into hooks or `.map()`.
- **Sparklines** – `useHomepageSparklines`: guard `candleData?.candles`
(fixes E2E crash when `candles` is undefined), `safeSymbols` so
`symbols` is never undefined, and microtask batching so multiple symbol
callbacks trigger one state update.

## Perps selectors & components

- **perpsController selectors** – Try/catch and defaults when state is
missing or partial (e.g. before Engine init or in E2E). Avoids calling
package selectors with `undefined` and normalizes return values (`?? []`
/ default prefs).
- **PerpsCard / PerpsPositionCard** – Wrapped with `React.memo` to avoid
unnecessary re-renders when parent updates.
- **usePerpsMarketListView** – `savedSortPreference.optionId` cast to
`SortOptionId` for type safety.

## Tests

- **PerpsSection** – Tests for `positionDisplayKey` (stable key,
optional fields, TP/SL, same key when only non-display fields differ).
- **PerpsMarketTileCard** – Removed live-price mock and related test;
added “displays market change24hPercent” for static data.
- **useHomepageSparklines** – Async `act` where needed for
microtask-flushed updates.
- **Homepage** – `useFocusEffect` mock simplified (invoke callback
once).


## **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: https://consensyssoftware.atlassian.net/browse/TMCU-512

## **Manual testing steps**

```gherkin
Feature: Perpetuals section performance and data freshness

  Scenario: Homepage carousel shows static data and does not re-render on a timer
    Given the user is on the homepage with no open perps positions/orders
    When the trending perps carousel is visible
    Then the section title and carousel tiles show market data (symbol, price, 24h change)
    And the section does not re-render every few seconds (observe in React DevTools or logs)
    And navigating away and back to the homepage refreshes carousel data

  Scenario: Homepage positions list re-renders only when position data changes
    Given the user has open perps positions and is on the homepage
    When the positions stream emits updates (e.g. every 5s)
    Then the section title does not flicker or re-render
    And only position cards whose data actually changed re-render

  Scenario: Pull-to-refresh updates markets and sparklines
    Given the user is on the homepage with the Perps section visible
    When the user triggers the section refresh (e.g. pull-to-refresh if wired)
    Then market data and sparklines are refetched
```

## **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
- [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]
> **Medium Risk**
> Moderate risk because it changes rendering/memoization behavior and
introduces a module-level TTL cache that could cause stale data or
missed UI updates if the cache keys/comparators are wrong.
> 
> **Overview**
> **Perps homepage performance improvements.** Position/order rows now
avoid unnecessary re-renders via `React.memo` (including a new
`positionDisplayKey` comparator) and null-safe carousel list handling;
`PerpsCard`/`PerpsPositionCard` are also exported as memoized
components.
> 
> **Trending carousel simplification.** `PerpsMarketTileCard` no longer
subscribes to live prices and drops the `disableLivePrices` prop; it
always renders from the passed market snapshot, and tests are updated
accordingly.
> 
> **Fewer update storms.** `useHomepageSparklines` batches per-symbol
candle callbacks into a single microtask-flushed state update, and
related tests are adjusted.
> 
> **Stability + networking.** Perps Redux selectors now defensively
default/guard against missing controller state, and `useRampTokens` adds
a 5-minute, module-level cache that deduplicates identical requests
(including in-flight), with comprehensive cache behavior tests.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
49cecc3. 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 6, 2026
@pull pull Bot added the ⤵️ pull label Mar 6, 2026
@pull pull Bot merged commit a290214 into Reality2byte:main Mar 6, 2026
0 of 2 checks passed
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.

7 participants