[pull] main from MetaMask:main#776
Merged
Merged
Conversation
## **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 | |---|---| |  |  | | 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% | |---|---| |  |  | | 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 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )