Skip to content

[pull] main from MetaMask:main#733

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

[pull] main from MetaMask:main#733
pull[bot] merged 2 commits into
Reality2byte:mainfrom
MetaMask:main

Conversation

@pull

@pull pull Bot commented May 6, 2026

Copy link
Copy Markdown

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 : )

tommasini and others added 2 commits May 6, 2026 00:16
…eystore key invalidation (#29752)

---

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

**Reason for the change:**

On Android, when a user changes their biometric enrollment (or due to an
Android 16 Keystore behavioral change), the Keystore key backing the
vault backup entry can be permanently invalidated. In that state,
`getInternetCredentials(VAULT_BACKUP_KEY)` throws an exception rather
than returning `false`/`undefined`. Because that call sat inside the
outer `try/catch` in `backupVault`, the thrown error was caught at the
top-level catch, which logged "Vault backup failed" to Sentry and
returned `{ success: false }` — even though the underlying vault was
fine and a fresh backup could have been written successfully.

**Solution:**

Wrap the initial `getInternetCredentials` read in its own inner
`try/catch`. If the read throws (key invalidated), we:
1. Log a non-error warning via `Logger.log` (no Sentry noise)
2. Clear any corrupted keychain entries with `clearAllVaultBackups()`
3. Fall through to write a fresh backup normally

This makes `backupVault` resilient to stale Android Keystore state and
prevents a false "Vault backup failed" from ever surfacing when the real
backup operation can succeed.

## **Changelog**

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry: Fixed an issue on Android where vault backup could
silently fail after a biometric enrollment change or Keystore key
invalidation

## **Related issues**

No issue: bug surfaced via Sentry report (`extra.message: "Vault backup
failed"`) — Android Keystore key invalidation causing
`getInternetCredentials` to throw inside `backupVault`

## **Manual testing steps**

```gherkin
Feature: Vault backup resilience on Android

  Scenario: Vault backup succeeds even when existing backup read throws due to Keystore invalidation
    Given the user has a MetaMask wallet set up on Android
    And a vault backup already exists in the keychain
    And the Android Keystore key has been invalidated (e.g. by adding/removing biometric enrollment)

    When the vault backup routine is triggered (e.g. on wallet unlock or state change)
    Then the backup should complete successfully
    And no "Vault backup failed" error should appear in Sentry
    And the user should not be prompted with an error or crash
```

> Note: This fix is primarily defensive for an Android Keystore edge
case. Manual reproduction requires a device where biometrics have been
changed after the backup was created, or an Android 16 device exhibiting
the Keystore regression. Unit test coverage added for the throwing path.

## **Screenshots/Recordings**

### **Before**

N/A

### **After**

N/A

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

- [x] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [x] 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
- [x] 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.

<!-- Generated with the help of the pr-description AI skill -->

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk, localized change that only alters error handling around
reading existing keychain credentials and adds a unit test for the
throwing path. Main risk is potentially masking unexpected read failures
by proceeding to overwrite the backup.
> 
> **Overview**
> Makes `backupVault` resilient to Android Keystore/keychain read
exceptions by wrapping the initial
`getInternetCredentials(VAULT_BACKUP_KEY)` call in an inner try/catch
and proceeding as if no existing backup was found (while logging via
`Logger.log`).
> 
> Adds a unit test ensuring vault backup still reports success when the
existing-backup read throws (simulating Android Keystore key
invalidation).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6a6edf9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…etection, and collapse animation (#29684)

## **Description**

Adds new `TabsIcon` components to `components-temp` and new icons to the
component library as part of the hub page discovery tabs A/B test.
Extracted into its own PR so it can be reviewed and merged independently
before the feature PR lands.

---

### `TabsIcon` Component Family

**`TabsIconTab`**
Tab item that renders an icon above a label. Icon and label have
active/disabled color states. Supports a `fillWidth` prop for
equal-width layouts and animated icon collapse via
`TabsIconAnimationContext`.

**`TabsIconBar`**
Tab bar built for icon+label tabs. Features:
- Automatic overflow detection — switches between a fixed layout and a
horizontally scrollable `ScrollView`
- Animated sliding underline indicator
- `fillWidth` mode for equal-width tabs
- Height collapse animation for hiding the bar on scroll

**`TabsIconList`**
Lazy-mounting tab list that manages active state, swipe gestures, and
`InteractionManager`-based content loading. Per-tab `keepMounted` prop
controls whether inactive tab content stays mounted or is unmounted when
the tab loses focus.

**`TabsIconAnimationContext`**
React context that carries an `Animated.Value` (`0` = icons expanded,
`1` = collapsed) from a parent provider down to `TabsIconTab` without
prop drilling.

---

### Icons

Adds three new SVG icons and registers them in `Icon.assets.ts` /
`Icon.types.ts`:

| Icon | `IconName` |
|---|---|
| Portfolio | `IconName.Portfolio` |
| Predict | `IconName.Predict` |
| Candlestick | `IconName.Candlestick` |

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/TMCU-591

## **Manual testing steps**

```gherkin
Feature: Tabs component library

  Scenario: user navigates between tabs
    Given the app is open on any screen using TabsList
    When user taps each tab
    Then the animated underline slides to the active tab
    And the correct tab content is displayed

  Scenario: tabs overflow to scrollable mode
    Given a TabsBar with more tabs than fit the container width
    When the screen renders
    Then the tab bar becomes horizontally scrollable
    And the active tab is scrolled into view automatically

  Scenario: new icons render correctly
    Given any view that uses the Icon component
    When IconName.Portfolio or IconName.Predict is passed
    Then the correct SVG icon is displayed
```

## **Screenshots/Recordings**

Take from the feature branch using these components
[here](#29193)

### iOS

Dark Mode


https://github.com/user-attachments/assets/7df6a46d-b3b3-44cc-a697-b796581dd759

Light Mode (No Gradient)


https://github.com/user-attachments/assets/97b1f901-6092-42f9-926c-e8e6785c6f4e

### Android

Dark Mode


https://github.com/user-attachments/assets/4232f3fc-a47c-4516-93c2-104e4a3fb5cb

Light Mode (No Gradient)


https://github.com/user-attachments/assets/3f11dd8f-696f-438e-9867-1b08dc69200d

### **Before**

`~`

### **After**

`~`

## **Pre-merge author checklist**

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

#### Performance checks (if applicable)

- [x] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [x] 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
- [x] 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**

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


[TMCU-591]:
https://consensyssoftware.atlassian.net/browse/TMCU-591?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ





<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds new tab UI components plus shared layout/list hooks involving
animations, gesture handling, and InteractionManager-based lazy loading;
regressions would primarily impact navigation/UX in screens adopting
these new components. Also extends the global `IconName` enum/assets,
which can affect consumers if mis-registered.
> 
> **Overview**
> Adds a new `TabsIcon` component family under `components-temp`:
> 
> `TabsIconTab` renders icon+label tabs with active/disabled states and
optional icon collapse via `TabIconAnimationContext`, `TabsIconBar`
manages underline animation plus *auto scroll overflow detection* and
optional height collapse, and `TabsIconList` composes the bar with
lazy-mounted tab content, disabled-tab handling, swipe gestures, and a
small imperative ref API.
> 
> Introduces reusable hooks `useTabsBarLayout` (measures tab/container
layouts, toggles scroll mode, drives underline animations, and
resets/cleans up on layout/tab changes) and `useTabsList` (active index
normalization, InteractionManager-backed lazy loading with timeout
fallback, key-preserving tab updates, and swipe-to-switch).
> 
> Extends the icon library by registering new SVGs and `IconName`
entries (`Group`, `Portfolio`, `Predictions`) and updates the
`candlestick.svg` asset. Comprehensive unit tests are added for the new
components and hooks.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ffa57ba. 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 6, 2026
@pull pull Bot added the ⤵️ pull label May 6, 2026
@pull pull Bot merged commit 7da7450 into Reality2byte:main May 6, 2026
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.

2 participants