Skip to content

[pull] main from MetaMask:main#524

Merged
pull[bot] merged 1 commit into
Reality2byte:mainfrom
MetaMask:main
Feb 15, 2026
Merged

[pull] main from MetaMask:main#524
pull[bot] merged 1 commit into
Reality2byte:mainfrom
MetaMask:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Feb 15, 2026

See Commits and Changes for more details.


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

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

…s rendering (#26061)

## **Description**

Eliminates the empty-screen flash on cold load of the Perps tab by
preloading data at app startup and seeding hooks from the controller
cache.

**What changed:**

1. **Background preloading** — `startMarketDataPreload()` is called from
the Wallet tab to fetch market data + user data (positions, orders,
account state) via lightweight standalone REST calls, caching results in
the controller's transient state. A 5-minute periodic refresh keeps the
cache warm.

2. **Cache-seeded hook initialization** — Stream hooks
(`usePerpsLivePositions`, `usePerpsLiveOrders`, `usePerpsLiveAccount`)
and `usePerpsMarkets` use lazy `useState` initializers that read from
the controller cache. If preloaded data exists, the first render shows
real data instead of a skeleton.

3. **Unified cache utility** — `hasPreloadedUserData(field)` and
`getPreloadedUserData(field)` provide a single place for cache-existence
checks across all hooks (positions, orders, account state, and market
data). No client-side TTL — the controller's 5-minute preload cycle
manages freshness.

4. **Stream channel initialization guards** — Channels defer WebSocket
subscriptions until the controller is initialized (retrying every 200ms,
max 150 attempts). This prevents doomed subscriptions that would
permanently block data delivery.

5. **Progressive loading** — `PerpsConnectionProvider` no longer blocks
with a full-screen "Connecting to Perps" skeleton. Children render
immediately with cache-seeded data; per-row skeletons appear only when
no cached data is available (e.g., first app install) and resolve once
WebSocket data arrives.

6. **Sentry traces for preload timing** — New `MarketDataPreload` and
`UserDataPreload` traces measure end-to-end preload duration, reporting
to Sentry for cold-start performance monitoring.

7. **Hardened subscribe methods** — All 8 `subscribeTo*()` methods now
use `getActiveProviderOrNull()` instead of `getActiveProvider()`,
returning early when no provider is available. This prevents
`CLIENT_NOT_INITIALIZED` throws during cold-start when preload fires
before WebSocket initialization.

8. **Skip user data preload when WS connected** — `preloadUserData()`
bails out early if the WebSocket is already connected, avoiding
redundant REST fetches that would be immediately overwritten by live
stream data.

## **Changelog**

CHANGELOG entry: Preloaded Perps market and user data at startup for
instant rendering

## **Related issues**

Fixes:

## **Manual testing steps**

```gherkin
Feature: Perps cold load performance

  Scenario: User opens Perps tab for the first time
    Given the user is on the Wallet tab
    And the app has been open for at least a few seconds (preload has run)

    When user navigates to the Perps tab
    Then markets list renders immediately with real data (no skeleton flash)
    And positions/orders/account state render from cache
    And live WebSocket data replaces cache within ~1-2s

  Scenario: User switches accounts while on Perps tab
    Given the user is on the Perps tab with data displayed

    When user switches to a different account
    Then cached user data is cleared
    And new account's data is preloaded
    And UI updates to show new account's positions/orders

  Scenario: Preloaded data older than 60s still renders
    Given the user opened the app 3 minutes ago (preload ran at startup)
    And the 5-minute refresh has not yet triggered

    When user navigates to the Perps tab
    Then markets and positions render from cache (no skeleton)
    And WebSocket data replaces cache shortly after
```

## **Screenshots/Recordings**

### **Before**

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


https://github.com/user-attachments/assets/d3edc121-d9b2-48a7-bd4e-ebeaf39efb0d


### **After**


Uploading after_ios16.mp4…



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

## **Performance Results**

### UI Render Speed (cold start — time to show market data)

| Scenario | `main` | This PR | Gain |
|---|---|---|---|
| iPhone Tab View | ~1.7s | **0ms** (cache) | **1.7s faster** |
| iPhone Home View | ~3.2s | **0ms** (cache) | **3.2s faster** |
| Android Home View | ~2.3–2.6s | **1ms** (cache) | **2.3–2.6s faster**
|

Cache decouples UI rendering from the WebSocket pipeline — users see
real data instantly, WS refreshes in the background.

### Sentry Production Baselines (Android p50, last 3 releases)

| Metric | v7.61.6 | v7.62.2 | v7.63.1 |
|---|---|---|---|
| Perps Tab View | 818ms | 676ms | 538ms |
| WS Connection | 5320ms | 7876ms | 9023ms |

Production WS connections are 5–9x slower than local (real network
latency) — cache impact will be **even larger** in production since it
eliminates the wait entirely.

## **Performance measurements**

All traces are instrumented and report to Sentry automatically.

| Sentry trace name | Where it fires | What it measures | Expected
improvement |
| ------------------------ |
------------------------------------------------------------------ |
---------------------------------------------------------- |
---------------------------------------------------- |
| `PerpsTabView` | `PerpsTabView.tsx:70-77` | Tab mount → positions
loaded + account balance available | Near-instant (cache-seeded) vs ~1-2
s (cold WS wait) |
| `PerpsMarketListView` | `PerpsHomeView.tsx:176-179`,
`PerpsMarketListView.tsx:212-215` | Render → markets list populated |
Near-instant vs ~1-2 s |
| `MarketDataPreload` | `PerpsController.ts` —
`startMarketDataPreload()` | REST fetch of all market metadata during
background preload | New trace (baseline measurement) |
| `UserDataPreload` | `PerpsController.ts` — `preloadUserData()` | REST
fetch of positions, orders, account state | New trace (baseline
measurement) |

### How to compare

**Sentry dashboard:**
1. Open the Sentry Performance dashboard for the project.
2. Filter by transaction name `PerpsTabView` or `PerpsMarketListView`.
3. Compare p50 / p95 durations between this branch and `main` (or the
previous release).

**Dev build (local):**
```bash
adb logcat | grep PERPSMARK_SENTRY
```
Each completed trace logs `PERPSMARK_SENTRY PerpsScreen: <traceName>
completed` with duration context.

## **Pre-merge author checklist**

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

## **Pre-merge reviewer checklist**

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


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches Perps data-fetching and WebSocket subscription timing across
multiple hooks/channels; regressions could cause stale/incorrect cache
usage or missed subscriptions, though changes are UI/performance-focused
and not security-sensitive.
> 
> **Overview**
> Improves Perps cold-start UX by **preloading market + user data into
`PerpsController.state`** and seeding initial renders from that cache
(markets, positions, orders, account state) so screens can paint real
data immediately instead of showing skeletons.
> 
> Adds a shared cache utility (`hasCachedPerpsData.ts`) with *TTL +
account-matching checks* for user-scoped data, updates stream hooks
(`usePerpsLivePositions`/`Orders`/`Account`) and `usePerpsMarkets` to
use lazy `useState` initializers from the controller cache, and
refactors market filtering/sorting into `filterAndSortMarkets`.
> 
> Hardens streaming/connect behavior by deferring channel connections
until initialization (`ensureReady`/`deferConnect` + retry limits,
including candles), using controller-level preloaded market data in
`MarketDataChannel`, removing the full-screen Perps connection skeleton
(children render immediately; unmount only when `isVisible === false`),
guarding REST fills fetch in `usePerpsHomeData` by connection readiness,
and starting/stopping background `startMarketDataPreload()` from the
Wallet tab when Perps is enabled.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
bc73227. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com>
Co-authored-by: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com>
Co-authored-by: Nico MASSART <NicolasMassart@users.noreply.github.com>
Co-authored-by: George Weiler <georgejweiler@gmail.com>
Co-authored-by: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com>
Co-authored-by: Aslau Mario-Daniel <marioaslau@gmail.com>
@pull pull Bot locked and limited conversation to collaborators Feb 15, 2026
@pull pull Bot added the ⤵️ pull label Feb 15, 2026
@pull pull Bot merged commit 3ab7d90 into Reality2byte:main Feb 15, 2026
3 of 37 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant