Skip to content

feat(perps): always on socket connection#27103

Merged
abretonc7s merged 10 commits into
mainfrom
feat/perps/alwayson
Mar 9, 2026
Merged

feat(perps): always on socket connection#27103
abretonc7s merged 10 commits into
mainfrom
feat/perps/alwayson

Conversation

@abretonc7s
Copy link
Copy Markdown
Contributor

@abretonc7s abretonc7s commented Mar 6, 2026

Description

Replaces per-section `PerpsConnectionProvider` connect/disconnect calls with a single top-level `PerpsAlwaysOnProvider` mounted at the wallet root.

Problem: Multiple `PerpsConnectionProvider` instances (Homepage, PerpsTabView, ActivityView, TrendingView, ExploreSearchScreen, UrlAutocomplete) each called `connect()`/`disconnect()`, creating reference-count races in `PerpsConnectionManager`. This caused intermittent bugs — positions not showing, 24h values missing — after long app backgrounding sessions.

Solution: `PerpsAlwaysOnProvider` is the sole caller of `PerpsConnectionManager.connect()` and `disconnect()`. `PerpsConnectionProvider` is now a pure React context source — it polls singleton state and exposes it via `PerpsConnectionContext`, but never calls connect/disconnect itself.

Key changes:

  • New `PerpsAlwaysOnProvider` — mounts once inside `ErrorBoundary` at `Wallet/index.tsx`, owns `AppState` listener, sole connect/disconnect owner
  • `PerpsConnectionProvider` stripped of all lifecycle management: removed `manageLifecycle` prop, `isVisible` prop, and the `usePerpsConnectionLifecycle` hook call
  • Deleted `usePerpsConnectionLifecycle` hook (its responsibilities moved to `PerpsAlwaysOnProvider`)
  • All section-level `PerpsConnectionProvider` instances kept as context sources (required by `useContext(PerpsConnectionContext)` consumers in `sections.config.tsx` et al.)
  • Restored `PerpsConnectionProvider` (context-only) in `ExploreSearchScreen`, `UrlAutocomplete`, and `TrendingView` `SectionWrapper` — these render `useSectionData` which reads `PerpsConnectionContext`
  • Removed visibility callback plumbing (`onVisibilityChange`, `perpsVisibilityCallback`) from `WalletTokensTabView` and `PerpsTabViewWithProvider`
  • Updated architecture docs

Changelog

CHANGELOG entry: null

Related issues

Fixes:

Manual testing steps

```gherkin
Feature: Perps always-on connection

Scenario: user backgrounds and foregrounds the app
Given perps feature flag is enabled
And user is on the Wallet screen

When user opens the Perps tab
Then positions and market data load correctly

When user backgrounds the app for 30+ seconds
And user foregrounds the app
Then Perps data reconnects and loads correctly

Scenario: user switches between tabs
Given perps feature flag is enabled

When user navigates between Wallet / Activity / Trending tabs
Then perps data remains connected without errors
And connectionRefCount stays at 1

Scenario: user switches accounts
Given perps feature flag is enabled

When user switches to a different account
Then Perps data refreshes for the new account

```

Screenshots/Recordings

Before

Multiple providers each managing lifecycle → reference-count races on background/foreground.

After

Single `PerpsAlwaysOnProvider` owns the lifecycle — `connectionRefCount` stays exactly 1. `PerpsConnectionProvider` is a context-only provider with no lifecycle responsibilities.

Pre-merge author checklist

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.

Note

Medium Risk
Changes Perps WebSocket connection ownership from many screen-scoped providers to a single wallet-root lifecycle manager, which can impact reconnect behavior, resource usage, and perps data availability across the app.

Overview
Centralizes Perps WebSocket lifecycle management by introducing PerpsAlwaysOnProvider (mounted at the Wallet root) as the only component that calls PerpsConnectionManager.connect()/disconnect(), driven by AppState background/foreground transitions with a delayed reconnect and error logging.

PerpsConnectionProvider is simplified to a context/state wrapper only: it no longer takes isVisible, no longer unmounts children when hidden, and removes the usePerpsConnectionLifecycle hook entirely (hook + tests deleted, barrel export removed). Call sites (Perps tab, Activity perps transactions tab, homepage Perps section, Explore search, UrlAutocomplete, Trending sections) are updated to stop passing visibility props and to use suppressErrorView where needed.

Tests and docs are updated to match the new always-on architecture, including new coverage for PerpsAlwaysOnProvider and adjusted PerpsConnectionProvider retry/error behavior.

Written by Cursor Bugbot for commit 1644485. This will update automatically on new commits. Configure here.

…ion lifecycle

Replace per-section PerpsConnectionProvider connect/disconnect calls with a
single top-level PerpsAlwaysOnProvider mounted at the wallet root.

Previously, multiple PerpsConnectionProvider instances (Homepage, PerpsTabView,
ActivityView, TrendingView, ExploreSearchScreen, UrlAutocomplete) each called
connect()/disconnect(), creating reference-count races that caused intermittent
bugs — positions not showing, 24h values missing — after long app backgrounding.

