Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/assets-controllers/src/TokenBalancesController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4328,6 +4328,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${tokenAddress}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex (1 USDC with 6 decimals)
Expand Down Expand Up @@ -4378,6 +4379,7 @@ describe('TokenBalancesController', () => {
type: 'eip155:1/slip44:60',
unit: 'ETH',
fungible: true,
decimals: 18,
},
postBalance: {
amount: '0xde0b6b3a7640000', // 1 ETH in wei
Expand Down Expand Up @@ -4431,6 +4433,7 @@ describe('TokenBalancesController', () => {
type: 'eip155:1/slip44:60',
unit: 'ETH',
fungible: true,
decimals: 18,
},
postBalance: {
amount: '0',
Expand Down Expand Up @@ -4468,6 +4471,7 @@ describe('TokenBalancesController', () => {
type: 'eip155:1/unknown:0x123',
unit: 'UNKNOWN',
fungible: true,
decimals: 18,
},
postBalance: {
amount: '1000',
Expand Down Expand Up @@ -4650,6 +4654,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${token1}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand All @@ -4661,6 +4666,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${token2}`,
unit: 'USDT',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0x1e8480', // 2000000 in hex
Expand Down Expand Up @@ -4706,6 +4712,7 @@ describe('TokenBalancesController', () => {
type: 'eip155:1/erc20:invalid-address', // Not a valid hex address
unit: 'INVALID',
fungible: true,
decimals: 18,
},
postBalance: { amount: '1000000' },
transfers: [],
Expand Down Expand Up @@ -4780,6 +4787,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${newTokenAddress}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand Down Expand Up @@ -4853,6 +4861,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${trackedTokenAddress}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand Down Expand Up @@ -4916,6 +4925,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${ignoredTokenAddress}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand Down Expand Up @@ -4973,6 +4983,7 @@ describe('TokenBalancesController', () => {
type: 'eip155:1/slip44:60',
unit: 'ETH',
fungible: true,
decimals: 18,
},
postBalance: {
amount: '0xde0b6b3a7640000', // 1 ETH in wei
Expand Down Expand Up @@ -5039,6 +5050,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${newTokenAddress}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand Down Expand Up @@ -5109,6 +5121,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${trackedToken}`,
unit: 'USDC',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0xf4240', // 1000000 in hex
Expand All @@ -5120,6 +5133,7 @@ describe('TokenBalancesController', () => {
type: `eip155:1/erc20:${untrackedToken}`,
unit: 'USDT',
fungible: true,
decimals: 6,
},
postBalance: {
amount: '0x1e8480', // 2000000 in hex
Expand Down
29 changes: 29 additions & 0 deletions packages/core-backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **BREAKING**: `BackendWebSocketService` - Simplified connection management and added KeyringController event integration ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added `KeyringController:lock` and `KeyringController:unlock` event subscriptions to automatically manage WebSocket connections based on wallet lock state
- Renamed internal method `setupAuthentication()` to `subscribeEvents()` to reflect broader event handling responsibilities
- Simplified reconnection logic: auto-reconnect on any unexpected disconnect, stay disconnected on manual disconnects (tracked via `#manualDisconnect` flag)
- Updated `connect()` to reset manual disconnect flag, allowing reconnection after previous manual disconnects
- Updated `disconnect()` to set manual disconnect flag, preventing automatic reconnection
- Improved error handling in `connect()` to properly rethrow errors to callers
- **BREAKING**: `AccountActivityService` - Replaced API-based chain support detection with system notification-driven chain tracking ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added internal `#chainsUp` Set to track chains reported as 'up' via system notifications
- Updated system notification handler to dynamically track chain status (add to set when 'up', remove when 'down')
- Updated WebSocket state change handler to flush all tracked chains as 'down' on disconnect/error (instead of using hardcoded list)
- Chain status is now entirely driven by backend system notifications rather than proactive API calls
- **BREAKING**: Updated `Transaction` type definition - renamed `hash` field to `id` for consistency with backend API ([#6819](https://github.com/MetaMask/core/pull/6819))
- **BREAKING**: Updated `Asset` type definition - added required `decimals` field for proper token amount formatting ([#6819](https://github.com/MetaMask/core/pull/6819))
- `BackendWebSocketService` - Added optional `traceFn` parameter to constructor for performance tracing integration (e.g., Sentry)
- Enables tracing of WebSocket operations including connect, disconnect methods
- Trace function receives operation metadata and callback to wrap for performance monitoring
- Updated documentation (README.md) to reflect new connection management model and chain tracking behavior ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added "WebSocket Connection Management" section explaining connection requirements and behavior
- Updated sequence diagram to show system notification-driven chain status flow
- Updated key flow characteristics to reflect internal chain tracking mechanism
Comment on lines +10 to +32
Copy link
Contributor

Choose a reason for hiding this comment

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

A few notes:

  • Should we be more specific about which changes are breaking? For instance, simplifying the reconnection logic in itself doesn't seem to be breaking, at least to me, as is updating connect, disconnect, etc. (And to be clear, if a change is "breaking" it means that there is some change that needs to be made on the client side to adjust to the change or else an error at compile-time or runtime will occur.)
  • For the breaking changes, can we explain what needs to change in consuming code to adapt to the changes if it is not clear?
  • Is there anything that we don't need to document, e.g. changes to internal methods or developer-only changes?
  • For changes that extend the API should we add them under Added?
  • Can we ensure that all entries have a PR next to them?
  • Can we use present tense instead of past tense for entries?

Putting this together, what if we do something like this:

Suggested change
### Changed
- **BREAKING**: `BackendWebSocketService` - Simplified connection management and added KeyringController event integration ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added `KeyringController:lock` and `KeyringController:unlock` event subscriptions to automatically manage WebSocket connections based on wallet lock state
- Renamed internal method `setupAuthentication()` to `subscribeEvents()` to reflect broader event handling responsibilities
- Simplified reconnection logic: auto-reconnect on any unexpected disconnect, stay disconnected on manual disconnects (tracked via `#manualDisconnect` flag)
- Updated `connect()` to reset manual disconnect flag, allowing reconnection after previous manual disconnects
- Updated `disconnect()` to set manual disconnect flag, preventing automatic reconnection
- Improved error handling in `connect()` to properly rethrow errors to callers
- **BREAKING**: `AccountActivityService` - Replaced API-based chain support detection with system notification-driven chain tracking ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added internal `#chainsUp` Set to track chains reported as 'up' via system notifications
- Updated system notification handler to dynamically track chain status (add to set when 'up', remove when 'down')
- Updated WebSocket state change handler to flush all tracked chains as 'down' on disconnect/error (instead of using hardcoded list)
- Chain status is now entirely driven by backend system notifications rather than proactive API calls
- **BREAKING**: Updated `Transaction` type definition - renamed `hash` field to `id` for consistency with backend API ([#6819](https://github.com/MetaMask/core/pull/6819))
- **BREAKING**: Updated `Asset` type definition - added required `decimals` field for proper token amount formatting ([#6819](https://github.com/MetaMask/core/pull/6819))
- `BackendWebSocketService` - Added optional `traceFn` parameter to constructor for performance tracing integration (e.g., Sentry)
- Enables tracing of WebSocket operations including connect, disconnect methods
- Trace function receives operation metadata and callback to wrap for performance monitoring
- Updated documentation (README.md) to reflect new connection management model and chain tracking behavior ([#6819](https://github.com/MetaMask/core/pull/6819))
- Added "WebSocket Connection Management" section explaining connection requirements and behavior
- Updated sequence diagram to show system notification-driven chain status flow
- Updated key flow characteristics to reflect internal chain tracking mechanism
### Added
- **BREAKING:** Add required argument `channelType` to `BackendWebSocketService.subscribe` method ([#6819](https://github.com/MetaMask/core/pull/6819))
- Add `channelType` to argument of the `BackendWebSocketService:subscribe` messenger action
- Add `channelType` to `WebSocketSubscription` type
- **BREAKING**: Update `Asset` type definition: add required `decimals` field for proper token amount formatting ([#6819](https://github.com/MetaMask/core/pull/6819))
- Add optional `traceFn` parameter to `BackendWebSocketService` constructor for performance tracing integration (e.g., Sentry) ([#6819](https://github.com/MetaMask/core/pull/6819))
- Enables tracing of WebSocket operations including connect, disconnect methods
- Trace function receives operation metadata and callback to wrap for performance monitoring
- Add optional `timestamp` property to `ServerNotificationMessage` and `SystemNoticationData` types ([#6819](https://github.com/MetaMask/core/pull/6819))
- Add optional `timestamp` property to payload for callback to `BackendWebSocketService.addChannelCallback` and `BackendWebSocketService.subscribe`, as well as corresponding actions
- Add optional `timestamp` property to `AccountActivityService:statusChanged` event and corresponding event type ([#6819](https://github.com/MetaMask/core/pull/6819))
### Changed
- **BREAKING:** Update `BackendWebSocketService` to automatically manage WebSocket connections based on wallet lock state ([#6819](https://github.com/MetaMask/core/pull/6819))
- `KeyringController:lock` and `KeyringController:unlock` are now required events in the `BackendWebSocketService` messenger
- **BREAKING**: Update `Transaction` type definition: rename `hash` field to `id` for consistency with backend API ([#6819](https://github.com/MetaMask/core/pull/6819))
- **BREAKING:** Add peer dependency on `@metamask/keyring-controller` (^23.0.0) ([#6819](https://github.com/MetaMask/core/pull/6819))
- Update `BackendWebSocketService` to simplify reconnection logic: auto-reconnect on any unexpected disconnect (not just code 1000), stay disconnected when manually disconnecting via `disconnect` ([#6819](https://github.com/MetaMask/core/pull/6819))
- Improve error handling in `BackendWebSocketService.connect()` to properly rethrow errors to callers ([#6819](https://github.com/MetaMask/core/pull/6819))
- Update `AccountActivityService` to replace API-based chain support detection with system notification-driven chain tracking ([#6819](https://github.com/MetaMask/core/pull/6819))
- Instead of hardcoding a list of supported chains, assume that the backend has the list
- When receiving a system notification, capture the backend-tracked status of each chain instead of assuming it is up or down
- Flush all tracked chains as 'down' on disconnect/error (instead of using hardcoded list)
- Update documentation in `README.md` to reflect new connection management model and chain tracking behavior ([#6819](https://github.com/MetaMask/core/pull/6819))
- Add "WebSocket Connection Management" section explaining connection requirements and behavior
- Update sequence diagram to show system notification-driven chain status flow
- Update key flow characteristics to reflect internal chain tracking mechanism


### Removed

- **BREAKING**: Removed `getSupportedChains()` public method and all related API fetching logic
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we specify where this was removed from? We are also missing a PR link. We also don't need to mention implementation details.

Suggested change
- **BREAKING**: Removed `getSupportedChains()` public method and all related API fetching logic
- **BREAKING**: Remove `getSupportedChains` method from `AccountActivityService` ([#6819](https://github.com/MetaMask/core/pull/6819))

- **BREAKING**: Removed hardcoded `DEFAULT_SUPPORTED_CHAINS` fallback list and cache expiration mechanism
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't a public constant. Do we need to mention this in the changelog?

Suggested change
- **BREAKING**: Removed hardcoded `DEFAULT_SUPPORTED_CHAINS` fallback list and cache expiration mechanism


## [1.0.1]

### Changed
Expand Down
84 changes: 62 additions & 22 deletions packages/core-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Core backend services for MetaMask, serving as the data layer between Backend se
- [Data Flow](#data-flow)
- [Sequence Diagram: Real-time Account Activity Flow](#sequence-diagram-real-time-account-activity-flow)
- [Key Flow Characteristics](#key-flow-characteristics)
- [WebSocket Connection Management](#websocket-connection-management)
- [Connection Requirements](#connection-requirements)
- [Connection Behavior](#connection-behavior)
- [API Reference](#api-reference)
- [BackendWebSocketService](#backendwebsocketservice)
- [Constructor Options](#constructor-options)
Expand Down Expand Up @@ -227,16 +230,17 @@ sequenceDiagram
Backend->>WS: Connection established
WS->>AA: WebSocket connection status notification<br/>(BackendWebSocketService:connectionStateChanged)<br/>{state: 'CONNECTED'}

par StatusChanged Event
AA->>TBC: Chain availability notification<br/>(AccountActivityService:statusChanged)<br/>{chainIds: ['0x1', '0x89', ...], status: 'up'}
TBC->>TBC: Increase polling interval from 20s to 10min<br/>(.updateChainPollingConfigs({0x89: 600000}))
and Account Subscription
AA->>AA: call('AccountsController:getSelectedAccount')
AA->>WS: subscribe({channels, callback})
WS->>Backend: {event: 'subscribe', channels: ['account-activity.v1.eip155:0:0x123...']}
Backend->>WS: {event: 'subscribe-response', subscriptionId: 'sub-456'}
WS->>AA: Subscription sucessful
end
AA->>AA: call('AccountsController:getSelectedAccount')
AA->>WS: subscribe({channels, callback})
WS->>Backend: {event: 'subscribe', channels: ['account-activity.v1.eip155:0:0x123...']}
Backend->>WS: {event: 'subscribe-response', subscriptionId: 'sub-456'}

Note over WS,Backend: System notification sent automatically upon subscription
Backend->>WS: {event: 'system-notification', data: {chainIds: ['eip155:1', 'eip155:137', ...], status: 'up'}}
WS->>AA: System notification received
AA->>AA: Track chains as 'up' internally
AA->>TBC: Chain availability notification<br/>(AccountActivityService:statusChanged)<br/>{chainIds: ['0x1', '0x89', ...], status: 'up'}
TBC->>TBC: Increase polling interval from 20s to 10min<br/>(.updateChainPollingConfigs({0x89: 600000}))

Note over TBC,Backend: User Account Change

Expand Down Expand Up @@ -285,24 +289,60 @@ sequenceDiagram
Note over TBC,Backend: Connection Health Management

Backend-->>WS: Connection lost
WS->>TBC: WebSocket connection status notification<br/>(BackendWebSocketService:connectionStateChanged)<br/>{state: 'DISCONNECTED'}
TBC->>TBC: Decrease polling interval from 10min to 20s(.updateChainPollingConfigs({0x89: 20000}))
WS->>AA: WebSocket connection status notification<br/>(BackendWebSocketService:connectionStateChanged)<br/>{state: 'DISCONNECTED'}
AA->>AA: Mark all tracked chains as 'down'<br/>(flush internal tracking set)
AA->>TBC: Chain status notification for all tracked chains<br/>(AccountActivityService:statusChanged)<br/>{chainIds: ['0x1', '0x89', ...], status: 'down'}
TBC->>TBC: Decrease polling interval from 10min to 20s<br/>(.updateChainPollingConfigs({0x89: 20000}))
TBC->>HTTP: Fetch balances immediately
WS->>WS: Automatic reconnection<br/>with exponential backoff
WS->>Backend: Reconnection successful - Restart initial setup
WS->>Backend: Reconnection successful

Note over AA,Backend: Restart initial setup - resubscribe and get fresh chain status
AA->>WS: subscribe (same account, new subscription)
WS->>Backend: {event: 'subscribe', channels: ['account-activity.v1.eip155:0:0x123...']}
Backend->>WS: {event: 'subscribe-response', subscriptionId: 'sub-999'}
Backend->>WS: {event: 'system-notification', data: {chainIds: [...], status: 'up'}}
WS->>AA: System notification received
AA->>AA: Track chains as 'up' again
AA->>TBC: Chain availability notification<br/>(AccountActivityService:statusChanged)<br/>{chainIds: [...], status: 'up'}
TBC->>TBC: Increase polling interval back to 10min
```

#### Key Flow Characteristics

1. **Initial Setup**: BackendWebSocketService establishes connection, then AccountActivityService simultaneously notifies all chains are up AND subscribes to selected account, TokenBalancesController increases polling interval to 10 min, then makes initial HTTP request for current balance state
2. **User Account Changes**: When users switch accounts, AccountActivityService unsubscribes from old account, TokenBalancesController makes HTTP calls to fill data gaps, then AccountActivityService subscribes to new account
3. **Real-time Updates**: Backend pushes data through: Backend → BackendWebSocketService → AccountActivityService → TokenBalancesController (+ future TransactionController integration)
4. **System Notifications**: Backend sends chain status updates (up/down) through WebSocket, AccountActivityService processes and forwards to TokenBalancesController which adjusts polling intervals and fetches balances immediately on chain down (chain down: 10min→20s + immediate fetch, chain up: 20s→10min)
5. **Parallel Processing**: Transaction and balance updates processed simultaneously - AccountActivityService publishes both transactionUpdated (future) and balanceUpdated events in parallel
6. **Dynamic Polling**: TokenBalancesController adjusts HTTP polling intervals based on WebSocket connection health (10 min when connected, 20s when disconnected)
7. **Direct Balance Processing**: Real-time balance updates bypass HTTP polling and update TokenBalancesController state directly
8. **Connection Resilience**: Automatic reconnection with resubscription to selected account
9. **Ultra-Simple Error Handling**: Any error anywhere → force reconnection (no nested try-catch)
1. **Initial Setup**: BackendWebSocketService establishes connection, then AccountActivityService subscribes to selected account. Backend automatically sends a system notification with all chains that are currently up. AccountActivityService tracks these chains internally and notifies TokenBalancesController, which increases polling interval to 5 min
2. **Chain Status Tracking**: AccountActivityService maintains an internal set of chains that are 'up' based on system notifications. On disconnect/error, it marks all tracked chains as 'down' before clearing the set
3. **System Notifications**: Backend automatically sends chain status updates (up/down) upon subscription and when status changes. AccountActivityService forwards these to TokenBalancesController, which adjusts polling intervals (up: 5min, down: 30s + immediate fetch)
4. **User Account Changes**: When users switch accounts, AccountActivityService unsubscribes from old account and subscribes to new account. Backend sends fresh system notification with current chain status for the new account
5. **Connection Resilience**: On reconnection, AccountActivityService resubscribes to selected account and receives fresh chain status via system notification. Automatic reconnection with exponential backoff
6. **Real-time Updates**: Backend pushes data through: Backend → BackendWebSocketService → AccountActivityService → TokenBalancesController (+ future TransactionController integration)
7. **Parallel Processing**: Transaction and balance updates processed simultaneously - AccountActivityService publishes both transactionUpdated (future) and balanceUpdated events in parallel
8. **Direct Balance Processing**: Real-time balance updates bypass HTTP polling and update TokenBalancesController state directly

## WebSocket Connection Management

### Connection Requirements

The WebSocket connects when **ALL 3 conditions are true**:

1. ✅ **Feature enabled** - `isEnabled()` callback returns `true` (feature flag)
2. ✅ **User signed in** - `AuthenticationController.isSignedIn = true`
3. ✅ **Wallet unlocked** - `KeyringController.isUnlocked = true`

**Plus:** Platform code must call `connect()` when app opens/foregrounds and `disconnect()` when app closes/backgrounds.

### Connection Behavior

**Idempotent `connect()`:**

- Safe to call multiple times - validates conditions and returns early if already connected
- Multiple rapid calls reuse the same connection promise (no duplicate connections)
- No debouncing needed - handled automatically

**Auto-Reconnect:**

- ✅ **Unexpected disconnects** (network issues, server restart) → Auto-reconnect
- ❌ **Manual disconnects** (app backgrounds, wallet locks, user signs out) → Stay disconnected

## API Reference

Expand Down
5 changes: 3 additions & 2 deletions packages/core-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@
"devDependencies": {
"@metamask/accounts-controller": "^33.1.1",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/keyring-controller": "^23.1.1",
"@ts-bridge/cli": "^0.6.1",
"@types/jest": "^27.4.1",
"deepmerge": "^4.2.2",
"jest": "^27.5.1",
"nock": "^13.3.1",
"sinon": "^9.2.4",
"ts-jest": "^27.1.4",
"typedoc": "^0.24.8",
"typedoc-plugin-missing-exports": "^2.0.0",
"typescript": "~5.2.2"
},
"peerDependencies": {
"@metamask/accounts-controller": "^33.1.0"
"@metamask/accounts-controller": "^33.1.0",
"@metamask/keyring-controller": "^23.0.0"
},
"engines": {
"node": "^18.18 || >=20"
Expand Down
Loading
Loading