Skip to content

Commit 18d7b69

Browse files
authored
feat(core-backend): update websocket behavior (#6819)
## Explanation ### Current State & Motivation The `core-backend` package had several architectural issues that needed to be addressed: 1. **Inflexible Chain Management**: `AccountActivityService` was proactively fetching supported chains from the Accounts API (`/v2/supportedNetworks`) with a hardcoded fallback list. This approach was: - Out of sync with backend capabilities (client-side caching vs. real-time backend state) - Unnecessarily complex with cache expiration logic - Made assumptions about supported chains rather than reacting to actual backend status 2. **Incomplete Connection Management**: `BackendWebSocketService` only subscribed to `AuthenticationController:stateChange` events but didn't directly respond to wallet lock/unlock events from `KeyringController`. This created a dependency where connection management relied solely on authentication state changes. 3. **Reconnection Logic Based on Close Codes**: The service used WebSocket close codes to determine reconnection behavior, which was fragile and didn't properly distinguish between manual disconnects (user action) and unexpected disconnects (network issues). 4. **Type Mismatches**: The `Transaction` and `Asset` types didn't match the backend API contract, causing potential integration issues. 5. **No Performance Monitoring**: There was no way to trace WebSocket operations for performance analysis in production environments. 6. **Inconsistent Address Casing**: WebSocket-based token balance and detection updates had inconsistent address normalization, causing issues with state lookups and controller integration. ### Solution #### 1. System Notification-Driven Chain Tracking (`AccountActivityService`) **Before**: Client proactively fetches and caches supported chains ```typescript async getSupportedChains(): Promise<string[]> { // Fetch from API with cache + fallback to hardcoded list } ``` **After**: Client reacts to backend system notifications ```typescript #chainsUp: Set<string> = new Set(); // Backend automatically sends system notifications with chain status // Service tracks chains dynamically based on real-time backend state ``` **Benefits**: - Real-time chain status that matches backend capabilities - No stale cache issues - Simpler code (removed ~100 lines) - Backend has single source of truth **Flow**: 1. Client subscribes to account activity 2. Backend automatically sends system notification: `{chainIds: ['eip155:1', 'eip155:137'], status: 'up'}` 3. Service tracks these chains internally 4. On disconnect, service flushes all tracked chains as 'down' 5. On reconnect, backend sends fresh system notification with current status #### 2. Simplified Reconnection Logic (`BackendWebSocketService`) **Before**: Reconnection based on close codes ```typescript #shouldReconnectOnClose(code: number): boolean { return code !== 1000; // Only don't reconnect on normal closure } ``` **After**: Reconnection based on manual disconnect flag ```typescript #manualDisconnect = false; connect() { this.#manualDisconnect = false; // Reset flag on explicit connect } disconnect() { this.#manualDisconnect = true; // Set flag to prevent auto-reconnect } onclose() { if (!this.#manualDisconnect) { this.#scheduleReconnect(); // Auto-reconnect unless manually disconnected } } ``` **Benefits**: - Clear intent: manual disconnects stay disconnected, unexpected disconnects auto-reconnect - No ambiguity with close codes - Simpler logic #### 3. KeyringController Event Integration Added direct subscriptions to wallet lock state: ```typescript #subscribeEvents() { // Sign in/out this.#messenger.subscribe('AuthenticationController:stateChange', ...); // Wallet lock/unlock (NEW) this.#messenger.subscribe('KeyringController:unlock', () => this.connect()); this.#messenger.subscribe('KeyringController:lock', () => this.disconnect()); } ``` **Connection Requirements** (all must be true): 1. ✅ Feature enabled (`isEnabled()` callback) 2. ✅ User signed in (`AuthenticationController.isSignedIn`) 3. ✅ Wallet unlocked (`KeyringController` state) #### 4. Type Alignment with Backend API - `Transaction.hash` → `Transaction.id` (matches backend field name) - `Asset` now requires `decimals: number` (needed for proper token formatting) #### 5. Performance Tracing Integration Added optional `traceFn` parameter to enable performance monitoring: ```typescript new BackendWebSocketService({ messenger, url: BACKEND_WS_URL, traceFn: trace as TraceCallback, // Inject platform-specific trace function // ...other options }); ``` **Benefits**: - Platform-agnostic tracing support (enables Sentry integration in extension/mobile) - Traces WebSocket operations (connect, disconnect methods) - Enables performance monitoring in production - Core package remains platform-agnostic with no-op default **Integration**: - **Extension**: Uses `trace` from `shared/lib/trace.ts` (Sentry-backed) - **Mobile**: Uses `trace` from `app/util/trace.ts` (Sentry-backed) - **Core**: Defaults to no-op if not provided, keeping the package platform-agnostic **Example usage**: ```typescript // Extension/Mobile init files inject their platform-specific trace function const service = new BackendWebSocketService({ messenger, url: BACKEND_WS_URL, traceFn: trace as TraceCallback, // When operations occur, they're automatically traced: // - "BackendWebSocketService:connect" // - "BackendWebSocketService:disconnect" }); ``` #### 6. Address Normalization for WebSocket Updates (`assets-controllers`) Fixed inconsistent address casing in WebSocket-based token balance and detection updates to ensure proper state lookups and controller integration. **TokenBalancesController** (`#onAccountActivityBalanceUpdate`): - **Account addresses**: Normalized to lowercase for `tokenBalances` state lookups - **Token addresses**: Normalized to checksum format for `tokenBalances` state storage - **Native balance updates**: Uses checksummed account addresses for `AccountTrackerController:updateNativeBalances` - **New tokens**: Uses checksummed token addresses for `TokensController:addTokens` **TokenDetectionController** (`addDetectedTokensViaWs`): - **Token addresses**: Explicitly normalized to checksum format before calling `TokensController:addTokens` - **Token cache lookups**: Uses lowercase token addresses for `tokensChainsCache` lookups - **Fresh cache**: Fetches `tokensChainsCache` at the start of the method to prevent stale data **Benefits**: - Consistent address format across different state stores - Prevents state lookup failures due to casing mismatches - Ensures compatibility between controllers with different address format expectations - Robust handling without assumptions about input format **Example**: ```typescript // Before: Address casing was inconsistent state.tokenBalances[address][tokenAddress] = balance; // Could fail if casing mismatched // After: Explicit normalization const normalizedAccount = address.toLowerCase(); const checksummedToken = toChecksumAddress(tokenAddress); state.tokenBalances[normalizedAccount as Hex][checksummedToken] = balance; ``` ### Breaking Changes Impact **For `AccountActivityService` consumers**: - ❌ **Removed**: `getSupportedChains()` method - consumers should subscribe to `AccountActivityService:statusChanged` events instead - Chain status is now event-driven rather than request-response **For type definitions**: - `Transaction` objects must use `id` instead of `hash` - `Asset` objects must include `decimals` field ### Testing Updates - Removed `nock` dependency (no longer fetching from external API) - Updated all tests to use system notification-driven flow - Added tests for KeyringController event integration - Added tests for manual disconnect flag behavior - Increased test coverage for edge cases in reconnection logic - Updated `TokenDetectionController` tests to expect checksummed addresses in `addTokens` calls ## References - Part of ongoing core-backend stabilization effort - Aligns with backend system notification architecture ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Revamps core-backend WebSocket lifecycle (keyring lock/unlock, manual disconnect, tracing, new params/types, system notification–driven chains) and fixes assets controllers to normalize addresses for WebSocket token updates; docs/tests updated. > > - **Core Backend (`@metamask/core-backend`)**: > - **WebSocket**: New connection model (idempotent connect, auto-reconnect unless manual disconnect), subscribes to `KeyringController:lock/unlock`; exposes richer `getConnectionInfo`. > - **API Changes (BREAKING)**: > - `BackendWebSocketService.subscribe` now requires `channelType`. > - Types: `Transaction.hash` → `id`; `Asset` adds required `decimals`; `ServerNotificationMessage` includes `timestamp`. > - Messenger/events expanded; add peer dep on `@metamask/keyring-controller`. > - **Tracing**: Optional `traceFn` to instrument connect/disconnect/notifications. > - **AccountActivityService**: Drops `getSupportedChains`; chains tracked via system notifications; publishes `statusChanged` with optional `timestamp`. > - **Docs**: README updated with new flow and “WebSocket Connection Management”. > - **Assets Controllers**: > - **TokenBalancesController**: Normalizes account (lowercase) and token (checksum) for WS updates; fixes tracking checks; updates native balance propagation; adds decimals handling in tests. > - **TokenDetectionController**: Adds checksummed addresses and lowercase cache lookups in `addDetectedTokensViaWs`; metrics use checksummed addresses. > - **Changelog**: Notes fix for address casing in WS balance updates. > - **Repo**: > - README: adds `@metamask/core-backend` to package list and dependency graph; minor graph edge additions. > - Build/config: add keyring-controller refs; adjust dev/peer deps; test updates to reflect new behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 16e8b32. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 52444dd commit 18d7b69

17 files changed

+959
-926
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Each package in this repository has its own README where you can find installati
3434
- [`@metamask/chain-agnostic-permission`](packages/chain-agnostic-permission)
3535
- [`@metamask/composable-controller`](packages/composable-controller)
3636
- [`@metamask/controller-utils`](packages/controller-utils)
37+
- [`@metamask/core-backend`](packages/core-backend)
3738
- [`@metamask/delegation-controller`](packages/delegation-controller)
3839
- [`@metamask/earn-controller`](packages/earn-controller)
3940
- [`@metamask/eip-5792-middleware`](packages/eip-5792-middleware)
@@ -98,6 +99,7 @@ linkStyle default opacity:0.5
9899
chain_agnostic_permission(["@metamask/chain-agnostic-permission"]);
99100
composable_controller(["@metamask/composable-controller"]);
100101
controller_utils(["@metamask/controller-utils"]);
102+
core_backend(["@metamask/core-backend"]);
101103
delegation_controller(["@metamask/delegation-controller"]);
102104
earn_controller(["@metamask/earn-controller"]);
103105
eip_5792_middleware(["@metamask/eip-5792-middleware"]);
@@ -159,6 +161,7 @@ linkStyle default opacity:0.5
159161
assets_controllers --> account_tree_controller;
160162
assets_controllers --> accounts_controller;
161163
assets_controllers --> approval_controller;
164+
assets_controllers --> core_backend;
162165
assets_controllers --> keyring_controller;
163166
assets_controllers --> multichain_account_service;
164167
assets_controllers --> network_controller;
@@ -192,6 +195,11 @@ linkStyle default opacity:0.5
192195
chain_agnostic_permission --> permission_controller;
193196
composable_controller --> base_controller;
194197
composable_controller --> json_rpc_engine;
198+
core_backend --> base_controller;
199+
core_backend --> controller_utils;
200+
core_backend --> profile_sync_controller;
201+
core_backend --> accounts_controller;
202+
core_backend --> keyring_controller;
195203
delegation_controller --> base_controller;
196204
delegation_controller --> accounts_controller;
197205
delegation_controller --> keyring_controller;
@@ -264,6 +272,7 @@ linkStyle default opacity:0.5
264272
permission_log_controller --> json_rpc_engine;
265273
phishing_controller --> base_controller;
266274
phishing_controller --> controller_utils;
275+
phishing_controller --> transaction_controller;
267276
polling_controller --> base_controller;
268277
polling_controller --> controller_utils;
269278
polling_controller --> network_controller;

packages/assets-controllers/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix address casing in WebSocket-based token balance updates to ensure consistency ([#6819](https://github.com/MetaMask/core/pull/6819))
13+
1014
## [80.0.0]
1115

1216
### Added

packages/assets-controllers/src/TokenBalancesController.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4328,6 +4328,7 @@ describe('TokenBalancesController', () => {
43284328
type: `eip155:1/erc20:${tokenAddress}`,
43294329
unit: 'USDC',
43304330
fungible: true,
4331+
decimals: 6,
43314332
},
43324333
postBalance: {
43334334
amount: '0xf4240', // 1000000 in hex (1 USDC with 6 decimals)
@@ -4378,6 +4379,7 @@ describe('TokenBalancesController', () => {
43784379
type: 'eip155:1/slip44:60',
43794380
unit: 'ETH',
43804381
fungible: true,
4382+
decimals: 18,
43814383
},
43824384
postBalance: {
43834385
amount: '0xde0b6b3a7640000', // 1 ETH in wei
@@ -4431,6 +4433,7 @@ describe('TokenBalancesController', () => {
44314433
type: 'eip155:1/slip44:60',
44324434
unit: 'ETH',
44334435
fungible: true,
4436+
decimals: 18,
44344437
},
44354438
postBalance: {
44364439
amount: '0',
@@ -4468,6 +4471,7 @@ describe('TokenBalancesController', () => {
44684471
type: 'eip155:1/unknown:0x123',
44694472
unit: 'UNKNOWN',
44704473
fungible: true,
4474+
decimals: 18,
44714475
},
44724476
postBalance: {
44734477
amount: '1000',
@@ -4650,6 +4654,7 @@ describe('TokenBalancesController', () => {
46504654
type: `eip155:1/erc20:${token1}`,
46514655
unit: 'USDC',
46524656
fungible: true,
4657+
decimals: 6,
46534658
},
46544659
postBalance: {
46554660
amount: '0xf4240', // 1000000 in hex
@@ -4661,6 +4666,7 @@ describe('TokenBalancesController', () => {
46614666
type: `eip155:1/erc20:${token2}`,
46624667
unit: 'USDT',
46634668
fungible: true,
4669+
decimals: 6,
46644670
},
46654671
postBalance: {
46664672
amount: '0x1e8480', // 2000000 in hex
@@ -4706,6 +4712,7 @@ describe('TokenBalancesController', () => {
47064712
type: 'eip155:1/erc20:invalid-address', // Not a valid hex address
47074713
unit: 'INVALID',
47084714
fungible: true,
4715+
decimals: 18,
47094716
},
47104717
postBalance: { amount: '1000000' },
47114718
transfers: [],
@@ -4780,6 +4787,7 @@ describe('TokenBalancesController', () => {
47804787
type: `eip155:1/erc20:${newTokenAddress}`,
47814788
unit: 'USDC',
47824789
fungible: true,
4790+
decimals: 6,
47834791
},
47844792
postBalance: {
47854793
amount: '0xf4240', // 1000000 in hex
@@ -4853,6 +4861,7 @@ describe('TokenBalancesController', () => {
48534861
type: `eip155:1/erc20:${trackedTokenAddress}`,
48544862
unit: 'USDC',
48554863
fungible: true,
4864+
decimals: 6,
48564865
},
48574866
postBalance: {
48584867
amount: '0xf4240', // 1000000 in hex
@@ -4916,6 +4925,7 @@ describe('TokenBalancesController', () => {
49164925
type: `eip155:1/erc20:${ignoredTokenAddress}`,
49174926
unit: 'USDC',
49184927
fungible: true,
4928+
decimals: 6,
49194929
},
49204930
postBalance: {
49214931
amount: '0xf4240', // 1000000 in hex
@@ -4973,6 +4983,7 @@ describe('TokenBalancesController', () => {
49734983
type: 'eip155:1/slip44:60',
49744984
unit: 'ETH',
49754985
fungible: true,
4986+
decimals: 18,
49764987
},
49774988
postBalance: {
49784989
amount: '0xde0b6b3a7640000', // 1 ETH in wei
@@ -5039,6 +5050,7 @@ describe('TokenBalancesController', () => {
50395050
type: `eip155:1/erc20:${newTokenAddress}`,
50405051
unit: 'USDC',
50415052
fungible: true,
5053+
decimals: 6,
50425054
},
50435055
postBalance: {
50445056
amount: '0xf4240', // 1000000 in hex
@@ -5109,6 +5121,7 @@ describe('TokenBalancesController', () => {
51095121
type: `eip155:1/erc20:${trackedToken}`,
51105122
unit: 'USDC',
51115123
fungible: true,
5124+
decimals: 6,
51125125
},
51135126
postBalance: {
51145127
amount: '0xf4240', // 1000000 in hex
@@ -5120,6 +5133,7 @@ describe('TokenBalancesController', () => {
51205133
type: `eip155:1/erc20:${untrackedToken}`,
51215134
unit: 'USDT',
51225135
fungible: true,
5136+
decimals: 6,
51235137
},
51245138
postBalance: {
51255139
amount: '0x1e8480', // 2000000 in hex

packages/assets-controllers/src/TokenBalancesController.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -844,12 +844,10 @@ export class TokenBalancesController extends StaticIntervalPollingController<{
844844
account: ChecksumAddress,
845845
chainId: ChainIdHex,
846846
): boolean {
847-
const normalizedAddress = tokenAddress.toLowerCase();
848-
849847
// Check if token exists in allTokens
850848
if (
851849
this.#allTokens?.[chainId]?.[account.toLowerCase()]?.some(
852-
(token) => token.address.toLowerCase() === normalizedAddress,
850+
(token) => token.address === tokenAddress,
853851
)
854852
) {
855853
return true;
@@ -858,7 +856,7 @@ export class TokenBalancesController extends StaticIntervalPollingController<{
858856
// Check if token exists in allIgnoredTokens
859857
if (
860858
this.#allIgnoredTokens?.[chainId]?.[account.toLowerCase()]?.some(
861-
(addr) => addr.toLowerCase() === normalizedAddress,
859+
(token) => token === tokenAddress,
862860
)
863861
) {
864862
return true;
@@ -1124,23 +1122,26 @@ export class TokenBalancesController extends StaticIntervalPollingController<{
11241122
updates: BalanceUpdate[];
11251123
}) => {
11261124
const chainId = caipChainIdToHex(chain);
1127-
const account = checksum(address);
1125+
const checksummedAccount = checksum(address);
11281126

11291127
try {
11301128
// Process all balance updates at once
11311129
const { tokenBalances, newTokens, nativeBalanceUpdates } =
1132-
this.#prepareBalanceUpdates(updates, account, chainId);
1130+
this.#prepareBalanceUpdates(updates, checksummedAccount, chainId);
11331131

11341132
// Update state once with all token balances
11351133
if (tokenBalances.length > 0) {
11361134
this.update((state) => {
1137-
// Initialize account and chain structure
1138-
state.tokenBalances[account] ??= {};
1139-
state.tokenBalances[account][chainId] ??= {};
1135+
// Temporary until ADR to normalize all keys - tokenBalances state requires: account in lowercase, token in checksum
1136+
const lowercaseAccount =
1137+
checksummedAccount.toLowerCase() as ChecksumAddress;
1138+
state.tokenBalances[lowercaseAccount] ??= {};
1139+
state.tokenBalances[lowercaseAccount][chainId] ??= {};
11401140

11411141
// Apply all token balance updates
11421142
for (const { tokenAddress, balance } of tokenBalances) {
1143-
state.tokenBalances[account][chainId][tokenAddress] = balance;
1143+
state.tokenBalances[lowercaseAccount][chainId][tokenAddress] =
1144+
balance;
11441145
}
11451146
});
11461147
}

packages/assets-controllers/src/TokenDetectionController.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,6 +3535,8 @@ describe('TokenDetectionController', () => {
35353535
describe('addDetectedTokensViaWs', () => {
35363536
it('should add tokens detected from websocket with metadata from cache', async () => {
35373537
const mockTokenAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
3538+
const checksummedTokenAddress =
3539+
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
35383540
const chainId = '0x1';
35393541

35403542
await withController(
@@ -3581,7 +3583,7 @@ describe('TokenDetectionController', () => {
35813583
'TokensController:addTokens',
35823584
[
35833585
{
3584-
address: mockTokenAddress,
3586+
address: checksummedTokenAddress,
35853587
decimals: 6,
35863588
symbol: 'USDC',
35873589
aggregators: [],
@@ -3652,7 +3654,11 @@ describe('TokenDetectionController', () => {
36523654

36533655
it('should add all tokens provided without filtering (filtering is caller responsibility)', async () => {
36543656
const mockTokenAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
3657+
const checksummedTokenAddress =
3658+
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
36553659
const secondTokenAddress = '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c';
3660+
const checksummedSecondTokenAddress =
3661+
'0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C';
36563662
const chainId = '0x1';
36573663
const selectedAccount = createMockInternalAccount({
36583664
address: '0x0000000000000000000000000000000000000001',
@@ -3718,7 +3724,7 @@ describe('TokenDetectionController', () => {
37183724
'TokensController:addTokens',
37193725
[
37203726
{
3721-
address: mockTokenAddress,
3727+
address: checksummedTokenAddress,
37223728
decimals: 6,
37233729
symbol: 'USDC',
37243730
aggregators: [],
@@ -3727,7 +3733,7 @@ describe('TokenDetectionController', () => {
37273733
name: 'USD Coin',
37283734
},
37293735
{
3730-
address: secondTokenAddress,
3736+
address: checksummedSecondTokenAddress,
37313737
decimals: 18,
37323738
symbol: 'BNT',
37333739
aggregators: [],
@@ -3744,6 +3750,8 @@ describe('TokenDetectionController', () => {
37443750

37453751
it('should track metrics when adding tokens from websocket', async () => {
37463752
const mockTokenAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
3753+
const checksummedTokenAddress =
3754+
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
37473755
const chainId = '0x1';
37483756
const mockTrackMetricsEvent = jest.fn();
37493757

@@ -3793,7 +3801,7 @@ describe('TokenDetectionController', () => {
37933801
event: 'Token Detected',
37943802
category: 'Wallet',
37953803
properties: {
3796-
tokens: [`USDC - ${mockTokenAddress}`],
3804+
tokens: [`USDC - ${checksummedTokenAddress}`],
37973805
token_standard: 'ERC20',
37983806
asset_type: 'TOKEN',
37993807
},
@@ -3810,6 +3818,8 @@ describe('TokenDetectionController', () => {
38103818

38113819
it('should be callable directly as a public method on the controller instance', async () => {
38123820
const mockTokenAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
3821+
const checksummedTokenAddress =
3822+
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
38133823
const chainId = '0x1';
38143824

38153825
await withController(
@@ -3857,7 +3867,7 @@ describe('TokenDetectionController', () => {
38573867
'TokensController:addTokens',
38583868
[
38593869
{
3860-
address: mockTokenAddress,
3870+
address: checksummedTokenAddress,
38613871
decimals: 6,
38623872
symbol: 'USDC',
38633873
aggregators: [],

packages/assets-controllers/src/TokenDetectionController.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ERC20,
1616
safelyExecute,
1717
isEqualCaseInsensitive,
18+
toChecksumHexAddress,
1819
} from '@metamask/controller-utils';
1920
import type {
2021
KeyringControllerGetStateAction,
@@ -1042,27 +1043,28 @@ export class TokenDetectionController extends StaticIntervalPollingController<To
10421043
const tokensWithBalance: Token[] = [];
10431044
const eventTokensDetails: string[] = [];
10441045

1045-
for (const nonZeroTokenAddress of tokensSlice) {
1046-
// Check map of validated tokens
1046+
for (const tokenAddress of tokensSlice) {
1047+
// Normalize addresses explicitly (don't assume input format)
1048+
const lowercaseTokenAddress = tokenAddress.toLowerCase();
1049+
const checksummedTokenAddress = toChecksumHexAddress(tokenAddress);
1050+
1051+
// Check map of validated tokens (cache keys are lowercase)
10471052
const tokenData =
1048-
this.#tokensChainsCache[chainId]?.data?.[
1049-
nonZeroTokenAddress.toLowerCase()
1050-
];
1053+
this.#tokensChainsCache[chainId]?.data?.[lowercaseTokenAddress];
10511054

10521055
if (!tokenData) {
10531056
console.warn(
1054-
`Token metadata not found in cache for ${nonZeroTokenAddress} on chain ${chainId}`,
1057+
`Token metadata not found in cache for ${tokenAddress} on chain ${chainId}`,
10551058
);
10561059
continue;
10571060
}
10581061

1059-
const { decimals, symbol, aggregators, iconUrl, name, address } =
1060-
tokenData;
1062+
const { decimals, symbol, aggregators, iconUrl, name } = tokenData;
10611063

1062-
// Push to lists
1063-
eventTokensDetails.push(`${symbol} - ${address}`);
1064+
// Push to lists with checksummed address (for allTokens storage)
1065+
eventTokensDetails.push(`${symbol} - ${checksummedTokenAddress}`);
10641066
tokensWithBalance.push({
1065-
address,
1067+
address: checksummedTokenAddress,
10661068
decimals,
10671069
symbol,
10681070
aggregators,

packages/core-backend/CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **BREAKING:** Add required argument `channelType` to `BackendWebSocketService.subscribe` method ([#6819](https://github.com/MetaMask/core/pull/6819))
13+
- Add `channelType` to argument of the `BackendWebSocketService:subscribe` messenger action
14+
- Add `channelType` to `WebSocketSubscription` type
15+
- **BREAKING**: Update `Asset` type definition: add required `decimals` field for proper token amount formatting ([#6819](https://github.com/MetaMask/core/pull/6819))
16+
- Add optional `traceFn` parameter to `BackendWebSocketService` constructor for performance tracing integration (e.g., Sentry) ([#6819](https://github.com/MetaMask/core/pull/6819))
17+
- Enables tracing of WebSocket operations including connect, disconnect methods
18+
- Trace function receives operation metadata and callback to wrap for performance monitoring
19+
- Add optional `timestamp` property to `ServerNotificationMessage` and `SystemNoticationData` types ([#6819](https://github.com/MetaMask/core/pull/6819))
20+
- Add optional `timestamp` property to `AccountActivityService:statusChanged` event and corresponding event type ([#6819](https://github.com/MetaMask/core/pull/6819))
21+
22+
### Changed
23+
24+
- **BREAKING:** Update `BackendWebSocketService` to automatically manage WebSocket connections based on wallet lock state ([#6819](https://github.com/MetaMask/core/pull/6819))
25+
- `KeyringController:lock` and `KeyringController:unlock` are now required events in the `BackendWebSocketService` messenger
26+
- **BREAKING**: Update `Transaction` type definition: rename `hash` field to `id` for consistency with backend API ([#6819](https://github.com/MetaMask/core/pull/6819))
27+
- **BREAKING:** Add peer dependency on `@metamask/keyring-controller` (^23.0.0) ([#6819](https://github.com/MetaMask/core/pull/6819))
28+
- 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))
29+
- Improve error handling in `BackendWebSocketService.connect()` to properly rethrow errors to callers ([#6819](https://github.com/MetaMask/core/pull/6819))
30+
- Update `AccountActivityService` to replace API-based chain support detection with system notification-driven chain tracking ([#6819](https://github.com/MetaMask/core/pull/6819))
31+
- Instead of hardcoding a list of supported chains, assume that the backend has the list
32+
- When receiving a system notification, capture the backend-tracked status of each chain instead of assuming it is up or down
33+
- Flush all tracked chains as 'down' on disconnect/error (instead of using hardcoded list)
34+
- Update documentation in `README.md` to reflect new connection management model and chain tracking behavior ([#6819](https://github.com/MetaMask/core/pull/6819))
35+
- Add "WebSocket Connection Management" section explaining connection requirements and behavior
36+
- Update sequence diagram to show system notification-driven chain status flow
37+
- Update key flow characteristics to reflect internal chain tracking mechanism
38+
39+
### Removed
40+
41+
- **BREAKING**: Remove `getSupportedChains` method from `AccountActivityService` ([#6819](https://github.com/MetaMask/core/pull/6819))
42+
1043
## [1.0.1]
1144

1245
### Changed

0 commit comments

Comments
 (0)