PerpsAlwaysOnProvider is the sole caller of PerpsConnectionManager.connect() and
disconnect(). It manages AppState transitions (background → disconnect, foreground
→ reconnect with ReconnectionDelayAndroidMs stabilisation) and cleans up on
unmount. connectionRefCount in PerpsConnectionManager stays exactly 1 throughout
the app lifetime.

All PerpsConnectionProvider instances now use manageLifecycle={false}, acting as
React context sources only. The visibility callback plumbing in PerpsTabView and
WalletTokensTabView is also removed as it is no longer needed.

Updated docs/perps to reflect the new architecture.
@abretonc7s abretonc7s requested a review from a team as a code owner March 6, 2026 02:04
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 6, 2026

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-perps Perps team label Mar 6, 2026
@github-actions github-actions Bot added the size-M label Mar 6, 2026
Comment thread app/components/Views/TrendingView/sections.config.tsx Outdated
Comment thread app/components/Views/Homepage/Sections/Perpetuals/PerpsSectionWithProvider.tsx Outdated
…y tests

- Add PerpsAlwaysOnProvider.test.tsx covering connect/disconnect lifecycle,
  AppState foreground/background handling, timer cancellation, and unmount cleanup
- Update Wallet/index.test.tsx to reflect removal of isVisible/onVisibilityChange
  props from PerpsTabView (lifecycle now owned by PerpsAlwaysOnProvider)

New code coverage: 81% on changed lines
Copy link
Copy Markdown
Contributor Author

@abretonc7s abretonc7s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Copy link
Copy Markdown
Contributor Author

@abretonc7s abretonc7s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@github-actions github-actions Bot added size-L and removed size-M labels Mar 6, 2026
Comment thread app/components/UI/Perps/providers/PerpsAlwaysOnProvider.test.tsx Outdated
Comment thread app/components/Views/Wallet/index.test.tsx Outdated
…apper

The PerpsConnectionProvider was removed from the perps SectionWrapper in
sections.config.tsx, but useSectionData still calls useContext(PerpsConnectionContext).
Without an ancestor provider the context is null, making error-state handling
dead code (connection failures silently show stale data instead of returning
empty/loading=false).

Restores PerpsConnectionProvider with manageLifecycle={false} so the context
is available while lifecycle remains owned by PerpsAlwaysOnProvider.

Also fixes low-severity bugbot findings in tests:
- Replace toBeTruthy() with toBeOnTheScreen() in PerpsAlwaysOnProvider.test.tsx
- Remove 'should' prefix from renamed test names in Wallet/index.test.tsx
Comment thread app/components/UI/UrlAutocomplete/index.tsx Outdated
…d UrlAutocomplete

useSectionsData (called by useExploreSearch → ExploreSearchResults) invokes
SECTIONS_CONFIG.perps.useSectionData which reads useContext(PerpsConnectionContext).
ExploreSearchScreen and UrlAutocomplete both render ExploreSearchResults but had
their PerpsConnectionProvider removed, leaving the context null and making
perps error-state handling dead code.

Restores PerpsConnectionProvider with suppressErrorView and manageLifecycle={false}
(context-only, lifecycle stays with PerpsAlwaysOnProvider).
Comment thread app/components/Views/Wallet/index.tsx Outdated
PerpsAlwaysOnProvider was wrapping ErrorBoundary, so a synchronous render
error in its useSelector call would bypass the wallet error boundary and
crash the app. Swapping the nesting order ensures any render error in
PerpsAlwaysOnProvider is caught by the wallet boundary, consistent with
the stated goal that a perps failure cannot block the rest of the app.
Comment thread app/components/UI/Perps/providers/PerpsConnectionProvider.tsx Outdated
…nProvider

Every callsite already passes manageLifecycle={false} explicitly. Defaulting
to true was a footgun — any future PerpsConnectionProvider added without the
prop would silently reintroduce the reference-count race conditions this PR
was created to fix.

Update two existing tests that test the manageLifecycle=true path to pass
the prop explicitly now that the default is false.
…onnectionLifecycle hook

With PerpsAlwaysOnProvider owning the entire connection lifecycle, the
manageLifecycle and isVisible props on PerpsConnectionProvider and the
underlying usePerpsConnectionLifecycle hook are dead code.

- Remove manageLifecycle and isVisible props from PerpsConnectionProvider
- Remove usePerpsConnectionLifecycle import and call from PerpsConnectionProvider
- Delete usePerpsConnectionLifecycle hook and its test file
- Remove manageLifecycle={false} from all callsites (no longer needed)
- Update PerpsConnectionProvider tests to remove lifecycle mock and
  rewrite lifecycle-dependent tests to use polling-based state instead
@github-actions github-actions Bot added size-XL and removed size-L labels Mar 6, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment thread TASK_PERPSALWAYSON.md Outdated
Comment thread app/components/UI/Perps/providers/PerpsAlwaysOnProvider.tsx
…overage

