Skip to content

fix: refresh unified assets balances from real-time websocket pushes#31091

Merged
xavier-brochard merged 2 commits into
mainfrom
fix/assets-realtime-balance-refresh
Jun 4, 2026
Merged

fix: refresh unified assets balances from real-time websocket pushes#31091
xavier-brochard merged 2 commits into
mainfrom
fix/assets-realtime-balance-refresh

Conversation

@xavier-brochard

@xavier-brochard xavier-brochard commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description

When the assetsUnifyState feature flag is enabled, the UI reads token balances from AssetsController.assetsBalance instead of TokenBalancesController. After a swap (e.g. QuickBuy), balances did not update in the simulator until the 30s poll, even though the real-time websocket push arrived almost immediately.

Root cause: both AccountActivityService and the controller's internal BackendWebsocketDataSource open a separate websocket subscription to the same account-activity channel. The backend routes each notification to a single subscriptionId, so only one subscriber receives the push. AccountActivityService wins that race, starving the data source's subscription — so AssetsController.assetsBalance is never merged with the fresh balance until the periodic poll.

Solution: bridge the AccountActivityService:balanceUpdated event (which reliably receives the push) into the controller's own public handleAssetsUpdate merge entrypoint. The push payload is transformed into the merge shape by a pure, unit-tested helper (buildAssetsBalanceUpdateFromPush) that mirrors the data source's own raw-amount → human-readable-decimal conversion, skipping malformed rows so a single bad entry can't drop the whole push. The bridge is gated behind assetsUnifyState, and the 30s poll remains as a fallback.

Why it wasn't noticeable before (and surfaced via QuickBuy)

This bug affects every balance update under assetsUnifyState, not just QuickBuy — which is why the fix lives at the controller-bridge layer rather than in QuickBuy. It was surfaced by the QuickBuy because of how each flow ends:

  • Regular Swaps (Bridge): on completion the confirm flow navigates to Routes.TRANSACTIONS_VIEW (useBridgeConfirm). Navigating away and back re-mounts the wallet token list, which under unify force-refreshes AssetsController.getAssets({ forceUpdate: true }) (useRefreshTokens). That forced read masked the starved websocket path, so balances looked instant.
  • QuickBuy: by design it stays on TraderPositionView and only shows lifecycle toasts — no navigation, no re-mount, no forced refresh. With nothing to mask it, the starved real-time path was exposed and balances only updated on the 30s poll (consistent with "pull-to-refresh fixes it" and "waiting eventually updates").

Both flows submit through the same BridgeStatusController.submitTx; the difference is purely the post-submit navigation side effect. This fix repairs the real-time path itself, so balances update live for all flows without relying on navigation or pull-to-refresh.

Changelog

CHANGELOG entry: Fixed token balances not updating immediately after a swap when the unified assets state is enabled.

Related issues

Fixes:

Manual testing steps

```gherkin
Feature: Real-time balance refresh after swap

Scenario: user completes a swap with unified assets state enabled
Given the assetsUnifyState feature flag is enabled
And the user is on a screen displaying their token balances

When the user completes a swap (e.g. via QuickBuy)
Then the source and destination token balances update within a couple of seconds
And the user does not need to pull-to-refresh or wait for the 30s poll

```

Screenshots/Recordings

Before

After

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.

When `assetsUnifyState` is enabled the UI reads balances from
`AssetsController.assetsBalance`. The controller's internal
`BackendWebsocketDataSource` and `AccountActivityService` both subscribe to
the same account-activity channel, but the backend routes each notification
to a single subscriptionId, so the data source's subscription is starved and
`assetsBalance` only refreshes on the 30s poll (e.g. after a QuickBuy swap).

Bridge the `AccountActivityService:balanceUpdated` event, which reliably
receives the push, into the controller's public `handleAssetsUpdate` merge
entrypoint so balances update in real time. The payload-to-merge
transformation is extracted into a pure, unit-tested helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

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.

@github-actions github-actions Bot added the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label Jun 4, 2026
@mm-token-exchange-service mm-token-exchange-service Bot added the team-social-ai Social & AI team label Jun 4, 2026
@github-actions github-actions Bot added the size-M label Jun 4, 2026
@xavier-brochard xavier-brochard marked this pull request as ready for review June 4, 2026 16:31
@xavier-brochard xavier-brochard requested a review from a team as a code owner June 4, 2026 16:31

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 3ce3557. Configure here.

Comment thread app/core/Engine/utils/buildAssetsBalanceUpdateFromPush.ts Outdated
@github-actions github-actions Bot added the risk:high AI analysis: high risk label Jun 4, 2026
`buildAssetsBalanceUpdateFromPush` only skipped `undefined` decimals/amounts,
so runtime payloads with `null` (or NaN/negative/fractional) decimals or
non-string amounts reached `fromTokenMinimalUnitString` and could throw.
A throw in the loop aborted the entire push, dropping otherwise-valid balance
rows until the next poll.

Tighten the per-row guards and isolate each conversion in a try/catch so a
single malformed row is skipped while the remaining valid balances still merge.

Co-authored-by: Cursor <cursoragent@cursor.com>
@xavier-brochard xavier-brochard removed the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeIdentity, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps
  • Selected Performance tags: @PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (global-infrastructure-change): Global infrastructure changed: app/core/Engine/Engine.ts. Running all tests.

Performance Test Selection:
Hard rule (global-infrastructure-change): Global infrastructure changed: app/core/Engine/Engine.ts. Running all tests.

View GitHub Actions results

@Cal-L Cal-L left a comment

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.

LGTM

@xavier-brochard xavier-brochard enabled auto-merge June 4, 2026 16:57
@xavier-brochard xavier-brochard added this pull request to the merge queue Jun 4, 2026
Merged via the queue into main with commit 4606543 Jun 4, 2026
228 of 230 checks passed
@xavier-brochard xavier-brochard deleted the fix/assets-realtime-balance-refresh branch June 4, 2026 17:38
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 4, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.81.0 Issue or pull request that will be included in release 7.81.0 label Jun 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.81.0 Issue or pull request that will be included in release 7.81.0 risk:high AI analysis: high risk size-M team-social-ai Social & AI team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants