@@ -41,6 +41,10 @@ import type {
41
41
TokensControllerState ,
42
42
TokensControllerStateChangeEvent ,
43
43
} from './TokensController' ;
44
+ import type {
45
+ AccountActivityServiceBalanceUpdatedEvent ,
46
+ } from '@metamask/backend-platform' ;
47
+ import type { AccountBalancesUpdatedEventPayload } from '@metamask/keyring-api' ;
44
48
45
49
const DEFAULT_INTERVAL = 180000 ;
46
50
@@ -104,7 +108,8 @@ export type AllowedEvents =
104
108
| TokensControllerStateChangeEvent
105
109
| PreferencesControllerStateChangeEvent
106
110
| NetworkControllerStateChangeEvent
107
- | KeyringControllerAccountRemovedEvent ;
111
+ | KeyringControllerAccountRemovedEvent
112
+ | AccountActivityServiceBalanceUpdatedEvent ;
108
113
109
114
export type TokenBalancesControllerMessenger = RestrictedMessenger <
110
115
typeof controllerName ,
@@ -202,6 +207,18 @@ export class TokenBalancesController extends StaticIntervalPollingController<Tok
202
207
'KeyringController:accountRemoved' ,
203
208
( accountAddress : string ) => this . #handleOnAccountRemoved( accountAddress ) ,
204
209
) ;
210
+
211
+ // Subscribe to AccountActivityService balance updates for EVM networks
212
+ try {
213
+ this . messagingSystem . subscribe (
214
+ 'AccountActivityService:balanceUpdated' ,
215
+ ( balances : AccountBalancesUpdatedEventPayload ) =>
216
+ this . #handleAccountActivityBalanceUpdated( balances ) ,
217
+ ) ;
218
+ } catch ( error ) {
219
+ // AccountActivityService might not be available in all environments
220
+ console . log ( 'AccountActivityService not available for EVM token balance updates:' , error ) ;
221
+ }
205
222
}
206
223
207
224
/**
@@ -305,6 +322,71 @@ export class TokenBalancesController extends StaticIntervalPollingController<Tok
305
322
} ) ;
306
323
}
307
324
325
+ /**
326
+ * Handles balance updates received from the AccountActivityService for EVM networks.
327
+ * Converts account IDs to addresses and updates token balances accordingly.
328
+ *
329
+ * @param balanceUpdate - The balance update from AccountActivityService containing new balances.
330
+ */
331
+ #handleAccountActivityBalanceUpdated(
332
+ balanceUpdate : AccountBalancesUpdatedEventPayload ,
333
+ ) : void {
334
+ // Convert account IDs to addresses first
335
+ const addressBalances : Record < string , Record < string , { unit : string ; amount : string } > > = { } ;
336
+
337
+ for ( const [ accountId , assetBalances ] of Object . entries ( balanceUpdate . balances ) ) {
338
+ let accountAddress : string ;
339
+
340
+ if ( accountId . startsWith ( '0x' ) ) {
341
+ // Already an address
342
+ accountAddress = accountId ;
343
+ } else {
344
+ // Convert account ID to address
345
+ try {
346
+ const accounts = this . messagingSystem . call ( 'AccountsController:listAccounts' ) ;
347
+ const account = accounts . find ( acc => acc . id === accountId ) ;
348
+ if ( ! account ) {
349
+ continue ;
350
+ }
351
+ accountAddress = account . address ;
352
+ } catch ( error ) {
353
+ continue ;
354
+ }
355
+ }
356
+
357
+ // Only process valid EVM addresses
358
+ if ( isStrictHexString ( accountAddress . toLowerCase ( ) ) && isValidHexAddress ( accountAddress ) ) {
359
+ addressBalances [ accountAddress ] = assetBalances ;
360
+ }
361
+ }
362
+
363
+ // Update state with address-based balances
364
+ this . update ( ( state ) => {
365
+ for ( const [ accountAddress , assetBalances ] of Object . entries ( addressBalances ) ) {
366
+ const address = accountAddress as Hex ;
367
+
368
+ // Initialize account if needed
369
+ if ( ! state . tokenBalances [ address ] ) {
370
+ state . tokenBalances [ address ] = { } ;
371
+ }
372
+
373
+ // Process each EVM asset
374
+ for ( const [ assetType , balance ] of Object . entries ( assetBalances ) ) {
375
+ const evmERC20AssetMatch = assetType . match ( / ^ e i p 1 5 5 : ( \d + ) \/ e r c 2 0 : ( 0 x [ a - f A - F 0 - 9 ] { 40 } ) $ / i) ;
376
+
377
+ if ( evmERC20AssetMatch ) {
378
+ const chainId = `0x${ parseInt ( evmERC20AssetMatch [ 1 ] , 10 ) . toString ( 16 ) } ` as Hex ;
379
+ const tokenAddress = evmERC20AssetMatch [ 2 ] as Hex ;
380
+
381
+ // Initialize chain and token if needed
382
+ ( ( state . tokenBalances [ address ] ??= { } ) [ chainId ] ??= { } ) [ tokenAddress ] =
383
+ `0x${ parseInt ( balance . amount , 10 ) . toString ( 16 ) } ` as Hex ;
384
+ }
385
+ }
386
+ }
387
+ } ) ;
388
+ }
389
+
308
390
/**
309
391
* Returns an array of chain ids that have tokens.
310
392
* @param allTokens - The state for imported tokens across all chains.
0 commit comments