[pull] main from MetaMask:main#575
Merged
pull[bot] merged 13 commits intoReality2byte:mainfrom Mar 4, 2026
Merged
Conversation
## **Description** Disables the Max button for Predict Withdraw. We will enable it once we support Max with a Predict balance. ## **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: Disable the Max button for Predict Withdraw ## **Related issues** Fixes: #26944 ## **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** - [ ] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: a small UI-prop change that only affects whether the Max button is shown for Predict withdraw, with a focused unit test to prevent regressions. > > **Overview** > Disables the **Max** button in the Predict Withdraw confirmation flow by no longer passing `hasMax` into `CustomAmountInfo` from `PredictWithdrawInfo`. > > Adds a unit test asserting `PredictWithdrawInfo` does not enable `hasMax`, ensuring the Max control stays off until a Predict balance-backed max is supported. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 21b8547. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Signed-off-by: dan437 <80175477+dan437@users.noreply.github.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**
Hides the 'quote expired' bottom sheet when outside of the bridge view
(for example, looking at the tokens list).
<!--
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: hides a quote expired element when outside of the
bridge page
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: swaps and bridge
Scenario: user let's a quote expire
When user navigates away from the bridge view
Then we should not show the quote expired bottom sheet
```
## **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]
> **Low Risk**
> Low risk: adds a focus gate around existing modal navigation logic and
extends unit tests; no changes to quote computation or transaction flow.
>
> **Overview**
> Prevents the Bridge "quote expired" bottom sheet from appearing when
the user has navigated away from the Bridge screen by gating
`useRenderQuoteExpireModal` on `useIsFocused()`.
>
> Updates the hook tests to mock focus state and cover the new behavior,
including not showing while unfocused and showing once focus is regained
while the quote remains expired.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
23aa0ea. 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** This PR improves Android Detox launch reliability and debuggability by adding targeted handling for intermittent adb reverse collisions that happen during app launch (outside our fixture port-forwarding flow). ### What changed - Added pre-launch diagnostics in tests/helpers.js to log current Android reverse mappings: - Runs adb -s <device> reverse --list immediately before device.launchApp() - Logs the same list again when launch fails and after cleanup, to aid CI triage - Added guarded launch recovery for a known flaky Detox failure mode: - Detects launch errors containing adb reverse ... Address already in use - Extracts the conflicting ephemeral port from the error (pattern `reverse tcp:<PORT> tcp:<PORT>`) - Removes only that conflicting reverse mapping (`adb reverse --remove tcp:<PORT>`) - Retries device.launchApp() once - Reused the same recovery path in both: - standard launch flow (launchApp) - debug/deep-link launch flow (launchAppForDebugBuild) ### Why this is needed - Our fixture forwarding uses static→allocated mappings (e.g. 12345 -> 40151) and logs through FixtureUtils. - The CI failure seen (reverse tcp:39825 tcp:39825) is Detox-managed ephemeral forwarding during launch, not fixture forwarding. - When that ephemeral reverse mapping is already present/stale, launch fails before Detox can connect to the app. - This PR adds observability plus a minimal, targeted recovery for that exact case without disturbing normal fixture forwards. ### Additional test update in this branch - Updated tests/smoke/card/card-home-add-funds.spec.ts analytics checks to use: - Assertions.checkIfArrayHasLength(<events>, 1) - This prevents false positives where filter(...) returns [] (truthy) and previously passed checkIfValueIsDefined. <!-- 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: ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMQA-1548 ## **Manual testing steps** N/A ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A <!-- [screenshots/recordings] --> ### **After** N/A <!-- [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** > Adds Android-specific `adb` reverse inspection and cleanup plus a one-time relaunch retry, which can change CI/device launch behavior and relies on parsing error strings and executing shell commands. Scope is limited to Detox test helpers and one smoke spec assertion change. > > **Overview** > Improves Android Detox launch reliability by routing all `TestHelpers.launchApp()` (including debug/deeplink launches) through a new `launchAppWithRecovery()` wrapper that logs current `adb reverse --list`, detects the flaky *"Address already in use"* reverse-port collision, removes the conflicting `tcp:<port>` mapping, and retries `device.launchApp()` once. > > Also tightens the Card Home smoke test analytics assertions by checking filtered event arrays have length `1` (and removes an unused expected event), preventing false positives when no events are emitted. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5f8cfec. 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** Don't show OTA modal when users are on the onboarding screen, the updates will apply in the next session automatically. Ticket: https://consensyssoftware.atlassian.net/browse/MCWP-374 Testing workflow: https://github.com/MetaMask/metamask-mobile/actions/runs/22643630725/job/65629911055 ## **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: Removed showing OTA modal when users are on onboarding screen ## **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** 1. When users are on the onboarding screen and close the app, there is no modal and the updates apply in the next session  2. When users are on the onboarding screen and continue the onboarding, there is no modal and the updates apply in the next session  second launch <img width="463" height="930" alt="Screenshot 2026-03-03 at 2 26 06 PM" src="https://github.com/user-attachments/assets/47d3d1d1-e0aa-4306-8679-dea48fbc36ac" /> 4. When users are already logged in (no changes from current behaviour)  ## **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] > **Low Risk** > Low risk: small conditional change to OTA-update UX gating with targeted test updates; main risk is accidentally suppressing the update modal for some users if onboarding state is misreported. > > **Overview** > Prevents the OTA update modal from showing during onboarding by gating the `useOTAUpdates` post-download behavior on `selectCompletedOnboarding`. > > When a new update is fetched while onboarding is incomplete, the hook now **logs and defers applying the update to the next app launch** instead of navigating to `OTAUpdatesModal`; existing behavior (showing the modal after interactions) remains for users who already completed onboarding. Tests are updated to cover both onboarding-complete and onboarding-incomplete cases and to assert no `reloadAsync` call from the hook. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0074e7c. 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** This PR updates the Activity (transactions) screen to use the shared header components **HeaderRoot** and **HeaderCompactStandard** with inline headers and proper safe area handling, following the same pattern as settings-general. **Reason for change:** The Activity screen previously used a custom `HeaderBase` with manual back-button logic and relied on `navigation.setOptions(getTransactionsNavbarOptions(...))` for the stack header. That `setOptions` call had no visible effect because the screen is already registered with `headerShown: false` in the navigator. Unifying on HeaderRoot/HeaderCompactStandard improves consistency, removes dead code, and ensures the screen is wrapped in SafeAreaView for correct insets. ## **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/DSYS-503 ## **Manual testing steps** ```gherkin Feature: Activity screen header and safe area Scenario: Activity tab shows HeaderRoot with title only Given the app is on the main tab bar When the user taps the Activity tab Then the Activity screen shows with a header that displays "Activity" and has no back button and no end accessory Scenario: Perps Activity (from See all) shows HeaderCompactStandard with back button Given the user is in the Perps section (e.g. Perps home with recent activity or market trades) When the user taps "See all" to open Activity with the Perps tab focused Then the Activity screen shows with a header that displays "Activity" and has a back button; tapping back returns to the previous screen Scenario: Safe area and layout Given the app is on the Activity screen (tab or Perps entry) When the user views the screen on a device with a notch or safe area Then the header and content respect the safe area (no overlap with status bar or home indicator) ``` ## **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/f3174511-a4a6-4121-89ed-435b28820d75 ## **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** > User-visible layout/navigation changes on the Activity screen (safe-area handling and header rendering/back behavior) could regress spacing or back navigation in some entry points. Also removes previously wired metrics/navigation option code, which may affect analytics expectations if still relied on elsewhere. > > **Overview** > Updates the Activity screen to render an inline `SafeAreaView` and shared header components (`HeaderRoot` by default, `HeaderCompactStandard` when `showBackButton` is true), instead of the previous `HeaderBase` + `navigation.setOptions(...)` approach. > > Removes now-unused account-switcher/metrics and safe-area inset plumbing, adds new testIDs for the safe area + headers, and updates unit tests/snapshots to assert the new header/safe-area structure. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bdcaf99. 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** Display OTA Summary in the workflow test workflow: https://github.com/MetaMask/metamask-mobile/actions/runs/22647455875 ## **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 OTA Summary in the workflow ## **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** <img width="612" height="358" alt="Screenshot 2026-03-03 at 3 25 27 PM" src="https://github.com/user-attachments/assets/13dbf239-8bdb-4198-9df0-16a072b37a19" /> ## **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] > **Low Risk** > Low risk: adds a new GitHub Actions job that only writes informational output to the workflow run summary and does not change build/publish logic. > > **Overview** > Adds an **"OTA Update Summary"** job to `push-eas-update.yml` that writes key dispatch inputs (update message/version, base ref, channel, and target commit) to `$GITHUB_STEP_SUMMARY` for easier visibility when running the workflow. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7cfb169. 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** Fixes Ledger blind signing errors showing as "Something went wrong" instead of "Blind Signing Disabled" during swaps and contract interactions. `@metamask/eth-ledger-bridge-keyring` wraps the Ledger `TransportStatusError(0x6985)` into a `HardwareWalletError` with `ErrorCode.Unknown` due to a cross-realm instanceof failure in the RN bundle. Our parser accepted that classification without checking the message, which clearly contains "Blind signing". The fix re-parses `ErrorCode.Unknown` errors by checking the message against known patterns before giving up. ## **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** no manual testing steps ## **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** - [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** > Changes error classification for `ErrorCode.Unknown` objects, which can affect downstream UI/flows that key off specific error codes. Scope is small and limited to message-based re-parsing before falling back to `Unknown`. > > **Overview** > Ensures `parseErrorByType` doesn’t blindly accept `{ code: ErrorCode.Unknown, message }` objects: it now attempts `parseErrorByMessage` first and returns a more specific `HardwareWalletError` when the message matches known patterns (notably Ledger blind-signing/contract-data prompts). > > Adds a unit test covering re-parsing an `ErrorCode.Unknown` object into `ErrorCode.DeviceStateBlindSignNotSupported`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 386cba3. 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**
## Summary
This PR adds **Sentry instrumentation for Performance E2E tests** so
timer-based metrics are sent per scenario, with CI controls to choose
whether data goes to **test** or **real** Sentry.
---
## What changed
### 1) New Sentry publisher for performance scenarios
- Added:
- `tests/reporters/providers/sentry/PerformanceSentryPublisher.ts`
- `tests/reporters/providers/sentry/PerformanceSentryPublisher.test.ts`
The publisher sends one Sentry `transaction` per scenario, including:
- timer durations as `measurements` (ms),
- scenario metadata (`project`, `tags`, `status`, `retry`, `worker`,
team),
- timer validation details under `extra.timer_steps`.
---
### 2) Integrated publishing into performance fixture (best-effort)
- Updated:
- `tests/framework/fixtures/performance/performance-fixture.ts`
After metrics are attached, the fixture attempts Sentry publishing.
Publishing is wrapped in `try/catch` so Sentry failures do **not**
block:
- quality gate validation,
- session-data attachment.
---
### 3) CI workflow selector for Sentry target (`test | real`)
- Updated:
- `.github/workflows/run-performance-e2e.yml`
- `.github/workflows/run-performance-e2e-release.yml`
- `.github/workflows/run-performance-e2e-experimental.yml`
- `.github/workflows/performance-test-runner.yml`
New workflow input: `sentry_target`
Behavior:
- `test` → uses `MM_SENTRY_DSN_TEST`
- `real` → uses `MM_SENTRY_DSN`
- invalid value → explicit workflow error
For real target, environment is tagged as:
- `github-actions-performance-e2e-real`
---
### 4) Publisher hardening fixes
In `PerformanceSentryPublisher.ts`:
- Measurement keys now remain within the intended **64-char max**,
including collision suffixes.
- Reserved aggregate keys are protected from timer-key collisions:
- `scenario_total_time_ms`
- `scenario_total_threshold_ms`
- Sentry envelope endpoint now includes auth query params:
- `sentry_key=<publicKey>`
- `sentry_version=7`
---
### 5) Types and docs updates
- Updated:
- `tests/reporters/PerformanceTracker.ts` (`MetricsOutput` exported)
- `tests/performance/README.md` (Sentry instrumentation section)
- `.e2e.env.example` (new `E2E_PERFORMANCE_SENTRY_*` vars)
---
## Notes for reviewers
- Scope is test infra + CI only (no product runtime behavior changes).
- Sentry publishing is intentionally **best-effort** and non-blocking
for teardown-critical steps.
- Unit tests were expanded to cover:
- DSN parsing/auth endpoint format,
- reserved measurement key protection,
- key-length constraints under collisions,
- sample-rate validation.
<!--
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:
## **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] -->
## **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.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds new outbound Sentry uploads from performance E2E runs and threads
new secrets/inputs through GitHub Actions, which could affect CI
behavior if misconfigured or if event volume is high. Uploads are gated
by env flags/DSN and include sampling controls, reducing blast radius.
>
> **Overview**
> Adds **optional Sentry instrumentation for performance E2E
scenarios**, emitting one Sentry `transaction` per test with timer
durations as measurements plus scenario metadata.
>
> Introduces `PerformanceSentryPublisher` (with unit tests) and wires it
into the performance fixture so metrics are published after
`PerformanceTracker.attachToTest()` succeeds; `MetricsOutput` is
exported to support this integration.
>
> Updates performance GitHub Actions workflows to accept a
`sentry_target` (test vs real), select the appropriate DSN secret, and
export `E2E_PERFORMANCE_SENTRY_*` env vars; documents the new
configuration in `.e2e.env.example` and `tests/performance/README.md`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2ba0d7f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: javiergarciavera <javiergarciavera@users.noreply.github.com>
…tItem components (#26953) <!-- 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 fixes an issue where the mUSD "Claim bonus" was displayed for geo-blocked users. ### Changes: - Create `selectMerklClaimTransactions` selector with deep-equal memoization to prevent re-render cascades from unrelated transaction updates - Create `useMerklBonusClaim` hook to replace `MerklClaimHandler` headless component pattern, composing `useMerklRewards`, `usePendingMerklClaim`, and `useMerklClaim` with internal eligibility gating - Update `usePendingMerklClaim` to use targeted Merkl transaction selector - Refactor `TokenListItem` and `TokenListItemV2` to use `useMerklBonusClaim` directly - Rename `isEligibleForMerklRewards` to `isTokenEligibleForMerklRewards` - Remove `MerklClaimHandler` - Rename `useMerklClaim` to `useMerklClaimTransaction` to better differentiate against `useMerklBonusClaim` <!-- 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: refactored merkl claim headless structure in token list item components and prevent "claim bonus" cta from rendering when users are geo-blocked ## **Related issues** Fixes: [MUSD-450: Claim bonus visible for geo-blocked users](https://consensyssoftware.atlassian.net/browse/MUSD-450) ## **Manual testing steps** ```gherkin Feature: Geo-blocked users cannot see Merkl "Claim bonus" CTA Scenario: user in allowed region sees "Claim bonus" CTA Given user is in a non-blocked region And user holds a Merkl-eligible token with claimable rewards And the Merkl campaign claiming feature flag is enabled When user views the token in the token list Then the "Claim bonus" CTA is displayed on the token row Scenario: user in blocked region does not see "Claim bonus" CTA Given user is in a geo-blocked region And user holds a Merkl-eligible token with claimable rewards And the Merkl campaign claiming feature flag is enabled When user views the token in the token list Then the "Claim bonus" CTA is not displayed And the standard percentage change is shown instead ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** `"Claim bonus"` CTA is displayed for geo-blocked users. <!-- [screenshots/recordings] --> ### **After** `"Claim bonus"` CTA is hidden for geo-blocked users. <!-- [screenshots/recordings] --> Recording to show claim experience this functions correctly after the refactor https://github.com/user-attachments/assets/6e6af59f-5f0a-4c69-8ad7-bc1a75eae61c ## **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 eligibility and pending-transaction plumbing that controls when the Merkl “Claim bonus” CTA is shown and when claim spinners/pending state appear, so regressions could hide/show the CTA incorrectly. Scope is limited to Merkl claim UI/hooks and is covered by new/updated unit tests. > > **Overview** > Fixes Merkl “Claim bonus” CTA eligibility by replacing the `MerklClaimHandler` headless component/state-sync pattern with a new `useMerklBonusClaim` hook that composes claimable rewards, pending-claim status, and claim transaction submission while **no-op’ing for ineligible/geo-blocked users**. > > Adds `selectMerklClaimTransactions` (deep-equal memoized) and updates `usePendingMerklClaim` to consume this filtered selector instead of scanning all transactions, reducing unrelated re-render cascades. > > Renames `isEligibleForMerklRewards` to `isTokenEligibleForMerklRewards`, renames `useMerklClaim` to `useMerklClaimTransaction`, and refactors `TokenListItem`/`TokenListItemV2` (and tests) to rely solely on the composed hook for CTA/spinner behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 820459d. 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**
Migrates all Card feature data-fetching hooks from a custom Redux-based
caching system (`useWrapWithCache`) to React Query
(`@tanstack/react-query`), reducing complexity and eliminating the
persisted Redux `card` slice.
**Why**: The `useWrapWithCache` hook duplicated caching logic that React
Query handles natively (stale-while-revalidate, garbage collection,
deduplication). React Query keeps this data in-memory only.
**What changed**:
- **Replaced `useWrapWithCache`** with `useQuery` / `useQueryClient`
across 10+ hooks: `useGetDelegationSettings`,
`useGetCardExternalWalletDetails`, `useCardDetails`,
`useGetLatestAllowanceForPriorityToken`, `useGetPriorityCardToken`,
`useGetUserKYCStatus`, `useUserRegistrationStatus`,
`useRegistrationSettings`, `useRegisterUserConsent`.
- **New query key factory** (`queries/keys.ts`): Centralized `cardKeys`
object for consistent cache key management and targeted invalidation.
- **Simplified `useLoadCardData`**: Orchestrates `useQuery` hooks;
`fetchAllData` uses `queryClient.refetchQueries` instead of imperative
fetch functions. Static config (`delegationSettings`) is excluded from
pull-to-refresh to avoid unnecessary round-trips.
- **Mutation hooks updated**: `useSpendingLimit`,
`useUpdateTokenPriority`, `DaimoPayModal` now call
`queryClient.invalidateQueries` instead of Redux `clearCacheData`
dispatches.
- **Auth error cleanup**: `CardHome` now calls
`queryClient.removeQueries` instead of `dispatch(clearAllCache())`.
- **Removed dead code**: Deleted `useWrapWithCache` hook and its
598-line test file. Removed `cache`, `priorityTokensByAddress`,
`lastFetchedByAddress`, `authenticatedPriorityToken`,
`authenticatedPriorityTokenLastFetched` fields and related
actions/selectors from the Redux `card` slice.
- **Redux migration 123**: Purges the removed fields from persisted
state for existing users.
- **All tests updated**: Rewrote/adapted tests across 17 test files
(512+ tests) to work with React Query mocks instead of Redux cache
mocks.
## **Changelog**
CHANGELOG entry: null
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: Card data fetching via React Query
Scenario: Card Home loads data on mount
Given the user is authenticated with a valid session
When the user navigates to the Card Home screen
Then card details, delegation settings, wallet details, and KYC status load without errors
And no data is persisted in the Redux card slice cache
Scenario: Pull-to-refresh re-fetches mutable data
Given the user is on the Card Home screen with loaded data
When the user pulls to refresh
Then wallet details, card details, and KYC status are refetched
And delegation settings are NOT refetched (static config with long stale time)
Scenario: Cache invalidation after spending limit change
Given the user has updated their spending limit
When the mutation completes successfully
Then the external wallet details query is invalidated
And fresh data is fetched on next access
Scenario: Auth error clears React Query cache
Given the user encounters an authentication error
When the error handler runs
Then all Card React Query caches are removed
And the user is redirected to the authentication screen
Scenario: Existing users migrate cleanly
Given the user has an existing persisted Redux state with card cache data
When the app launches and migration 123 runs
Then the cache, priorityTokensByAddress, lastFetchedByAddress, authenticatedPriorityToken, and authenticatedPriorityTokenLastFetched fields are removed from the card slice
```
## **Screenshots/Recordings**
<!-- No UI changes — this is a data-fetching layer refactor -->
### **Before**
<!-- N/A — internal refactor, no visual changes -->
### **After**
<!-- N/A — internal refactor, no visual changes -->
## **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 refactors core Card data-fetching, cache
invalidation, and auth-error cleanup paths; regressions could cause
stale/blank Card dashboard data or missed refreshes.
>
> **Overview**
> **Card dashboard data fetching is migrated to React Query.** Hooks
like `useCardDetails`, `useGetDelegationSettings`,
`useGetCardExternalWalletDetails`, and
`useGetLatestAllowanceForPriorityToken` switch from
`useWrapWithCache`/Redux-driven caching to `useQuery` with centralized
`cardQueries` keys, manual `refetch`-based fetch functions, and updated
stale-time/enablement behavior.
>
> **Cache invalidation/cleanup behavior changes.** `CardHome` now clears
Card caches via `queryClient.removeQueries` on auth errors and triggers
`fetchAllData` on focus when Card queries are invalidated;
`DaimoPayModal` invalidates `cardDetails` via
`queryClient.invalidateQueries` instead of dispatching Redux
cache-clearing actions.
>
> **Tests and minor UI behavior are updated for the new model.** Card
tests mock/query React Query clients instead of Redux cache actions, and
the onboarding `SignUp` country selector shows a loading state while
registration settings are fetching and disables selection during that
time.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c8c7a98. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.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** Refactors Rewards headers to use the shared header components (`HeaderRoot`, `HeaderCompactStandard`) and SafeAreaView for consistency with the rest of the app (e.g. Settings). **Changes:** - **Rewards Dashboard**: Replaced stack header with inline `HeaderRoot` and `SafeAreaView`; removed `setOptions` / `getNavigationOptionsTitle`. Header includes settings and conditional referral icon buttons. - **Rewards Settings**: Replaced stack header with inline `HeaderCompactStandard` and `SafeAreaView`; navigator uses `headerShown: false` for the settings screen. Back button uses `onBack`. - **Account group modal** (`RewardOptInAccountGroupModal`): Replaced `BottomSheetHeader` with `HeaderCompactStandard` and added a close (X) button to dismiss the sheet. - **Environment toggle sheet** (`RewardsEnvironmentToggle`): Replaced the title `View`/`Text` with `HeaderCompactStandard` and added a close (X) button to dismiss the bottom sheet. Tests updated for SafeAreaView/insets mocks and header behavior; removed tests that asserted `setOptions` usage. ## **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/DSYS-504 ## **Manual testing steps** ```gherkin Feature: Rewards headers Scenario: user sees Rewards dashboard with inline header Given the user is on the Rewards tab Then the dashboard shows the inline HeaderRoot with title and right-side icon(s) And safe area insets are respected Scenario: user opens Rewards settings and uses back Given the user is on the Rewards tab When the user taps the settings icon in the header Then Rewards settings opens with inline HeaderCompactStandard and back button When the user taps the back button Then the user returns to the Rewards dashboard Scenario: user opens account group modal and closes via X Given the user is on Rewards settings with at least one account group When the user taps an account group (e.g. "Account 1") Then the account group bottom sheet opens with HeaderCompactStandard and close (X) button When the user taps the close button Then the bottom sheet dismisses Scenario: user opens environment selector and closes via X Given the user is on Rewards settings and env switching is allowed When the user taps the environment selector button Then the environment bottom sheet opens with HeaderCompactStandard and close (X) button When the user taps the close button Then the bottom sheet dismisses ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/f54afe11-dcf4-4e37-babd-24906789c32b <!-- [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] > **Low Risk** > Low risk UI refactor that changes header rendering and stack `headerShown` behavior for Rewards screens; main risk is regressions in navigation/back behavior or safe-area/layout on different devices. > > **Overview** > **Rewards screens now render consistent in-screen headers** by moving away from stack `setOptions`/`getNavigationOptionsTitle` and using shared header components. > > The Rewards dashboard wraps content in `SafeAreaView` and uses `HeaderRoot` with settings + conditional referral icon actions; Rewards settings uses `SafeAreaView` + `HeaderCompactStandard` with an explicit back handler, and the navigator hides the stack header for settings (`headerShown: false`). > > Bottom sheets used in Rewards settings are updated to use `HeaderCompactStandard` (including close buttons) for the account-group opt-in modal and the environment selector, and tests are adjusted accordingly (safe-area mocks, header assertions, and more robust disabled-state checks). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d3cc3e2. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary
- Migrate `useUnrealizedPnL` from manual `useState`/`useEffect` to React
Query's `useQuery`, completing React Query adoption across the Predict
module
- Extract query key factory and `queryOptions()` into
`queries/unrealizedPnL.ts` following the established pattern
(`balance.ts`, `positions.ts`)
- Register the new query in `predictQueries` index
- Update consumer (`PredictPositionsHeader`) to use the simplified
`loadUnrealizedPnL()` signature (no more `{ isRefresh }` arg — React
Query handles this)
## Test plan
- [ ] `npx jest useUnrealizedPnL` — all tests pass
- [ ] `npx jest PredictPositionsHeader` — consumer tests still pass
- [ ] Verify unrealized P&L displays correctly on the Positions screen
- [ ] Verify pull-to-refresh updates P&L data
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Moderate risk because it changes how unrealized P&L is fetched/cached
(React Query) and when the UI shows/refreshes it (focus +
position-gated), which could affect staleness and error/display states.
>
> **Overview**
> Migrates `useUnrealizedPnL` from manual `useState`/`useEffect`
fetching to a React Query `useQuery` implementation backed by a new
`predictQueries.unrealizedPnL` entry and `queries/unrealizedPnL.ts`
(keys + `queryOptions`).
>
> Updates `PredictPositionsHeader` to consume the new hook shape
(`data`/`error` as `Error`), **only render P&L when there are active
(non-claimable) positions**, and refresh by invalidating the unrealized
P&L query on screen focus and via the imperative `refresh()` handler.
>
> Ensures unrealized P&L cache is refreshed after relevant actions by
invalidating `predictQueries.unrealizedPnL` on order placement and on
confirmed deposit/claim/withdraw toast events; tests are adjusted
accordingly.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3100866. 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** Introduces the `CardController` shell into the Engine as the first step toward the multi-provider Card architecture. **Why**: The Card feature is tightly coupled to Baanx with business logic scattered across hooks, views, and a 2,600-line SDK monolith. To support multiple card providers and clean up the architecture, we need a proper Engine controller that owns the Card feature's persistent state. This PR lays the foundation — an inert controller that holds state and syncs it to Redux via the standard Engine batcher. No user-facing changes. **What changed**: New files: - **`app/core/Engine/controllers/card-controller/types.ts`**: Defines `CardControllerState` (5 fields: `selectedCountry`, `activeProviderId`, `isAuthenticated`, `cardholderAccounts`, `providerData`), default state factory, and action/event/messenger types. - **`app/core/Engine/controllers/card-controller/CardController.ts`**: Bare-bones controller extending `BaseController`. All state is persisted and marked `usedInUi: true`. No business logic yet — provider delegation comes in subsequent PRs. - **`app/core/Engine/controllers/card-controller/index.ts`**: Init function following the standard Engine pattern — reads persisted state, creates controller instance. - **`app/core/Engine/messengers/card-controller-messenger/index.ts`**: Simple messenger with no delegated actions/events (those will be added when the controller needs to talk to `KeyringController`, `TransactionController`, etc.). - **`app/selectors/cardController.ts`**: Four selectors: `selectCardSelectedCountry`, `selectCardActiveProviderId`, `selectIsCardAuthenticated`, `selectCardholderAccounts`. Modified files: - **`app/core/Engine/types.ts`**: Added `CardController` to `Controllers`, `EngineState`, `GlobalActions`, `GlobalEvents`, and `ControllersToInitialize`. - **`app/core/Engine/Engine.ts`**: Registered init function, destructured from `controllersByName`, added to `this.context` and `state` getter. - **`app/core/Engine/messengers/index.ts`**: Added `CardController` entry to `CONTROLLER_MESSENGERS`. - **`app/core/Engine/constants.ts`**: Added `'CardController:stateChange'` to `BACKGROUND_STATE_CHANGE_EVENT_NAMES`. - **`app/util/test/initial-background-state.json`**: Added `CardController` default state to the test fixture. - **`.github/CODEOWNERS`**: Assigned `app/core/Engine/controllers/card-controller`, `app/core/Engine/messengers/card-controller-messenger`, and `app/selectors/cardController.ts` to `@MetaMask/card`. Test files: - **`CardController.test.ts`**: Tests controller construction with default state, partial state, and full persisted state. - **`index.test.ts`**: Tests the init function returns a controller instance, uses default state when none is persisted, and uses persisted state when provided. - **`cardController.test.ts`**: Tests all four selectors with populated state, empty state, and undefined `CardController` state (fallback values). ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: CardController initialization Scenario: Controller initializes with default state on fresh install Given the app is freshly installed When the Engine initializes Then state.engine.backgroundState.CardController exists And selectedCountry is null And activeProviderId is null And isAuthenticated is false And cardholderAccounts is an empty array And providerData is an empty object Scenario: Controller state persists across app restarts Given the app has been launched before And CardController state was persisted When the Engine initializes again Then the persisted CardController state is restored Scenario: No user-facing changes Given the user is on any screen in the app When the app loads Then nothing visually changes And the Card feature continues to work as before ``` ## **Screenshots/Recordings** No UI changes — this is an infrastructure-only PR. ### **Before** No `CardController` in Engine. Card state managed entirely by Redux slice. ### **After** `CardController` exists in Engine with default state. Syncs to `state.engine.backgroundState.CardController`. Existing Card feature behavior is unchanged. ## **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 updates core Engine initialization/types and introduces new persisted background state, which could affect startup or state serialization despite having no business logic yet. > > **Overview** > Introduces a new `CardController` (BaseController) that persists Card feature state (`selectedCountry`, `activeProviderId`, `isAuthenticated`, `cardholderAccounts`, `providerData`) and exposes it via the standard Engine background state batching. > > Wires the controller into Engine initialization and messaging (`Engine.ts`, `messengers/index.ts`, `types.ts`, `constants.ts`) so `CardController` state is included in `Engine.state`, state-change subscriptions, and debug/state log fixtures. > > Adds initial selectors in `selectors/cardController.ts`, plus unit tests for controller construction/init and selector fallbacks, and updates `CODEOWNERS` for new Card Engine paths. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8363bb1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )