diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 97a623f347f..7a1f398cd32 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -311,7 +311,7 @@ "count": 1 }, "no-restricted-syntax": { - "count": 2 + "count": 1 } }, "packages/assets-controllers/src/NftController.test.ts": { diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 1d3b84d2d50..412b39b1ba7 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- `MultichainBalancesController` no longer silently drops balance updates from `AccountsController:accountBalancesUpdated` when the account does not yet have an entry in `state.balances` ([#8484](https://github.com/MetaMask/core/pull/8484)) + - Previously, if a Snap sent balance updates before `updateBalance` had initialized `state.balances[accountId]`, the update was ignored because of an `accountId in state.balances` guard. The handler now initializes the entry before merging, ensuring balances are always persisted. + ## [104.1.0] ### Added diff --git a/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.test.ts b/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.test.ts index 1dfd40564c9..f0efff937f0 100644 --- a/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.test.ts +++ b/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.test.ts @@ -377,6 +377,34 @@ describe('MultichainBalancesController', () => { ); }); + it('initializes and stores balances when "AccountsController:accountBalancesUpdated" fires before the account has an entry in state', async () => { + const { controller, messenger } = setupController({ + state: { balances: {} }, + mocks: { + listMultichainAccounts: [], + }, + }); + + expect(controller.state.balances[mockBtcAccount.id]).toBeUndefined(); + + const balanceUpdate = { + balances: { + [mockBtcAccount.id]: mockBalanceResult, + }, + }; + + messenger.publish( + 'AccountsController:accountBalancesUpdated', + balanceUpdate, + ); + + await waitForAllPromises(); + + expect(controller.state.balances[mockBtcAccount.id]).toStrictEqual( + mockBalanceResult, + ); + }); + it('updates balances when receiving "AccountsController:accountBalancesUpdated" event', async () => { const mockInitialBalances = { [mockBtcNativeAsset]: { diff --git a/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.ts b/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.ts index 88356c68bd3..7d8fbceaef4 100644 --- a/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.ts +++ b/packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.ts @@ -384,9 +384,8 @@ export class MultichainBalancesController extends BaseController< this.update((state: Draft) => { Object.entries(balanceUpdate.balances).forEach( ([accountId, assetBalances]) => { - if (accountId in state.balances) { - Object.assign(state.balances[accountId], assetBalances); - } + state.balances[accountId] ??= {}; + Object.assign(state.balances[accountId], assetBalances); }, ); });