Skip to content

[pull] main from MetaMask:main#776

Merged
pull[bot] merged 14 commits into
Reality2byte:mainfrom
MetaMask:main
May 21, 2026
Merged

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

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 21, 2026

See Commits and Changes for more details.


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

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

MarioAslau and others added 14 commits May 21, 2026 00:23
## **Description**

The Predict Buy bottom-sheet introduced in
[#28779](#28779) opened
with the custom keypad already rendered behind the rest of the sheet
content. The root cause was a single `isInputFocused` boolean inside
`usePredictBuyInputState` that was hard-coded to `true` on first render
and overloaded across **five** unrelated behaviours:

1. Mounting `PredictKeypad`.
2. Highlighting the amount-display "active" state.
3. Hiding `PredictBuyBottomContent` while the keypad is up.
4. Disabling `PredictFeeSummary`.
5. Deferring the mm_pay relay-config side effects (`updatePendingAmount`
/ `setPayToken`) inside `PredictPayWithAnyTokenInfo`.

Because the only "off switch" was `setIsInputFocused(false)` (driven
from a `Done` button that doesn't exist in sheet mode) the previous PR
worked around the issue with three `isSheetMode ? false :
isInputFocused` ternaries — Bugbot flagged this on #28779. The
workarounds also produced a confusing situation where bottom content and
keypad rendered simultaneously on first sheet open.

This PR separates the conflated meanings, parameterises the initial
state, and removes the workarounds. No behaviour change for the legacy
full-screen flow.

### What changed

**`usePredictBuyInputState`
([hooks/usePredictBuyInputState.ts](app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyInputState.ts))**
- Accepts an options object: `{ initialKeypadOpen = true }`.
- Renamed state + setter: `isInputFocused` → `isKeypadOpen`,
`setIsInputFocused` → `setIsKeypadOpen`.
- Default value preserves legacy behaviour; sheet mode opts out.

**`PredictBuyWithAnyToken`
([PredictBuyWithAnyToken.tsx](app/components/UI/Predict/views/PredictBuyWithAnyToken/PredictBuyWithAnyToken.tsx))**
- Calls the hook as `usePredictBuyInputState({ initialKeypadOpen:
!isSheetMode })` so the sheet opens with the keypad collapsed.
- Removed the three `isSheetMode ? false : isInputFocused` patches.
- Renders `PredictBuyBottomContent` at the call site via `{(isSheetMode
|| !isKeypadOpen) && <PredictBuyBottomContent ... />}` instead of
relying on the component to bail internally. Keeps the legacy "hide
while typing" behaviour while letting the sheet show the bottom content
(and Confirm) at all times.
- Passes the new self-documenting `shouldDeferRelaySetup={!isSheetMode
&& isKeypadOpen}` prop to `PredictPayWithAnyTokenInfo`. Same effective
value as before, but the prop name now describes its actual purpose.

**`PredictPayWithAnyTokenInfo`
([components/PredictPayWithAnyTokenInfo](app/components/UI/Predict/views/PredictBuyWithAnyToken/components/PredictPayWithAnyTokenInfo/PredictPayWithAnyTokenInfo.tsx))**
- Renamed prop: `isInputFocused` → `shouldDeferRelaySetup`. This is the
genuine separation: the prop is **not** about UI keypad state, it's
about pausing `updatePendingAmount` / `setPayToken` calls. Legacy mode
still defers until the user taps Done; sheet mode never defers (relay
must update on every keystroke since there's no Done and the user can
tap Confirm with the keypad still open — preventing underfunded
deposits).
- Added a JSDoc comment explaining the contract.

**`PredictBuyBottomContent`
([components/PredictBuyBottomContent](app/components/UI/Predict/views/PredictBuyWithAnyToken/components/PredictBuyBottomContent/PredictBuyBottomContent.tsx))**
- Removed the `isInputFocused` prop and the `if (isInputFocused) return
null` early return. Component is now purely structural; visibility is
the parent's responsibility.

**`PredictKeypad` and `PredictBuyAmountSection`**
- Mechanical prop renames: `isInputFocused` → `isKeypadOpen`,
`setIsInputFocused` → `setIsKeypadOpen`.

**Legacy `PredictBuyPreview`
([views/PredictBuyPreview](app/components/UI/Predict/views/PredictBuyPreview/PredictBuyPreview.tsx))**
- Local `useState(true)` renamed to `isKeypadOpen` for consistency with
the rest of the tree. No behaviour change (still initialises `true`,
still gates `renderBottomContent`).

**Tests**
- All prop/state references renamed.
- New coverage on `usePredictBuyInputState` for `initialKeypadOpen:
false` and `initialKeypadOpen: true`.
- New assertions in `PredictBuyWithAnyToken.test.tsx` that the hook is
called with `initialKeypadOpen: false` in sheet mode and
`initialKeypadOpen: true` in non-sheet mode.
- `PredictBuyBottomContent.test.tsx`: removed the now-irrelevant
`isInputFocused is true / false` describe blocks since visibility is
caller-controlled.
- `PredictPayWithAnyTokenInfo.test.tsx`: updated 43 prop references and
renamed two test descriptions to reflect the new "relay deferral"
intent.
- `PredictBuyPreview.test.tsx`: updated `renderBottomContent` describe
block descriptions.

### Net behavioural effect (sheet mode)

- Sheet opens → keypad hidden, amount display shows `$0` (inactive),
quick amounts + pay-with row + fee summary + Confirm button visible
(Confirm disabled until amount > 0).
- Tap amount display → keypad opens at the bottom of the sheet, stacked
**below** the Confirm button (does not overlap).
- Tap a quick amount → value set, keypad closes.
- Tap Confirm with keypad still open → places order (relay setup is
up-to-date because `shouldDeferRelaySetup` is `false` in sheet mode).
- Auto-blur on banner display still works.

Legacy full-screen flow: unchanged.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: PRED-707 (follow-up to
[#28779](#28779))

## **Manual testing steps**

```gherkin
Feature: Predict buy bottom-sheet keypad initial state

  Scenario: Sheet opens with keypad collapsed (flag ON)
    Given the predictBottomSheet feature flag is enabled
    And the user is on a prediction market details page

    When user taps a "Yes" or "No" outcome button
    Then a bottom sheet opens with the buy preview content
    And the custom numeric keypad is NOT visible
    And the amount display shows "$0"
    And the quick amount buttons ($20 / $50 / $100 / $250) are visible
    And the Pay with row, fee summary and Confirm button are visible
    And the Confirm button is disabled

  Scenario: Tapping the amount opens the keypad
    Given the buy bottom sheet is open with no amount entered
    And the keypad is hidden

    When user taps the amount display
    Then the custom keypad becomes visible at the bottom of the sheet
    And the keypad does NOT cover the Confirm button
    And the amount display shows the active highlight

  Scenario: Tapping a quick amount sets value and closes the keypad
    Given the keypad is open
    And the user has typed "$73"

    When user taps the "$50" quick amount button
    Then the amount becomes "$50"
    And the keypad closes
    And haptic feedback fires (Light impact)
    And the Confirm button is enabled

  Scenario: Confirming with keypad still open
    Given the keypad is open
    And the user has typed "$25"

    When user taps the Confirm button
    Then the order is placed successfully
    And the relay was configured for $25 (no underfunded deposit)
    And the sheet closes

  Scenario: Banner display auto-blurs the keypad in sheet mode
    Given the buy bottom sheet is open
    And the keypad is open

    When an order_failed or price_changed banner appears
    Then the keypad auto-closes
    And the banner + Retry CTA are visible without needing to tap Done

  Scenario: Legacy full-screen flow is unchanged (flag OFF)
    Given the predictBottomSheet feature flag is disabled
    And the user navigates to the BuyPreview screen

    When the screen mounts
    Then the keypad is open by default (matching previous behaviour)
    And tapping Done closes the keypad and reveals the bottom content
    And the relay is configured once on Done (deferred during typing)
```

## **Screenshots/Recordings**

### **Before**

Sheet opens with the keypad rendered behind the bottom content; bottom
content and keypad are both visible simultaneously on first paint. (See
screenshot in PR comments — keypad sits below `Confirm` even though the
user has not yet tapped the amount display.)

### **After**

Sheet opens with the keypad hidden; bottom content (quick amounts, pay
with row, fee summary, Confirm) is the only thing visible. Tapping the
amount display reveals the keypad; tapping a quick amount or Confirm
collapses it.



https://github.com/user-attachments/assets/af89b78a-5103-48eb-b2c9-346ea64cd463


<!-- TODO: attach .mov / screenshots once recorded against the branch
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

> N/A — pure refactor of UI state semantics, no new code paths.

## **Pre-merge reviewer checklist**

- [x] 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 how the Predict buy keypad state is tracked and used to gate
bottom content and mm_pay relay setup; mistakes could surface as
incorrect UI visibility or misconfigured deposit/payment amounts during
checkout. Scope is mostly contained to Predict buy views and covered by
updated tests.
> 
> **Overview**
> Fixes the Predict buy bottom-sheet keypad initial state by splitting
the previously overloaded `isInputFocused` flag into a dedicated
`isKeypadOpen` state, including a new `initialKeypadOpen` option in
`usePredictBuyInputState` so sheet mode can start closed.
> 
> Updates
`PredictBuyWithAnyToken`/`PredictKeypad`/`PredictBuyAmountSection` to
use `isKeypadOpen` for keypad mounting and active styling, moves
bottom-content visibility control to the parent (removing
`PredictBuyBottomContent`’s internal early-return), and introduces
`shouldDeferRelaySetup` (replacing `isInputFocused`) to explicitly gate
`PredictPayWithAnyTokenInfo`’s relay-configuration side effects.
> 
> Refactors legacy `PredictBuyPreview` to the same `isKeypadOpen` naming
and updates/adds tests to assert the new initialization, visibility
behavior, and relay deferral propagation.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
98d3d4d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**

Adds the **Perps** section to the redesigned _Pay with_ bottom sheet. On
`perpsDepositAndOrder` confirmations, a **Perps account** row now
renders above the Crypto section with an inline **Add** button that
routes to the standalone Perps deposit confirmation. Visibility mirrors
the existing legacy-modal rule (`perpsDepositAndOrder` only —
`perpsDeposit` is filtered out to avoid a recursive "deposit to deposit"
loop).
The Perps row is an **Add CTA, not a selection row**. Payment-source
state for perps flows lives in a dual-state machinery
(`PerpsController.selectedPaymentToken` for the UX choice +
`TransactionPayController.payToken` for the real on-chain funding
source) that the legacy modal already orchestrates — there is no on-row
"selected token" to display.

All changes are dark-launched behind `MM_DEV_PAY_WITH_BOTTOM_SHEET` and
have no effect in production.

<!--
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: https://consensyssoftware.atlassian.net/browse/CONF-1362

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


https://github.com/user-attachments/assets/a6e692da-a2b1-4c92-8555-c4abcea2b2fa


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

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

- [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.

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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**
> Touches confirmations payment-selection and navigation flows
(including multi-pop dismissal logic) for `perpsDepositAndOrder`, which
could cause incorrect pay token selection or route-dismissal issues
despite being dev-flag gated.
> 
> **Overview**
> Adds a new **Perps** section to the redesigned `Pay with` bottom sheet
for `perpsDepositAndOrder`, showing a `Perps account` row with inline
`Add` that launches the Perps deposit confirmation and a tap that
selects Perps balance.
> 
> Updates Perps pay-token selection to support lightweight `{address,
chainId}` inputs, adjusts the Crypto section so checkmarks/selected rows
don’t conflict when Perps balance or fiat payment *implicitly owns*
selection, and wires the Perps order screen to open the new bottom sheet
when enabled.
> 
> Improves picker dismissal/navigation robustness: `PayWithModal` can
now atomically pop multiple routes (`dismissOnSelectCount`) to avoid
Android double-pop crashes, and `useDismissOnPaymentChange` now guards
against dismissing when the route isn’t focused. Adds/updates unit tests
and English strings for the new Perps labels.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
94d8917. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Goktug Poyraz <omergoktugpoyraz@gmail.com>
## **Description**

Add user-configurable max slippage **and** live estimated slippage to
the Perps order screen for market orders, with a client-side block when
the estimate exceeds the user's cap. Matches HyperLiquid's native UX
(max slippage applies to market orders placed from the order form only).

**What this PR ships**
- New `Max slippage` row on the order form, visible on market orders
only. Tapping opens a config bottom sheet with quick-pick presets (0.5%,
2%, 3%) and a keypad-driven custom sheet (range 0.1%–10%, 10 bps step).
- Max slippage persisted globally as a user preference in
`PerpsController` state (basis points, default 300 bps = 3%).
- **Live estimated slippage** computed from the HyperLiquid L2 order
book via VWAP: walks asks (BUY) or bids (SELL) up to the requested USD
notional and reports the weighted-average fill price's distance from the
mid-price. Displayed in the slippage row as `Est: X% / Max: Y%`.
- **Client-side block** on market orders whose estimated slippage
exceeds the configured cap. Submit is blocked, a toast explains why, and
a `slippage_limit_blocked_order` event fires.
- User-configured `maxSlippageBps` is wired through to the HyperLiquid
order: market-order limit price = `currentPrice * (1 ± maxSlippageBps /
10000)`. The first commit on this branch fixed a trap where the order
screen set bps while the limit-price math read a separate
(always-undefined) `slippage` decimal field, so the user's setting
silently fell back to a hardcoded 3%. The second commit collapses both
fields into a single `maxSlippageBps`.
- MetaMetrics: `slippage_config_opened`, `slippage_config_changed`, and
new `slippage_limit_blocked_order` interaction types;
`max_slippage_pct`, `max_slippage_source` (`default` /
`user_configured`), and `estimated_slippage_pct` properties.

All slippage values are stored internally as basis points (integers).
Display converts to percentage only at the UI boundary.

**How slippage is applied (parity with HyperLiquid native)**

| Path | Our value | HyperLiquid native |
|---|---|---|
| Market order — order form | User-configured `maxSlippageBps`, default
300 bps (3%) | User-configurable |
| Limit order | None — user-provided limit price used directly | Same |
| TP/SL trigger (market execution) | Hardcoded 1000 bps (10%) via
`DefaultTpslSlippageBps` | 1000 bps (10%) |
| Close position (market) | Hardcoded 300 bps (3%) via
`DefaultMarketSlippageBps` | 800 bps (8%) |

Close-position slippage is hardcoded and **out of scope** for this story
per Jira §4: "Slippage on position close / add margin flows" is
explicitly excluded. Alignment to HL's 8% can be a small follow-up if
desired.

**Acceptance criteria coverage**
- AC1 — Estimated slippage shown ✓ (VWAP from live order book)
- AC2 — Max slippage tap-to-configure with bottom sheet ✓
- AC3 — Setting persists across sessions ✓
- AC4 — Default 3% ✓
- AC5 — Order blocked when estimate exceeds max ✓ (client-side, with
toast + analytics event)
- AC6 — Order success rate guardrail — to verify post-rollout via
Mixpanel

**Out of scope (per Jira §4)**
- Per-asset / per-leverage overrides.
- TP/SL slippage configuration (execution slippage hardcoded; no user
control).
- Slippage configuration on position close / add margin flows.
- Any change to order routing — this PR is UI + persisted config + a
client-side guard.

## **Changelog**

CHANGELOG entry: Added estimated slippage and a configurable max
slippage preference for perps market orders, with submission blocked
when the estimate exceeds the configured cap.

## **Related issues**

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

## **Manual testing steps**

```gherkin
Feature: Perps slippage visualization, configuration, and block

  Scenario: user sees estimated slippage on market order
    Given user is on the perps order screen with a market order and amount entered
    When the order form renders
    Then a "Slippage" row shows `Est: X% / Max: 3%` (X computed from live book depth)
    And the value updates as the order amount changes

  Scenario: user configures max slippage via preset
    Given user is on the perps order screen
    When user taps the "Slippage" row
    Then a bottom sheet opens with quick-pick chips (0.5%, 2%, 3%) and an Edit chip
    When user selects "2%" and taps Set
    Then the row updates to `Est: X% / Max: 2%` and the value persists across sessions

  Scenario: user configures max slippage via custom keypad
    Given the slippage bottom sheet is open
    When user taps the Edit chip
    Then a custom-value sheet opens with `−`/`+` controls and a numeric keypad
    When user enters a value outside 0.1–10% range
    Then an error message appears and Set is disabled
    When user enters a valid value (e.g. 5) and taps Set
    Then the row updates to `Est: X% / Max: 5%` and persists

  Scenario: market order uses configured slippage
    Given max slippage is configured to 5%
    When user places a market BUY at $50,000 spot
    Then the HyperLiquid order is submitted with limit price ≈ $50,000 * 1.05 = $52,500

  Scenario: order blocked when estimated slippage exceeds max
    Given user has set max slippage to 0.1%
    And the order size is large enough that estimated slippage > 0.1%
    When user taps Place Order
    Then the order is NOT submitted
    And a toast explains "Estimated slippage exceeds your max"
    And a `slippage_limit_blocked_order` MetaMetrics event fires

  Scenario: limit order ignores max slippage
    Given user is on the perps order screen with a limit order
    When the form renders
    Then the Slippage row is hidden
    And the order uses the user-provided limit price untouched
```

## **Screenshots/Recordings**

### **Before**

Slippage UI was a single `Max slippage` row inside the leverage/details
box with an inline text-input bottom sheet — no preset chips, no DS chip
styling, and the user-configured value did not reach HyperLiquid (silent
3% fallback).

### **After**

| Order form (default 3%) | Main slippage sheet |
|---|---|
|
![order-default](https://raw.githubusercontent.com/abretonc7s/mm-mobile-farm-artifacts/main/fixes/30125/01-order-form-default.png)
|
![main-sheet](https://raw.githubusercontent.com/abretonc7s/mm-mobile-farm-artifacts/main/fixes/30125/02-main-slippage-sheet.png)
|
| Slippage row sits above Fees with `Est: 0% / Max: 3% ✏️`. BodySM size
hierarchy on Margin / Liquidation / Slippage / Fees. Flex spacer pins
the info block to the bottom of the scroll view. | DS `ButtonFilter`
chips `0.5% / 2% / 3%` (3% active), trailing edit pill, `Set` footer. |

| Custom slippage sheet | Order form after Set 5% |
|---|---|
|
![custom-sheet](https://raw.githubusercontent.com/abretonc7s/mm-mobile-farm-artifacts/main/fixes/30125/03-custom-slippage-sheet.png)
|
![order-5pct](https://raw.githubusercontent.com/abretonc7s/mm-mobile-farm-artifacts/main/fixes/30125/04-order-form-custom.png)
|
| Title `Use custom slippage`. Centered value with blinking cursor,
evenly spaced `−` / `+` `ButtonIcon` controls, full keypad, `Cancel` /
`Set` footer. Snaps and clamps to 10–1000 bps in 10 bps steps. | Row
updates to `Est: 0% / Max: 5% ✏️`. Persistence verified via recipe:
controller persists 500 bps after Set. |


## **Validation Recipe**

<details><summary>recipe.json — slippage UI visibility, config sheet,
persistence, default value</summary>

```json
{
  "title": "TAT-1043: Slippage visualization and configuration",
  "schema_version": 1,
  "description": "Validates slippage UI on perps market order: default 3% max slippage, slippage row visible, config sheet opens and changes value, persistence of the user's selection.",
  "validate": {
    "workflow": {
      "pre_conditions": ["wallet.unlocked", "perps.ready_to_trade"],
      "setup": [
        { "id": "setup-nav-home", "action": "navigate", "target": "PerpsHomeView" }
      ],
      "entry": "ensure-testnet",
      "nodes": {
        "ensure-testnet": { "action": "call", "ref": "perps/setup-testnet", "next": "check-default-slippage" },
        "check-default-slippage": { "action": "eval_sync", "expression": "(function(){var ctrl=Engine.context.PerpsController;var val=ctrl.getMaxSlippage();return JSON.stringify({defaultBps:val===undefined?300:val,isDefault:val===undefined||val===300})})()", "assert": { "operator": "eq", "field": "isDefault", "value": true }, "next": "clear-btc-position" },
        "clear-btc-position": { "action": "eval_async", "expression": "Engine.context.PerpsController.getPositions().then(function(ps){var p=ps.find(function(x){return x.symbol==='BTC'});if(!p)return JSON.stringify({cleared:true});return Engine.context.PerpsController.closePosition({symbol:'BTC'}).then(function(){return JSON.stringify({cleared:true})})})", "assert": { "operator": "eq", "field": "cleared", "value": true }, "next": "wait-btc-clear" },
        "wait-btc-clear": { "action": "wait", "duration_ms": 2000, "next": "nav-to-btc" },
        "nav-to-btc": { "action": "wait_for", "test_id": "perps-market-row-item-BTC", "timeout_ms": 8000, "next": "press-btc-row" },
        "press-btc-row": { "action": "press", "test_id": "perps-market-row-item-BTC", "next": "wait-side-button" },
        "wait-side-button": { "action": "wait_for", "test_id": "perps-market-details-long-button", "timeout_ms": 8000, "next": "press-long" },
        "press-long": { "action": "press", "test_id": "perps-market-details-long-button", "next": "wait-amount" },
        "wait-amount": { "action": "wait_for", "test_id": "perps-amount-display-touchable", "timeout_ms": 10000, "next": "press-amount" },
        "press-amount": { "action": "press", "test_id": "perps-amount-display-touchable", "next": "wait-keypad" },
        "wait-keypad": { "action": "wait_for", "test_id": "perps-order-view-keypad", "timeout_ms": 5000, "next": "clear-keypad" },
        "clear-keypad": { "action": "clear_keypad", "count": 8, "next": "type-amount" },
        "type-amount": { "action": "type_keypad", "value": "10", "next": "press-done" },
        "press-done": { "action": "press", "test_id": "perps-order-view-keypad-done", "next": "wait-order-form" },
        "wait-order-form": { "action": "wait_for", "test_id": "perps-order-view-place-order-button", "timeout_ms": 15000, "next": "wait-slippage-row" },
        "wait-slippage-row": { "action": "wait_for", "test_id": "perps-order-view-slippage-value", "timeout_ms": 10000, "next": "screenshot-row" },
        "screenshot-row": { "action": "screenshot", "filename": "evidence-slippage-visible.png", "note": "Market order form showing slippage row (3% default)", "next": "check-max-display" },
        "check-max-display": { "action": "wait_for", "test_id": "perps-order-view-slippage-row", "timeout_ms": 5000, "next": "open-config" },
        "open-config": { "action": "press", "test_id": "perps-order-view-slippage-row", "next": "wait-config-sheet" },
        "wait-config-sheet": { "action": "wait_for", "test_id": "perps-slippage-config-input", "timeout_ms": 5000, "next": "screenshot-config" },
        "screenshot-config": { "action": "screenshot", "filename": "evidence-slippage-config-sheet.png", "note": "Slippage config bottom sheet open with input field and quick-pick presets", "next": "change-value" },
        "change-value": { "action": "set_input", "test_id": "perps-slippage-config-input", "value": "5", "next": "save-value" },
        "save-value": { "action": "press", "test_id": "perps-slippage-config-save", "next": "wait-sheet-close" },
        "wait-sheet-close": { "action": "wait", "duration_ms": 1000, "next": "verify-persisted" },
        "verify-persisted": { "action": "eval_sync", "expression": "(function(){var ctrl=Engine.context.PerpsController;var val=ctrl.getMaxSlippage();return JSON.stringify({bps:val,isPersisted:val===500})})()", "assert": { "operator": "eq", "field": "isPersisted", "value": true }, "next": "screenshot-updated" },
        "screenshot-updated": { "action": "screenshot", "filename": "evidence-slippage-changed.png", "note": "Order form now shows max slippage updated to 5% after config change", "next": "restore-default" },
        "restore-default": { "action": "eval_sync", "expression": "(function(){Engine.context.PerpsController.setMaxSlippage(300);var val=Engine.context.PerpsController.getMaxSlippage();return JSON.stringify({restored:val===300})})()", "assert": { "operator": "eq", "field": "restored", "value": true }, "next": "done" },
        "done": { "action": "end", "status": "pass" }
      },
      "teardown": [
        { "id": "teardown-restore-slippage", "action": "eval_sync", "expression": "(function(){Engine.context.PerpsController.setMaxSlippage(300);return JSON.stringify({clean:true})})()", "assert": { "operator": "not_null" } },
        { "id": "teardown-nav-home", "action": "navigate", "target": "PerpsHomeView" }
      ]
    }
  }
}
```

</details>

## **Validation Logs**

Command:
```bash
IOS_SIMULATOR=mm-3 node scripts/perps/agentic/validate-recipe.js .task/feat/tat-1043-0513-225508/artifacts/recipe.json
```

<details><summary>Full output (all steps passed)</summary>

```
[check-default-slippage] result: {"defaultBps":300,"isDefault":true} PASS
[clear-btc-position] result: {"cleared":true} PASS
[nav-to-btc] result: {"visible":true} PASS
[press-btc-row] result: {"ok":true} PASS
[wait-side-button] result: {"visible":true} PASS
[press-long] result: {"ok":true} PASS
[wait-amount] result: {"visible":true} PASS
[press-amount] result: {"ok":true} PASS
[wait-keypad] result: {"visible":true} PASS
[clear-keypad] result: {"ok":true,"deleted":8} PASS
[type-amount] result: {"ok":true,"value":"10"} PASS
[press-done] result: {"ok":true} PASS
[wait-order-form] result: {"visible":true} PASS
[wait-slippage-row] result: {"visible":true} PASS
[screenshot-row] PASS
[check-max-display] result: {"visible":true} PASS
[open-config] result: {"ok":true} PASS
[wait-config-sheet] result: {"visible":true} PASS
[screenshot-config] PASS
[change-value] result: {"ok":true,"value":"5"} PASS
[save-value] result: {"ok":true} PASS
[wait-sheet-close] PASS
[verify-persisted] result: {"bps":500,"isPersisted":true} PASS
[screenshot-updated] PASS
[restore-default] result: {"restored":true} PASS

Recipe: PASS
```

</details>

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

#### Performance checks (if applicable)

- [x] I've tested on Android
- [x] I've tested with a power user scenario
- [x] I've instrumented key operations with Sentry traces for production
performance metrics

## **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]
> **High Risk**
> High risk because it changes market-order execution parameters
(limit-price buffering) and introduces a new client-side block path that
can prevent order submission; it also adds persisted controller state
used during trading.
> 
> **Overview**
> Adds a **Slippage** row to the Perps order form (market orders only)
showing live VWAP-based estimated slippage from the L2 order book
alongside a user-configurable max cap, and opens a new bottom-sheet flow
(quick-picks + custom keypad) to update that cap.
> 
> Persists `maxSlippageBps` in `PerpsController` with new
`getMaxSlippage`/`setMaxSlippage` actions (clamped/snapped to bounds),
wires the cap through order placement/editing to HyperLiquid via
`maxSlippageBps` (with backward-compatible normalization of deprecated
decimal `slippage`), and blocks `placeOrder` when the estimate exceeds
the configured cap (toast + new MetaMetrics properties/events).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
df3225c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…ys overlay (#30180)

## **Description**

Follow-up to
[#29853](#29853).
Switches `yarn skills` to the new **public**
[`MetaMask/skills`](https://github.com/MetaMask/skills) repo, with the
private [`Consensys/skills`](https://github.com/Consensys/skills) repo
available as an optional overlay for internal-only material. Adds a
`postinstall` hook so engineers get an auto-updated skill set on every
`yarn install` — no manual `yarn skills` needed.

### Why public

- Cloud agents (Cursor cloud, Codex cloud, Claude.ai web) can't `git
clone` a private repo or set engineer-managed env vars. The public repo
solves this with a one-liner curl bootstrap (no SSH, no auth).
- New engineers don't need Consensys SSO to start using skills — the
public set installs cleanly on its own.
- Out-of-the-box auto-sync via `postinstall` (Ramon's ask in
`#engineering-mobile`).

### Engineer experience (zero-config)

```
yarn install   # postinstall clones MetaMask/skills into .skills-cache/ and installs
yarn skills    # auto-detects the cache, no env var or shell rc edit needed
```

Override only if you keep a separate clone:
```
export METAMASK_SKILLS_DIR=~/dev/metamask/skills
export CONSENSYS_SKILLS_DIR=~/dev/Consensys/skills   # optional private overlay
```

### Changes

- `scripts/skills-sync.mts` — multi-source aware wrapper (was bash,
ported to node `.mts` for cross-platform consistency). Resolution order:
`METAMASK_SKILLS_DIR` → `CONSENSYS_SKILLS_DIR` → auto-fallback to
`.skills-cache/metamask-skills`. When the cache is used, injects
`METAMASK_SKILLS_DIR=<cache>` so the bash sync underneath sees the
source.
- `scripts/skills-postinstall.mts` **(new)** — auto-clones
`MetaMask/skills` into `.skills-cache/metamask-skills` and runs the
installer on every `yarn install`. Layered with `CONSENSYS_SKILLS_DIR`
when set. Best-effort: skipped on CI, on `SKILLS_SKIP_POSTINSTALL=1`, or
when offline. Opt back in on CI with `SKILLS_FORCE_POSTINSTALL=1`.
- `.gitignore` — adds `.skills-cache/`, updates comments to reference
public canonical.
- `package.json` — adds `postinstall` hook + lavamoat `"$root$": true`
allowScripts entry; `skills` script uses `tsx scripts/skills-sync.mts`
(node 20 has no native TS).
- `.skills.local.example` — documents new zero-config default + optional
env-var override.

### Companion PRs

- [`MetaMask/skills`](https://github.com/MetaMask/skills) (merged) —
public source of truth, install CLI, multi-source `sync`, cloud-agent
`bootstrap`.
- [`Consensys/skills#9`](Consensys/skills#9) —
private overlay only; repo retained for future internal material.
-
[`MetaMask/metamask-extension#42488`](MetaMask/metamask-extension#42488)
— parallel extension migration.
-
[`MetaMask/decisions#162`](MetaMask/decisions#162)
— ADR amendment.

### Out of scope

- TS rewrite of CLI.
- Personal-skills tooling.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: zero-config yarn skills sync

  Scenario: fresh clone, no env vars
    Given the engineer cloned this branch and has no METAMASK_SKILLS_DIR set
    When they run `yarn install`
    Then `.skills-cache/metamask-skills/` is populated from MetaMask/skills
    And skills are installed under `.claude/`, `.cursor/`, `.agents/`
    And `git status --short` reports a clean working tree
    When they later run `yarn skills`
    Then the wrapper auto-detects the cache and re-syncs without any env var

  Scenario: engineer with explicit clone
    Given METAMASK_SKILLS_DIR points at a separate clone outside the repo
    When the engineer runs `yarn skills`
    Then the explicit clone is used in preference to the cache

  Scenario: federation with private overlay
    Given METAMASK_SKILLS_DIR and CONSENSYS_SKILLS_DIR both point at valid clones
    When the engineer runs `yarn skills`
    Then skills from both sources install
    And on name collision the Consensys (private) version wins

  Scenario: CI install skips postinstall by default
    Given CI=1 is set
    When the engineer runs `yarn install`
    Then skills-postinstall.mts exits immediately without writing anything

  Scenario: CI install opts back in
    Given CI=1 and SKILLS_FORCE_POSTINSTALL=1 are set
    When the engineer runs `yarn install`
    Then skills-postinstall.mts clones the cache and runs the install

  Scenario: opt out per machine
    Given SKILLS_SKIP_POSTINSTALL=1 is set
    When the engineer runs `yarn install`
    Then skills-postinstall.mts exits immediately

  Scenario: Windows engineer without WSL/Git Bash
    Given the engineer runs the wrapper on Windows PowerShell
    When they run `yarn skills` or `yarn install`
    Then the node wrappers execute without requiring bash on PATH at the wrapper layer
```

## **Screenshots/Recordings**

No UI change — agent tooling and repo wiring only.

### **Before**

N/A

### **After**

N/A

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
- [ ] I've tested with a power user scenario
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics

## **Pre-merge reviewer checklist**

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes install-time behavior via a new
`postinstall` script (runs `git` and writes to `.skills-cache/`) and
updates Lavamoat allow-scripts, which could impact developer/CI installs
if misconfigured.
> 
> **Overview**
> Updates agent-skills tooling to prefer the public `MetaMask/skills`
source (optionally layered with `Consensys/skills`) and replaces the old
`bash`-only `scripts/skills-sync.sh` with a `tsx` wrapper that
auto-detects sources via env vars or `.skills.local`.
> 
> Adds a best-effort `postinstall` hook that clones/updates
`MetaMask/skills` into `.skills-cache/` (skipped on CI unless opted in)
and updates docs/config templates plus `.gitignore`/Lavamoat settings to
support the new zero-config workflow.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ca9187e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **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 updates Perps account resolution so compliance gated Perps flows
evaluate the same EVM account the user currently has selected.

As part of the compliance work, Perps needs to reliably identify the
active wallet address before allowing account specific actions such as
deposits, trades, signing, and account state operations. The previous
logic derived the address from the selected account group, which could
pick a different EVM account than the user facing selected account when
multiple EVM accounts exist in the group.

The solution adds a selected-account-first lookup across the Perps
controller and related services. Perps now reads
`AccountsController:getSelectedAccount` when it is an EVM account, and
falls back to the existing selected account group behavior when needed.
The PR also delegates that action through the Perps controller messenger
so the selected account path works at runtime.

This keeps Perps address resolution aligned with the active wallet while
preserving compatibility with the existing account group fallback path.

This also wires Perps cache invalidation to selected-account changes, so
switching between EVM accounts in the same account group clears stale
user data and preloads data for the newly selected account.

## **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: Prefer the selected EVM account when resolving Perps
account state and compliance-gated actions.

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

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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 how Perps resolves the active wallet address and when it
clears/preloads cached user data, which can impact deposits/signing and
account-scoped state if the new selection logic misidentifies the active
account.
> 
> **Overview**
> Perps now resolves the active EVM address by **preferring
`AccountsController:getSelectedAccount`** (when it’s an EVM account) and
falling back to the selected account group, via new
`getSelectedEvmAccountFromMessenger` helpers.
> 
> This selection logic is applied across `PerpsController` and related
services (deposit/withdrawal, wallet adapters, rewards, data lake
reporting), and market/user-data preloading now also listens to
`AccountsController:selectedAccountChange` to clear disk+memory user
cache and refresh data on account switches. Tests were added/updated to
cover selected-account preference, cache invalidation, and messenger
delegation.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b6c41c9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: abretonc7s <107169956+abretonc7s@users.noreply.github.com>
…in 2.23.0 cp-7.78.0 (#30463)

## Summary

`@segment/analytics-react-native` 2.23.0 introduced a strict
`validateURL` regex via [PR
#1157](segmentio/analytics-react-native#1157)
that only allows `[a-zA-Z0-9_.-]` in query-param values. The MetaMask
Segment proxy URL encodes the write key as standard base64 in a query
param (`?b=<base64>==`), and the trailing `=` padding characters are
rejected by this regex.

When the URL fails validation `SegmentDestination.getEndpoint()`
silently falls back to `https://api.segment.io/v1/b`. Events reach
Segment's default endpoint but the proxy write key is only valid through
`fn.segmentapis.com`, so they are rejected — causing **no events to
appear in Mixpanel**.

## Change

Added `normalizeProxyUrl` in `platform-adapter.ts` that strips trailing
`=` padding from query-param values before passing the URL to the
Segment client config.

- Stripping base64 padding is safe: decoders infer it from data length
and the proxy server accepts both forms.
- The `=` key–value separator is preserved (the regex uses a lookahead
`(?=&|$)` to match only padding at the end of a param value).
- Contains a `TODO` to remove once upstream fixes the regex to accept
all RFC 3986 query characters.

## Test plan

- [ ] Run the app in dev mode and verify analytics events appear in
Mixpanel
- [ ] Verify no "Invalid URL has been passed" errors in the console
- [ ] Run unit tests: `yarn jest platform-adapter`


Made with [Cursor](https://cursor.com)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Moderate risk because it changes how the Segment client is configured
and could affect where analytics events are sent if the proxy URL is
altered incorrectly, though the change is narrowly scoped and covered by
unit tests.
> 
> **Overview**
> Fixes Segment proxy URL validation failures by normalizing
`SEGMENT_PROXY_URL` before passing it to `createClient`, stripping
trailing base64 `=` padding from query-param values (while preserving
key/value separators).
> 
> Adds focused unit coverage for `normalizeProxyUrl` across common URL
shapes (single/double padding, multi-param URLs) and a wiring test to
ensure `createPlatformAdapter` passes the normalized `proxy` value into
Segment client configuration.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ee00a41. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: João Santos <joaosantos15@users.noreply.github.com>
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**

Changes
- Expose getVipTierForAccount from rewards controller
- implement RewardsVipBadge component, which fetches the account's
vipTier and displays it
- Update Bridge page disclaimers to reflect discounted fees

<!--
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: feat: show RewardsVipBadge in swap page

## **Related issues**

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

## **Manual testing steps**

```gherkin
Feature: rewards vip badge

  Scenario: user is in the VIP program
    Given they request a swap quote

    When user receives a quote
    Then they see the VIP badge and their discounted MM fee

  Scenario: user is not in the VIP program
    Given they request a swap quote

    When user receives a quote
    Then they don't see the VIP badge
```

## **Screenshots/Recordings**

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

### **Before**

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

### **After**

<img width="321" height="190" alt="Screenshot 2026-05-19 at 2 54 41 PM"
src="https://github.com/user-attachments/assets/0e4758b3-7fc6-4ebc-aa6b-25bac8c5afb7"
/>
<img width="321" height="142" alt="Screenshot 2026-05-19 at 2 54 50 PM"
src="https://github.com/user-attachments/assets/c7262044-1f33-4ae4-aada-4ef3ae6b118f"
/>
<img width="316" height="112" alt="Screenshot 2026-05-19 at 2 55 05 PM"
src="https://github.com/user-attachments/assets/fb2fd4cd-a5d2-454c-82cc-8cbae7ccc5aa"
/>


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

## **Pre-merge author checklist**

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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**
> Touches fee-disclaimer logic in the Bridge confirmation flow and adds
a new RewardsController API that calls `/vip/fees`, which could affect
pricing/UX if the quote fee fields or VIP lookup behave unexpectedly.
> 
> **Overview**
> **Bridge footer now surfaces VIP fee discounts.** A new
`useFeeDisclaimer` hook derives the disclaimer copy from quote
`baseBpsFee` vs `quoteBpsFee`, and `BridgeViewFooter` renders a
struck-through base fee plus discounted fee text when applicable.
> 
> **Adds a reusable VIP badge and controller support.** Introduces
`RewardsVipBadge` (and tests) that fetches and displays the account VIP
tier, exports `formatAccountToCaipAccountId` for CAIP formatting, and
adds `RewardsController.getVipTierForAccount` (with shared VIP fees
fetch/dedup refactor + action type wiring) to support the badge.
> 
> Also updates selectors/testIDs and i18n strings to support the new
disclaimer/badge and adjusts unit tests for discounted/undefined-fee
cases.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e1754ee. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…30458)

<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

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

## **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/GE-245

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

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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**
> Adjusts when and how carousel analytics events fire (display/select
naming and frequency), which can impact engagement metrics and
downstream dashboards. UI/navigation behavior is mostly unchanged but
click handler signature changes touch multiple components.
> 
> **Overview**
> Updates carousel analytics so **`Banner Display`** is emitted only
when a *non-empty* slide becomes the current card (instead of for every
visible slide), and standardizes event `name` to use `variableName` with
fallback to Contentful `id`.
> 
> Refactors slide click handling to pass the full `CarouselSlide` object
through `StackCard`/props and uses the derived analytics name for
**`Banner Select`** (including Solana-specific `action` properties), and
adds targeted unit tests covering the new display/select tracking
behavior.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
860e897. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**
The Repo contains an AGENTS.md file, but Claude Code doesn't pull that
in automatically - meaning that users of Claude Code miss a lot of that
context.

This adds a CLAUDE.md file that just pulls that AGENTS.md file in.

<!--
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: add claude.md file to wrap agents.md

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

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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: documentation-only change that doesn’t affect runtime code
or build behavior.
> 
> **Overview**
> Adds a new `CLAUDE.md` file that simply references `@AGENTS.md`,
making the existing agent instructions discoverable to Claude Code
without duplicating content.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
64bcd1a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

## **Description**

Re-enable the BIP-44 test after disabling device synchronization across
all Snaps tests.

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: only changes test execution by re-enabling an existing smoke
spec, with no production code impact.
> 
> **Overview**
> Re-enables the `BIP-44 Snap Tests` smoke suite by removing
`describe.skip`, so the BIP-44 Snap connection/signing/entropy tests run
again in CI.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
20422fb. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
Please submit this PR as a draft initially.

Do not mark it as "Ready for review" until this PR meets the canonical
Definition of Ready For Review in `docs/readme/ready-for-review.md`.

In short: the template must be materially complete (not just section
titles
present), all status checks must be currently passing, and the only
expected
follow-up commits must be reviewer-driven.
-->

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

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

<!--
Every checklist item must be consciously assessed before marking this PR
as
"Ready for review". A checked box means you deliberately considered that
responsibility, not that you literally performed every action listed.

Unchecked boxes are ambiguous: they are not an implicit "N/A" and they
are not
a silent "skip". See `docs/readme/ready-for-review.md` for the full
checklist
semantics.
-->

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **Pre-merge reviewer checklist**

<!--
Reviewer checklist items follow the same semantics as the author
checklist: an
unchecked box is ambiguous, a checked box means the reviewer consciously
assessed that responsibility. See `docs/readme/ready-for-review.md`.
-->

- [ ] 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 workflow-only change that expands allowed `environment`
inputs to include `prod`; impact is limited to CI behavior for Android
builds.
> 
> **Overview**
> Enables building Android in the `prod` track by adding `prod` to the
`environment` input contract for `.github/workflows/build-android.yml`
(both `workflow_call` and manual `workflow_dispatch`).
> 
> Updates the input validation case statement and the dispatch choice
options to accept `prod`, preventing builds from failing input
validation when targeting production.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0070f39. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Summary

Fixes the Perps withdraw confirmation flow so user-initiated
confirmation rejection, including iOS back-swipe, does not show the
retryable "withdrawal wasn't started" error toast.

## Root Cause

PR #30299 added an `addTransactionBatch` catch path that navigates back
and shows the withdrawal-start failure toast for every rejected batch
initialization. Back-swiping the confirmation rejects the approval with
a user-rejected error, so that normal cancellation path was being
treated as a real initialization failure.

## Changes

- Detect user-rejected errors before showing the withdrawal-start
failure toast.
- Keep the existing retry toast behavior for real `addTransactionBatch`
failures.
- Add regression coverage for user rejection so it does not call
`goBack`, build the retry toast, or show the toast.

Fixes #30485.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: scoped to Perps withdraw error handling and adds a
regression test; behavior only changes for user-rejected/cancel paths.
> 
> **Overview**
> Prevents Perps withdraw confirmation cancellations (user-rejected
errors) from being treated as `addTransactionBatch` failures: on
rejection, the hook now rethrows without calling `navigation.goBack()`
or showing the retryable “withdrawalStartFailed” toast.
> 
> Adds a regression test covering a
`providerErrors.userRejectedRequest()` rejection to ensure no back
navigation or toast is triggered, while keeping the existing retry-toast
behavior for real batch initialization failures.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c23f2c3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
….0 (#30503)

## **Description**

The TPSL (Take Profit / Stop Loss) page header was intermittently
positioned too high, overlapping the status bar / notch area. This
occurred both when creating a TPSL from the order screen and when
editing a TPSL for an open position.

**Root cause:** The screen relied on the shimmed `SafeAreaView` with
`edges={['top', 'bottom']}` to apply the top safe area inset. The shim
(`SafeAreaViewWithHookTopInset`) turns off the native top inset and
re-applies it via a hook-based `paddingTop`. With the `transparentModal`
presentation mode used by this screen, the hook-based top inset was
intermittently not applied, causing the header to render too high.

**Fix:** Follow the same proven pattern used by `PerpsOrderHeader` —
apply the top inset directly to the header view using
`useSafeAreaInsets()`, and only use `SafeAreaView` for the bottom edge.
This is deterministic and does not depend on the shimmed SafeAreaView
lifecycle for transparent modals.

## **Changelog**

CHANGELOG entry: Fixed TPSL page header overlapping the status bar area

## **Related issues**

Fixes: TAT-3213

## **Manual testing steps**

```gherkin
Feature: TPSL page header alignment

  Scenario: user creates a TPSL from the order screen
    Given the user is on the Perps order screen with an asset selected

    When user taps the TPSL / Auto close button
    Then the TPSL page opens with the header properly below the status bar / notch area

  Scenario: user edits a TPSL for an open position
    Given the user has an open Perps position with or without existing TP/SL values

    When user taps the Auto close / Edit TPSL button on the position
    Then the TPSL page opens with the header properly below the status bar / notch area

  Scenario: user repeatedly opens and closes the TPSL page
    Given the user is on the Perps order screen

    When user opens and closes the TPSL page multiple times
    Then the header is consistently aligned below the safe area every time
```

## **Screenshots/Recordings**

### **Before**

https://consensys.slack.com/archives/C092T3GPHQD/p1779353500168619

### **After**
<img width="1320" height="2868" alt="Simulator Screenshot - iPhone 17
Pro Max - 2026-05-21 at 11 40 56"
src="https://github.com/user-attachments/assets/71ea9e56-f7d6-43ea-8e22-d00c4ca78c5a"
/>


Header is consistently positioned below the safe area.

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

#### Performance checks (if applicable)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] I've tested with a power user scenario
- Use these [power-user
SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93)
to import wallets with many accounts and tokens
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- See [`trace()`](/app/util/trace.ts) for usage and
[`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274)
for an example

For performance guidelines and tooling, see the [Performance
Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers).

## **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 layout change limited to safe-area handling in the Perps
TPSL screen; no business logic or data flow changes.
> 
> **Overview**
> Fixes an intermittent layout issue where the Perps TPSL header could
render under the status bar/notch.
> 
> `PerpsTPSLView` now uses `useSafeAreaInsets()` to add top padding
directly on the header, and updates `SafeAreaView` to only apply the
bottom safe area (`edges={['bottom']}`) so top inset behavior is
deterministic in modal presentation.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f3dd563. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…le-core-ux files (#30473)

## **Description**

Part of the analytics cleanup workstream (#26686).

Renames `.addTraitsToUser()` → `.identify()` across all
mobile-core-ux-owned Settings components and network hooks, and migrates
`AccountActions.tsx` from the deprecated `useMetrics` to `useAnalytics`.
Also removes stale `MetaMetrics`/`MetaMetrics.events` mocks from
`AccountsMenu.test.tsx`.

Files touched:
- `BlockaidSettings.tsx`, `DisplayNFTMediaSettings`,
`BatchAccountBalanceSettings`, `AutoDetectTokensSettings`,
`AutoDetectNFTSettings` — rename `.addTraitsToUser` → `.identify`
- `useNetworkOperations.ts`, `useAddPopularNetwork.ts`,
`NetworkManager/index.tsx` — rename `.addTraitsToUser` → `.identify`
- `AccountActions.tsx` — migrate `useMetrics` → `useAnalytics`
- Corresponding test files updated to use `identify` override in
`createMockUseAnalyticsHook`
- `AccountsMenu.test.tsx` — remove stale
`jest.mock('../../../core/Analytics', ...)` and
`jest.mock('../../../core/Analytics/MetaMetrics.events', ...)`

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Closes: #26821
Refs: #26686

## **Manual testing steps**

N/A

## **Screenshots/Recordings**

### **Before**

N/A — no UI changes.

### **After**

N/A — no UI changes.

## **Pre-merge author checklist**

- [x] I've followed the [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))

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, test
affected areas)
- [ ] I confirm that this PR addresses what is claimed in the PR title
- [ ] I confirm that I've manually reviewed the changes if not manually
tested

Made with [Cursor](https://cursor.com)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk refactor limited to analytics hook usage and test mocks, with
minimal runtime behavior change beyond calling the new `identify` alias.
> 
> **Overview**
> Updates mobile-core-ux components and network-related hooks to use
`useAnalytics().identify` instead of the deprecated `addTraitsToUser`
for updating analytics user traits (settings toggles and network
add/remove flows).
> 
> Migrates `AccountActions` off `useMetrics` to `useAnalytics`, and
cleans up related unit tests by updating mocked hook return values and
removing stale `MetaMetrics`/`MetaMetrics.events` mocks in
`AccountsMenu.test.tsx`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
367f37e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@pull pull Bot locked and limited conversation to collaborators May 21, 2026
@pull pull Bot added the ⤵️ pull label May 21, 2026
@pull pull Bot merged commit b9d8a78 into Reality2byte:main May 21, 2026
3 of 13 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.