Skip to content

[pull] main from MetaMask:main#335

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

[pull] main from MetaMask:main#335
pull[bot] merged 14 commits into
Reality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Nov 18, 2025

See Commits and Changes for more details.


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

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

kevinbluer and others added 14 commits November 18, 2025 17:44
…22832)

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

- Improving copy on positions that have resolved early (so it's no long
incorrectly showing things like "Ended in 14 days" and now says
"Resolved early"

<!--
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: null

## **Related issues**

Fixes:
[PRED-284](https://consensyssoftware.atlassian.net/browse/PRED-284)

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

<img width="420" alt="Simulator Screenshot - iPhone 16 Pro Max -
2025-11-17 at 11 01 16"
src="https://github.com/user-attachments/assets/e309867e-0fa7-46f0-a41a-a5cb14e28acc"
/>

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


[PRED-284]:
https://consensyssoftware.atlassian.net/browse/PRED-284?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Updates `PredictPositionResolved` to use i18n and display "Resolved
early" when `endDate` is in the future; adjusts tests and adds locale
strings.
> 
> - **Predict UI**:
> - Replace hardcoded copy with i18n in `PredictPositionResolved`
(amount/outcome, won/lost, ended/resolved-early).
> - Add `formatMarketEndDate` using `dayjs.isAfter` to show `"Resolved
early"` for future `endDate`; otherwise `"Ended" <relative time>`.
> - **Tests**:
> - Update `PredictPositionResolved.test.tsx` to mock i18n and dayjs,
and assert via regex against localized strings.
> - **Localization**:
> - Add `predict.market_details.ended` and
`predict.market_details.resolved_early` keys to `en.json`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c2f46af. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Version Bump After Release

This PR bumps the main branch version from 7.60.0 to 7.61.0 after
cutting the release branch.

### Why this is needed:
- **Nightly builds**: Each nightly build needs to be one minor version
ahead of the current release candidate
- **Version conflicts**: Prevents conflicts between nightlies and
release candidates
- **Platform alignment**: Maintains version alignment between MetaMask
mobile and extension
- **Update systems**: Ensures nightlies are accepted by app stores and
browser update systems

### What changed:
- Version bumped from `7.60.0` to `7.61.0`
- Platform: `mobile`
- Files updated by `set-semvar-version.sh` script

### Next steps:
This PR should be **manually reviewed and merged by the release
manager** to maintain proper version flow.

### Related:
- Release version: 7.60.0
- Release branch: release/7.60.0
- Platform: mobile
- Test mode: false

---
*This PR was automatically created by the
`create-platform-release-pr.sh` script.*

Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com>
…r system (#22566)

Fixes:
https://consensyssoftware.atlassian.net/jira/polaris/projects/MWMR/ideas/view/8845676?selectedIssue=MWMR-10

## **Description**

This PR implements the handler layer and integration for the Universal
Router system, building on the core infrastructure from Phase 2 PR 1. It
introduces a declarative, handler-based approach for processing deep
links, replacing the need to modify core routing logic when adding new
deep link types.

### What Changed?

Added handler implementation and integration layer to
`app/core/DeeplinkManager/router/`:

1. **BaseHandler** (`handlers/BaseHandler.ts` - 103 lines)
- Common utilities for all handlers (navigation, auth, error handling,
analytics)
   - Parameter validation helpers
   - Success/error result factory methods

2. **NavigationHandler** (`handlers/NavigationHandler.ts` - 75 lines)
- Handles simple navigation deep links (HOME, CREATE_ACCOUNT, REWARDS,
PREDICT, PERPS)
   - Priority: 10 (standard)

3. **SwapHandler** (`handlers/SwapHandler.ts` - 67 lines)
   - Handles token swap deep links
   - Priority: 50 (high priority for core functionality)
   - Authentication-aware with default slippage settings

4. **SendHandler** (`handlers/SendHandler.ts` - 78 lines)
   - Handles transaction/send deep links
- Delegates APPROVE actions to legacy system (requires complex
transaction creation)
   - Parameter validation for required fields

5. **UniversalRouterIntegration**
(`integration/UniversalRouterIntegration.ts` - 80 lines)
   - Integration wrapper for easy adoption into existing deeplink system
   - Feature flag checking (`MM_UNIVERSAL_ROUTER`)
   - Context creation from existing DeeplinkManager

6. **Updated UniversalRouter** (~20 lines added)
   - Registers all handlers in `initialize()` method
   - Imports and instantiates concrete handler classes

### Why?

Completes the Universal Router architecture started in Phase 2 PR 1 by:
- ✅ **Enabling handler-based routing**: New deep link types can be added
without modifying core logic
- ✅ **Isolated testing**: Each handler can be tested independently
- ✅ **Clear separation**: Routing logic separated from handler
implementation
- ✅ **Feature-flagged**: Safe gradual rollout with automatic fallback to
legacy

### Trade-offs

- **No BrowserHandler yet**: Kept scope minimal. Browser/dApp links
continue using legacy system for now.
- **APPROVE delegates to legacy**: Transaction creation is complex and
tightly coupled to legacy system. Keeping it delegated reduces risk.
- **String route names**: Used string literals instead of Routes
constants in some places to avoid circular dependencies.

## **Changelog**

CHANGELOG entry: null

_(Internal infrastructure - no end-user-facing behavior changes. Feature
flag is OFF by default.)_

## **Related issues**

Refs: Deep Link Consolidation Epic (Phase 2 PR 2)

## **Manual testing steps**

Feature: Universal Router Handler System

  Scenario: Router remains inactive when feature flag is OFF
    Given the app is running with MM_UNIVERSAL_ROUTER flag disabled
    And handlers are registered in the UniversalRouter

    When a deep link is opened (e.g., metamask://home)
    Then the deep link should be handled by the legacy system
    And no errors should occur

  Scenario: Router processes links when feature flag is ON
    Given the app is running with MM_UNIVERSAL_ROUTER flag enabled
    And NavigationHandler is registered for HOME action

    When a deep link is opened (metamask://home)
    Then the UniversalRouter should route to NavigationHandler
    And the handler should navigate to home screen
    And analytics should track the handler success

  Scenario: Router falls back to legacy for unhandled actions
    Given the MM_UNIVERSAL_ROUTER flag is enabled
    And no handler is registered for a specific action

    When a deep link with unhandled action is opened
    Then the router should delegate to legacy system
    And no errors should occur

  Scenario: Authentication check blocks unauthenticated users
    Given the MM_UNIVERSAL_ROUTER flag is enabled
    And the wallet is locked

When a deep link requiring authentication is opened (e.g.,
metamask://swap)
    Then the handler should detect authentication is required
    And should delegate to legacy system### Testing Commands

# Run all router tests
yarn jest app/core/DeeplinkManager/router/

# Run ESLint
yarn eslint app/core/DeeplinkManager/router/

# Run TypeScript check
yarn lint:tsc | grep "app/core/DeeplinkManager/router"##
**Screenshots/Recordings**

N/A - Infrastructure change with no UI impact

## **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 (37 passing unit tests)
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---


<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds handler-based routing (navigation, swap, send), registers them in
`UniversalRouter`, and integrates behind a feature flag with
comprehensive tests.
> 
> - **Router**
> - `UniversalRouter.initialize()` now clears registry and registers
`SwapHandler`, `SendHandler` (priority 50) and `NavigationHandler`
(priority 10); throws on registration failure.
> - Routing delegates to legacy when no handler matches or when a
handler requests `fallbackToLegacy`.
> - **Handlers**
> - Add `handlers/BaseHandler` with navigation/auth/analytics helpers
and result/param utilities.
> - Add `handlers/NavigationHandler` for actions like `HOME`,
`CREATE_ACCOUNT`, `REWARDS`, `PREDICT`, `PERPS*`.
> - Add `handlers/SwapHandler` and `handlers/SendHandler` with auth
checks; `APPROVE` delegates to legacy.
>   - Export all via `handlers/index`.
> - **Integration**
> - Add `integration/UniversalRouterIntegration` to gate usage via
`MM_UNIVERSAL_ROUTER` and construct handler context; exposes
`processWithNewRouter` and `getRouter`.
> - **Interfaces/Utils**
> - Update `HandlerContext.instance` typing and docs; add optional
lifecycle hooks; add `testUtils`.
> - **Tests**
> - New unit tests for handlers and integration; update
`UniversalRouter.test` (init failure case, priority order, legacy
delegation).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7d8fe2d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…22792)

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

The scrollable section in the select assets list stops scrolling
prematurely, preventing some assets at the bottom from being displayed.
Users are unable to select these assets as the list snaps back to a
previous position when trying to scroll to the end. This issue needs to
be resolved before the new asset picker goes live.

<!--
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: Fixes scrolling issue in asset list that prevents
rendering bottom assets

## **Related issues**

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

## **Manual testing steps**

```gherkin
Ensure that assets that appear at the bottom of the asset list in both src and dest asset selector are visible in both android and ios.
```

## **Screenshots/Recordings**

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

### **Before**

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



https://github.com/user-attachments/assets/31ccef83-f961-4785-b763-4151c9c8f229

### **After**

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


https://github.com/user-attachments/assets/8d1bbe3c-ef73-41c0-9732-2f7f7e687530


## **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]
> Make bridge token lists occupy full height and remove extra bottom
padding to prevent premature scroll stops.
> 
> - **Bridge token selectors (`BridgeTokenSelectorBase`)**:
> - Add `tokensListContainer` with `flex: 1` and set `tokensList` `flex:
1` to ensure the list fills available space.
> - Add `tokensListContent` with `paddingBottom: 0` and pass via
`contentContainerStyle` to avoid extra bottom space.
> - **Tests**:
> - Update snapshots for `BridgeSourceTokenSelector` and
`BridgeDestTokenSelector` to reflect new layout and list content
container styles.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7e45658. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…rns (#22874)

## **Description**

This PR refactors the `TokenSelection` screen to align with standard
Ramp patterns and prepares it for implementing dynamic token fetching
(TRAM-2820).

### Key Changes:

1. **Standardized Navigation Pattern**
- Replaced custom navigation setup with `getDepositNavbarOptions()`
(consistent with all other Ramp screens)
- Added `createTokenSelectionNavDetails()` export for type-safe
navigation
- Removed `rampType` from params, now derives routing decision from
Redux state via `getRampRoutingDecision`

2. **Updated Parameter Structure**
   - Changed from `{ rampType, selectedCryptoAssetId }` to `{ intent }`
- Now uses `RampIntent` type which includes `assetId` for pre-selection
   - Aligns with Aggregator flow's intent-based navigation pattern

3. **Layout Component Migration**
   - Replaced `SafeAreaView` with `ScreenLayout` component
   - Uses the same layout pattern as all Aggregator and Deposit screens
   - Provides consistent safe area handling and background styling

4. **Style Migration to Tailwind CSS**
   - Replaced `StyleSheet.create()` with Tailwind `twClassName` props
   - Migrated `View` components to `Box` from design system
   - Removed `TokenSelection.styles.ts` file entirely
   - Simplified styling: `py-2`, `px-4 py-3`, `flex-1`

5. **Test Updates**
   - Added mock for `getRampRoutingDecision` selector
   - Updated `useParams` mock to use `intent` structure
   - Updated test case for token selection to use `intent.assetId`
   - Added `rampRoutingDecision` to test state

### Why These Changes?

- **Consistency**: Now follows the exact same patterns as other Ramp
screens
- **Maintainability**: Using shared components means updates apply
automatically
- **Type Safety**: Intent-based parameters provide better TypeScript
support
- **Preparation**: Sets foundation for implementing dynamic token
fetching from API (TRAM-2816/2820)

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: TRAM-2820

## **Manual testing steps**

```gherkin
Feature: Token Selection Screen

  Scenario: user views token selection screen
    Given user is on the token selection screen
    
    When screen loads
    Then user sees standard Ramp navigation header
    And user sees close button in top right
    And user sees network filter bar
    And user sees search input
    And user sees list of tokens

  Scenario: user searches for tokens
    Given user is on the token selection screen
    
    When user types "USDC" in search input
    Then user sees only tokens matching "USDC"
    
  Scenario: user filters by network
    Given user is on the token selection screen
    
    When user selects a specific network filter
    Then user sees only tokens on that network

  Scenario: user views unsupported token info
    Given user sees a greyed-out unsupported token
    
    When user taps the info icon
    Then user sees unsupported token modal
```

## **Screenshots/Recordings**

### **Before**

- Used custom navigation with `ButtonIcon` components
- Used `SafeAreaView` with custom styles
- Used StyleSheet for all styling
- Parameter: `selectedCryptoAssetId`

### **After**

- Uses `getDepositNavbarOptions()` (standard pattern)
- Uses `ScreenLayout` component (standard pattern)  
- Uses Tailwind CSS with `twClassName` props
- Parameter: `intent.assetId`

_(Note: Visual appearance remains the same - this is a
refactoring/preparation PR)_

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Refactors TokenSelection to use shared ScreenLayout and intent-based
params/navigation, updates tests and snapshots, and switches Solana USDC
to CAIP/slip44 identifiers and icons.
> 
> - **TokenSelection screen**:
> - Migrate to `ScreenLayout` and design-system `Box`; remove
`TokenSelection.styles.ts`.
> - Standardize navbar via `getDepositNavbarOptions` and add
`createTokenSelectionNavDetails`.
> - Change route params to `intent` (`RampIntent`) and read routing via
Redux `getRampRoutingDecision`.
> - Hook integrations: keep `useSearchTokenResults`; add list scrolling
on search.
> - **Token item rendering**:
>   - Default `TokenListItem` `textColor` to `TextColor.Alternative`.
> - **Data/constants**:
> - Update Solana USDC to `solana:…/slip44:501` with matching icon URL.
> - **Tests & snapshots**:
> - Update to intent-based params, add `rampRoutingDecision` in state,
add empty-state and unsupported-info interactions; refresh snapshots
(including updated Solana USDC icon/asset).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e9c18ad. 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**

We only want mainnet ETH to be stakable, not ETH in other chains.

<img width="398" height="520" alt="Screenshot 2025-11-17 at 10 47 47"
src="https://github.com/user-attachments/assets/dc205302-cbb6-4d43-8b42-0661da19061d"
/>

## **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: Only mainnet ETH should be stakable

## **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]
> Restricts staking eligibility to ETH on Ethereum mainnet or Hoodi
testnet and updates related tests/mocks accordingly.
> 
> - **Stake Logic**:
> - Update `selectIsStakeableToken` to allow staking for `ETH` only on
mainnet (`isMainnetByChainId`) or Hoodi testnet (new `isHoodiChainId`
supporting hex/decimal/CAIP forms).
> - Keep TRX staking gated by `selectTrxStakingEnabled` and TRON chain
check.
> - **Tests & Mocks**:
> - Add/adjust tests in `stakeableTokens.test.ts` for mainnet,
non-mainnet, and Hoodi scenarios.
> - Ensure ETH test assets include `chainId` (e.g., in
`EarnBalance.test.tsx`).
> - Standardize `isNonEvmChainId` mocking and preserve actual exports
when mocking `util/networks` across multiple tests
(`SearchTokenAutocomplete`, `Tokens`, `AddAsset`, `TokenListItem*`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4405081. 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**

> > Add MetaMask relayer order submission mocks (with CLOB fallback) for
cash-out and unskip the Spurs vs. Pelicans cash-out e2e test.
> 
> - **E2E Mocks (Polymarket)**
> - Add relayer order submission mock for
`predict.dev-api.cx.metamask.io/order` via `/proxy` with cash-out `SELL`
order validation and success response.
> - Adjust CLOB fallback mock for `clob.polymarket.com` to return
`success`, `orderID`, `transactionsHashes`, `takingAmount`, and
`makingAmount` (decimal strings).
> - Integrate balance update after cash-out via
`POLYMARKET_UPDATE_USDC_BALANCE_MOCKS`.
> - **E2E Test**
> - Unskip and run `should cash out on open position: Spurs vs.
Pelicans` in `e2e/specs/predict/predict-cash-out.spec.ts`, using new
mocks and asserting updated activity and balances.

## **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]
> Adds MetaMask relayer and CLOB fallback mocks for cash-out (with
balance update), introduces prices API mock, switches Polygon RPC to
Infura proxy, and enables the Spurs vs. Pelicans cash-out e2e test.
> 
> - **E2E Mocks (Polymarket)**:
> - **Relayer + CLOB (cash-out)**: Add
`/proxy?url=predict.*.metamask.io/order` mock validating `SELL` orders
and success payload; update `clob.polymarket.com` fallback to return
`success`, `orderID`, `transactionsHashes`, `takingAmount`,
`makingAmount`; trigger
`POLYMARKET_UPDATE_USDC_BALANCE_MOCKS('cash-out')`.
> - **Prices API**: New `POLYMARKET_PRICES_MOCKS` for
`clob.polymarket.com/prices` and register in
`POLYMARKET_COMPLETE_MOCKS`.
> - **Positions filtering**: Add `eventId` filtering and ensure
`proxyWallet` mapping for resolved/current positions in
`POLYMARKET_POSITIONS_WITH_WINNINGS_MOCKS` and
`POLYMARKET_RESOLVED_MARKETS_POSITIONS_MOCKS`.
> - **Fixture**:
> - `withPolygon`: Route via Infura URL through mock `/proxy` (uses
`MM_INFURA_PROJECT_ID`).
> - **E2E Test**:
> - Enable and refactor `predict-cash-out.spec.ts`: unskip test, set
mocks (`POLYMARKET_REMOVE_CASHED_OUT_POSITION_MOCKS` then
`POLYMARKET_POST_CASH_OUT_MOCKS`), assert updated balance `$58.66`,
position removal, and activity details (`Cashed out`, `$30.75`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e126795. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…trics (#22822)

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

## **Description**

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

This PR improves metrics tracking for the card delegation flow by
distinguishing between actual failures and user cancellations.

Previously, when users cancelled the transaction confirmation modal
during the card delegation process, it was incorrectly tracked as a
failed delegation (`CARD_DELEGATION_PROCESS_FAILED`). This skewed
failure metrics and made it difficult to identify genuine system errors.

## **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 card delegation metrics to correctly distinguish
between user cancellations and actual failures

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

- [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]
> Track `CARD_DELEGATION_PROCESS_USER_CANCELED` for user cancellations
and only log/track failures for real errors in the card delegation flow.
> 
> - **Card Delegation Flow**:
> - Update `useCardDelegation` to emit
`CARD_DELEGATION_PROCESS_USER_CANCELED` on `UserCancelledError`; only
emit `CARD_DELEGATION_PROCESS_FAILED` and log errors for
non-cancellation failures.
> - Add `UserCancelledError` handling in approval transaction path;
preserve state updates and error propagation.
> - **Analytics**:
> - Add `EVENT_NAME.CARD_DELEGATION_PROCESS_USER_CANCELED` and expose
via `MetaMetricsEvents`.
> - **Tests**:
> - Extend `useCardDelegation.test.ts` to assert canceled vs failed
event tracking, absence of error logging on cancel, and existing metrics
properties remain unchanged.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9c66894. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

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

This PR fixes a race condition bug in the onboarding flow where users
typing quickly and immediately pressing submit buttons would have their
input data lost due to debounce delays.

## **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 a bug where rapidly typing in Card onboarding
forms and immediately submitting could send incomplete data to the
server

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: Onboarding form submission with rapid typing

  Scenario: user types email rapidly and submits immediately in Sign Up screen
    Given user is on the Sign Up screen
    And all form fields are empty

    When user rapidly types "test@example.com" in email field
    And user rapidly types "Password123!" in password field
    And user rapidly types "Password123!" in confirm password field
    And user selects a country
    And user immediately presses Continue button (within 1 second)
    Then the API should receive "test@example.com" (not partial/stale email)
    And user should navigate to Confirm Email screen

  Scenario: user types phone number rapidly and submits immediately in Set Phone Number screen
    Given user is on the Set Phone Number screen
    And country code is selected

    When user rapidly types "1234567890" in phone number field
    And user immediately presses Continue button (within 1 second)
    Then the API should receive "1234567890" (not partial phone number)
    And user should navigate to Confirm Phone Number screen

  Scenario: US user types SSN rapidly and submits immediately in Personal Details screen
    Given user is on the Personal Details screen
    And user has selected US as country
    And first name, last name, date of birth, and nationality are filled

    When user rapidly types "123456789" (9 digits) in SSN field
    And user immediately presses Continue button (within 1 second)
    Then the API should receive "123456789" (complete SSN, not partial)
    And user should navigate to Physical Address screen

  Scenario: user submits invalid data with rapid typing
    Given user is on the Sign Up screen

    When user rapidly types "invalid-email" in email field
    And user rapidly types "weak" in password field
    And user rapidly types "weak" in confirm password field
    And user selects a country
    And user immediately presses Continue button
    Then validation errors should be shown
    And API should not be called
    And user should remain on Sign Up screen
```

## **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]
> Use non-debounced field values for validation/submission in onboarding
forms and switch consent `policyType` from `US` to `us`.
> 
> - **Onboarding UI (validation/submission)**:
> - Sign Up (`SignUp.tsx`): validate/submit using live `email`,
`password`, `confirmPassword`; update `isDisabled`; send current values
to API; tweak handlers.
> - Set Phone Number (`SetPhoneNumber.tsx`): validate `phoneNumber`
(4–15 digits) before submit; compute `isDisabled` from current value;
pass live number to API/navigation; adjust error handling.
> - Personal Details (`PersonalDetails.tsx`): require and validate `SSN`
(9 digits) for US before submit; compute `isDisabled` using live `SSN`;
send current `ssn` to API.
> - **Consent policy casing**:
> - Change consent `policyType` from `US` to `us` across code, tests,
and types (`useRegisterUserConsent.ts`,
`useRegisterUserConsent.test.ts`, `CardSDK.test.ts`, `types.ts`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b039631. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**

Adds a new RampUnsupportedModal to inform users when buying crypto is
unavailable in their region. This modal provides clear feedback when
users attempt to access ramp functionality from an unsupported
geographic location.

**Changes:**
- Created new `RampUnsupportedModal` component using design system
patterns (Box with Tailwind, BottomSheet)
- Registered modal route (`UNSUPPORTED_REGION_MODAL`) in navigation
stack
- Added localization strings for modal title, description, and CTA
- Included comprehensive unit tests with snapshot coverage

**Motivation:**
When users from restricted regions attempt to buy crypto, they need
clear communication about why the feature is unavailable. This modal
provides user-friendly messaging explaining regional limitations and
regulatory restrictions.

## **Changelog**

CHANGELOG entry: Added unsupported region modal to inform users when
buying crypto is unavailable in their location

## **Related issues**

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

## **Manual testing steps**

Feature: Unsupported Region Modal

  Scenario: user views unsupported region modal
    Given user is in a region where ramp services are unavailable
    
    When user navigates to the unsupported region modal
Then user should see the modal with title "Unavailable in your region"
    And user should see descriptive text about regional limitations
    And user should see a "Got It" button

  Scenario: user dismisses modal via close button
    Given user has the unsupported region modal open
    
    When user taps the close button in the header
    Then the modal should close
    And user should return to the previous screen

  Scenario: user dismisses modal via Got It button
    Given user has the unsupported region modal open
    
    When user taps the "Got It" button
    Then the modal should close
    And user should return to the previous screen


## **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/94b2a9f2-c976-4b09-8dd3-f08611b3621f


<!-- [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]
> Introduces `RampUnsupportedModal` with navigation support, improves
eligibility modal error handling, updates tests/snapshots, and adds
localization strings.
> 
> - **UI — Ramps**:
> - Adds `UI/Ramp/components/RampUnsupportedModal` (component, index,
nav details) with close actions.
> - Updates `EligibilityFailedModal` to `catch` `Linking.openURL`
errors.
> - **Navigation**:
> - Adds `Routes.SHEET.UNSUPPORTED_REGION_MODAL` and registers it in
`Nav/App/App.tsx` (`RootModalFlow`).
> - **Tests**:
> - Adds `RampUnsupportedModal.test.tsx` with snapshot and interaction
tests; route registration test in `Nav/App/App.test.tsx`.
> - Updates eligibility modal tests (mock `Linking.openURL`, assertion
to `toBeOnTheScreen`).
> - **Localization**:
> - Adds `fiat_on_ramp_aggregator.unsupported_region_modal` strings in
`locales/languages/en.json`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e23d610. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

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

Due to the previous implementation of detecting migration failures to
prompt the Vault Recovery screen causing the Vault Recovery screen to
show up on a fresh install to users who had previously had the MM app
installed.

This is due to on iOS due to keychain not being reset on app uninstall. 

This method uses a flag stored in FileSystem Storage to check instead of
the presence of a backed up vault.

Bug: #22567

Bitrise Build:
https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/3efcb785-454e-4fa6-8cd3-8b8b8b224999

## **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]
> Use a FilesystemStorage-backed migration error flag to trigger vault
recovery from onboarding and clear it after successful restore; set the
flag on migration failures and add corresponding tests.
> 
> - **Onboarding**:
> - Read `MIGRATION_ERROR_HAPPENED` from `FilesystemStorage`; if
`'true'` and `getVaultFromBackup()` returns a vault, reset navigation to
`Routes.VAULT_RECOVERY.RESTORE_WALLET`.
> - Skip check in E2E or when `route.params.delete` is set; add error
logging.
> - **Vault Recovery (WalletRestored)**:
> - On continue, remove `MIGRATION_ERROR_HAPPENED` via
`FilesystemStorage.removeItem`; log failures but still navigate to
`Routes.ONBOARDING.LOGIN` with `{ isVaultRecovery: true }`.
> - **Migrations**:
> - On any migration error, set `MIGRATION_ERROR_HAPPENED` in
`FilesystemStorage` (uses `Device.isIos()` to avoid iCloud backup)
before rethrowing.
> - **Constants**:
>   - Add `MIGRATION_ERROR_HAPPENED` to `app/constants/storage.ts`.
> - **Tests**:
> - Update/add tests for onboarding migration check, recovery redirect
conditions, error handling, and clearing the flag in `WalletRestored`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e1156ba. 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>
…en (#22892)

## **Description**

This PR implements **TRAM-2820**: Dynamic token fetching for the Token
Selection screen in the Ramp flow, replacing hardcoded mock data with
real-time API data from the on-ramp-cache endpoint.

### **What changed and why:**

**Problem:** The Token Selection screen was using hardcoded
`MOCK_CRYPTOCURRENCIES` data, preventing users from seeing the actual
available tokens for their region and selected flow (Buy vs Deposit).

**Solution:** 
1. **Created `useRampTokens` hook** to fetch tokens from the
on-ramp-cache API endpoint
2. **Integrated hook into TokenSelection component** with loading and
error states
3. **Added network filtering** to only show tokens for networks the user
has added to their wallet
4. **Implemented smart list switching** - displays curated `topTokens`
by default, switches to complete `allTokens` when searching

### **Key Features:**

- ✅ **Environment-aware API calls**: Uses staging or production URLs
based on `METAMASK_ENVIRONMENT`
- ✅ **Region-based tokens**: Fetches tokens based on user's detected
geolocation
- ✅ **Flow-aware**: Different token lists for Aggregator (buy) vs
Deposit flows
- ✅ **Network filtering**: Only shows tokens for networks in user's
wallet (e.g., excludes Tron if user doesn't have Tron network)
- ✅ **Smart UX**: Shows curated top tokens initially, searches across
all tokens
- ✅ **Loading/Error states**: Proper UI feedback during fetch and on
errors
- ✅ **Type-safe**: Full TypeScript support with `RampsToken` interface

### **Technical Details:**

**API Endpoint:**
```
{baseUrl}/regions/{region}/tokens?action={action}&sdk=2.1.5
```

**Mapping:**
- `UnifiedRampRoutingType.AGGREGATOR` → `action=buy`
- `UnifiedRampRoutingType.DEPOSIT` → `action=deposit`

**Response Structure:**
```json
{
  "topTokens": [...],  // Curated list
  "allTokens": [...]   // Complete list
}
```

**Previous work included in this PR (refactoring):**
- Standardized navigation using `getDepositNavbarOptions()`
- Migrated to `ScreenLayout` component
- Converted styles to Tailwind CSS with `twClassName`
- Updated parameter structure from `selectedCryptoAssetId` to
`intent.assetId`

---

## **Changelog**

CHANGELOG entry: null

---

## **Related issues**

Refs:
[TRAM-2820](https://consensyssoftware.atlassian.net/browse/TRAM-2820)

---

## **Manual testing steps**

```gherkin
Feature: Dynamic Token Fetching

  Scenario: user views token selection screen with real tokens
    Given user is in a supported region (e.g., US-CA)
    And user has Ethereum network in wallet
    
    When user navigates to token selection screen
    Then user sees loading indicator
    And user sees tokens fetched from API
    And user sees supported tokens displayed normally
    And user sees unsupported tokens greyed out with info icon

  Scenario: user searches across all available tokens
    Given user is on token selection screen
    And curated top tokens are displayed
    
    When user types in search field
    Then search switches to complete token list
    And user sees all matching tokens (supported and unsupported)
    
    When user clears search
    Then screen returns to showing curated top tokens

  Scenario: user only sees tokens for networks they have
    Given user has only Ethereum network in wallet
    And API returns tokens for Ethereum, Tron, and Solana
    
    When screen loads
    Then user sees only Ethereum tokens
    And Tron and Solana tokens are filtered out

  Scenario: error handling when API fails
    Given API is unavailable or returns error
    
    When user navigates to token selection screen
    Then user sees error message
    And user can close the screen

  Scenario: no region detected
    Given user's region cannot be detected
    
    When user navigates to token selection screen
    Then no API call is made
    And appropriate state is shown
```

---

## **Screenshots/Recordings**

### **Before**

- Used hardcoded `MOCK_CRYPTOCURRENCIES` (5 tokens only)
- No loading states
- No error handling
- Custom navigation setup
- StyleSheet-based styling

### **After**

- Dynamic token fetching from API (25+ tokens)
- Loading indicator while fetching
- Error message on fetch failure
- Standard Ramp navigation pattern
- Tailwind CSS styling
- Network-based filtering
- Smart list switching (topTokens/allTokens)




https://github.com/user-attachments/assets/fc623885-1fc7-4f5e-8b19-921511e4e255


---

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

---

## **Files Changed Summary**

### **New Files:**
1. `app/components/UI/Ramp/hooks/useRampTokens.ts` - Hook to fetch
tokens from API
2. `app/components/UI/Ramp/hooks/useRampTokens.test.ts` - Comprehensive
tests (24 test cases)
3.
`app/components/UI/Ramp/components/TokenListItem/TokenListItem.test.tsx`
- Component tests (12 test cases)

### **Modified Files:**
4. `app/components/UI/Ramp/components/TokenSelection/TokenSelection.tsx`
- Integrated hook, added loading/error states
5.
`app/components/UI/Ramp/components/TokenSelection/TokenSelection.test.tsx`
- Updated tests for hook integration
6. `app/components/UI/Ramp/components/TokenListItem/TokenListItem.tsx` -
Made `isSelected` optional, removed from usage
7. `babel.config.tests.js` - Added hook files to process.env exclude
list
8. `locales/languages/en.json` - Added error message localization

### **Deleted Files:**
9.
`app/components/UI/Ramp/components/TokenSelection/TokenSelection.styles.ts`
- Migrated to Tailwind



[TRAM-2820]:
https://consensyssoftware.atlassian.net/browse/TRAM-2820?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduce useRampTokens to fetch region- and flow-aware tokens from
on-ramp cache and integrate into TokenSelection with loading/error
states, network filtering, and UI tweaks.
> 
> - **Ramp tokens fetching**:
> - Add `useRampTokens` hook to fetch tokens from env-aware on-ramp
cache (`production`/`staging`) with `action=buy|deposit` and `sdk`
version.
> - Filter tokens by user-added networks; expose `topTokens`,
`allTokens`, `isLoading`, `error`.
> - **TokenSelection**:
> - Replace mock data with `useRampTokens` output; default to
`topTokens`, switch to `allTokens` when searching.
> - Add loading spinner and localized error state
(`deposit.token_modal.error_loading_tokens`).
>   - Apply network filter bar using unique chains from fetched tokens.
> - Remove intent-based selection; mark unsupported via
`!token.tokenSupported` and show info modal.
> - **TokenListItem**:
> - Make `isSelected` optional; show `token.name` primary and
`token.symbol` secondary; use `depositNetworkName ?? networkName` for
badge; enable info button for disabled items.
> - **Tests & config**:
> - Add comprehensive tests for `useRampTokens`, `TokenSelection`, and
`TokenListItem` (snapshots and behaviors).
> - Update `babel.config.tests.js` to avoid inlining env vars for new
hook files.
> - Update snapshots reflecting name/symbol swap and disabled/state
changes.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5dadc12. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

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

Fixed critical OTP input freezing issues affecting three Card
onboarding/authentication screens where users were unable to navigate,
proceed, or sometimes even focus the 6-digit OTP input field.

**Problem:**
Users experienced UI freezing on OTP verification screens, preventing
them from:

- Focusing the OTP input field
- Navigating back to previous screens
- Proceeding after entering the verification code

**Root Cause:**
The implementations were missing the `useBlurOnFulfill` hook from
`react-native-confirmation-code-field`, which is critical for proper
input lifecycle management. Without this hook:

- The input field remained focused after all digits were entered
- The keyboard wasn't properly dismissed
- Focus conflicts caused the UI to freeze
- Navigation became blocked

**Solution:**

- Added `useBlurOnFulfill` hook to properly manage OTP input focus and
blur events
- Updated ref handling to use the hook's return value instead of manual
useRef
- Fixed hook dependency arrays to prevent stale closure issues
- Fixed a cooldown timing bug in ConfirmPhoneNumber (was setting
cooldown before API success)
- Updated all test mocks to include the new hook

The implementation now matches the working pattern used in the
Ramp/Deposit OTP screen (OtpCode.tsx), which doesn't experience these
issues.

## **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 OTP input freezing issues on Card email and phone
verification screens

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

- [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]
> Integrates useBlurOnFulfill for OTP fields, adds platform-specific
autocomplete, refactors focus/refs, and fixes resend cooldown timing;
updates tests and mocks accordingly.
> 
> - **Card OTP flows**
> - Use `useBlurOnFulfill` for `CodeField` in `CardAuthentication`,
`ConfirmEmail`, and `ConfirmPhoneNumber` to manage focus/blur and
prevent freezes.
> - Replace manual `useRef` with hook-provided refs; update focus
effects and dependency arrays.
> - Add platform-specific `autoComplete` (`android: 'sms-otp'`, default:
`'one-time-code'`) and type `TextInputProps`.
>   - Cast `CodeField` refs to `React.RefObject<TextInput>`.
> - **Resend/cooldown behavior**
> - In `ConfirmPhoneNumber`, set cooldown after successful resend;
maintain guard against rapid presses with `resendInProgressRef`.
> - In `CardAuthentication`/`ConfirmEmail`, ensure countdown timers and
resend handlers respect cooldown state.
> - **Tests**
> - Update `react-native-confirmation-code-field` mocks to include
`useBlurOnFulfill` in related test files.
> - Minor adjustments (e.g., variable rename for
`contactVerificationId`) to align with implementation.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
158a841. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Change tracked to enrolled and related strings

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

Changing Rewards strings to better terminology "enrolled" / "not
enrolled"

## **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: n/a

## **Manual testing steps**

```gherkin
Feature: my feature name

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

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

## **Screenshots/Recordings**

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

### **Before**

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

### **After**

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

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [ ] 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]
> Updates Rewards i18n strings in `locales/languages/en.json` to replace
“opt-in/tracked” with “enrolled” and adjusts related labels/descriptions
and error text.
> 
> - **Rewards i18n (`locales/languages/en.json`)**:
> - Terminology: replace “opt-in/opted in/tracked” with
“enroll/enrolled”.
> - `onboarding.not_supported_account_type_description`: “opted into” →
“enrolled in”.
> - `onboarding.geo_check_fail_description`: “country allows opting
into” → “region allows enrolling into”.
> - `settings.*`: maintain “opted in” counts but update related copy;
error `link_account_failed_error`: “link” → “add”.
> - `link_account_group.tracked/untracked/unsupported/tracked_count`:
“Tracked/Untracked/Unsupported” → “Enrolled/Not enrolled/Not supported”;
count string updated to “enrolled”.
> - Misc: `link_account_failed_error` under settings changed to “Failed
to add account.”
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d194816. 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 Nov 18, 2025
@pull pull Bot added the ⤵️ pull label Nov 18, 2025
@pull pull Bot merged commit ef915e4 into Reality2byte:main Nov 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.