- Mock AppState.currentState as 'active' in beforeEach so lastAppState
  initializes correctly in tests (Jest has null, real app has 'active')
- Add iOS active→inactive→background test: verifies only one disconnect
  fires (the inactive transition), not two
- Add notification center pull-down test: verifies active→inactive→active
  disconnects once then reconnects once
- Remove accidentally committed TASK_PERPSALWAYSON.md
@abretonc7s abretonc7s changed the title feat(perps): introduce PerpsAlwaysOnProvider for single-owner connection lifecycle feat(perps): always on socket connection Mar 6, 2026
Comment on lines +142 to +154
it('calls disconnect when app goes to background', () => {
render(
<PerpsAlwaysOnProvider>
<Text>child</Text>
</PerpsAlwaysOnProvider>,
);

act(() => {
mockAppStateListener?.('background');
});

expect(mockDisconnect).toHaveBeenCalledTimes(1);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it disconnect immediately? or do we still have a graze period

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it calls the disconnect but then htere is grace period until it actually disconnect when it goes ot background

expect(mockDisconnect).toHaveBeenCalledTimes(1);
expect(mockSubscriptionRemove).toHaveBeenCalledTimes(1);
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a case for when the app locks/unlocks?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this go via the same path as backgrounded when locked

Comment thread app/components/UI/Perps/providers/PerpsAlwaysOnProvider.tsx
Comment thread app/components/UI/Perps/providers/PerpsAlwaysOnProvider.tsx Outdated
{/* Only mount providers when tab is active to prevent polling when hidden */}
{isPerpsTabActive ? (
<PerpsConnectionProvider isVisible={isPerpsTabActive}>
<PerpsConnectionProvider>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if thi sis going to be always on, isn't it better to mount it at the app level?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main issue is to access redux and load when wallet unlocks. We could potentially go a bit higher but I don't think it would make significant changes, also better to happen after onboarding etc.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 6, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePreps
  • Risk Level: medium
  • AI Confidence: 85%
click to see 🤖 AI reasoning details

E2E Test Selection:
This PR refactors the Perps WebSocket connection lifecycle management by introducing a new PerpsAlwaysOnProvider at the wallet root level and removing the per-section lifecycle management from PerpsConnectionProvider.

Key changes:

  1. New PerpsAlwaysOnProvider mounted at Wallet/index.tsx - manages all Perps WebSocket connections centrally
  2. Deleted usePerpsConnectionLifecycle hook - lifecycle management moved to the new provider
  3. Simplified PerpsConnectionProvider - now only provides React context, no lifecycle management
  4. Updated multiple consumers: PerpsTabView, PerpsSectionWithProvider, ActivityView, UrlAutocomplete

The changes affect:

  • SmokePerps: Direct Perps functionality - Add Funds flow, balance verification, Perps transactions. The connection lifecycle changes could affect how Perps connects/disconnects during user flows.
  • SmokeWalletPlatform: The Wallet main view now wraps content with PerpsAlwaysOnProvider. Trending tab contains Perps section which is affected. Transaction history and wallet lifecycle are impacted.
  • SmokeConfirmations: Per tag guidance, when selecting SmokePerps, also select SmokeConfirmations since Add Funds deposits are on-chain transactions.

The risk is medium because:

  • This is an architectural refactor, not a feature change
  • The changes are well-tested with new unit tests for PerpsAlwaysOnProvider
  • The pattern simplifies connection management (single lifecycle owner vs multiple)
  • However, it touches the core Wallet component and multiple Perps integration points

Performance Test Selection:
The changes affect Perps WebSocket connection lifecycle management. The new PerpsAlwaysOnProvider is mounted at the wallet root and manages connection/disconnection based on app state. This could impact Perps market loading performance, position management, and add funds flow timing. The @PerformancePreps tag should verify that the new always-on connection architecture doesn't negatively impact Perps performance metrics.

View GitHub Actions results

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 6, 2026

⚠️ E2E Fixture Validation — Structural changes detected

Category Count
New keys 68
Missing keys 11
Type mismatches 0
Value mismatches 9 (informational)

The committed fixture schema is out of date. To update, comment:

@metamaskbot update-mobile-fixture

View full details | Download diff report

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 6, 2026


- Manage actual connection lifecycle (delegates to Manager)
- Know about WebSockets or providers
- Manage connection lifecycle when `manageLifecycle={false}` (the default for all section-level instances)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the manageLifecycle prop defined? Couldn't find it in the code

Copy link
Copy Markdown
Contributor

@aganglada aganglada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested it locally and LGTM

@abretonc7s abretonc7s added this pull request to the merge queue Mar 9, 2026
Merged via the queue into main with commit 52520eb Mar 9, 2026
100 of 101 checks passed
@abretonc7s abretonc7s deleted the feat/perps/alwayson branch March 9, 2026 00:35
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 9, 2026
@metamaskbot metamaskbot added the release-7.70.0 Issue or pull request that will be included in release 7.70.0 label Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.70.0 Issue or pull request that will be included in release 7.70.0 size-XL team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants