Skip to content

Commit 9ec7167

Browse files
authored
feat: changes to AccountTrackerController to enable migration in extension (#6938)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> Adds optional callback to disable fetching balances in AccountTrackerController. Attempts to refresh balance when a network is added and when the keyring is unlocked. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> * Related to https://consensyssoftware.atlassian.net/browse/ASSETS-1368 ## 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] > Adds optional fetchingEnabled gate, refreshes on networkAdded/unlock, and updates tx handlers to refresh both from/to addresses via new refreshAddresses; updates tests and changelog with breaking notes. > > - **AccountTrackerController**: > - Add optional `fetchingEnabled` constructor callback; `refresh` short-circuits when disabled. > - Subscribe to `NetworkController:networkAdded` and `KeyringController:unlock` to trigger balance refreshes. > - Update tx event handlers to refresh both `from` and `to` via new `refreshAddresses`; remove single-address refresh path. > - Adjust allowed actions/events and inline `PreferencesController:getState` typing; remove legacy `AccountsController:selectedAccountChange` reference. > - **Tests**: > - Add cases for `networkAdded`, `unlock`, and disabled fetching; address checksum fixes. > - **Changelog**: > - Document breaking event/action changes and the new `fetchingEnabled` option. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 878ed7e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1be70b0 commit 9ec7167

File tree

3 files changed

+160
-20
lines changed

3 files changed

+160
-20
lines changed

packages/assets-controllers/CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Changed
11+
12+
- **BREAKING:** Modify AccountTrackerController events and actions ([#6938](https://github.com/MetaMask/core/pull/6938))
13+
- `AccountsController:selectedAccountChange` is no longer part of the list of allowed events
14+
- `NetworkController:getState` refetches balances
15+
- `TransactionController:unapprovedTransactionAdded` refetches balances
16+
- `TransactionController:unapprovedTransactionAdded'` refetches balances for the transaction from address and network
17+
- `TransactionController:transactionConfirmed` refetches balances for the transaction from address and network
18+
- Add optional `fetchingEnabled` callback to `AccountTrackerController` constructor to stop it from fetching balances ([#6938](https://github.com/MetaMask/core/pull/6938))
19+
1020
## [85.0.0]
1121

1222
### Added

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

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type MessengerEvents,
99
type MockAnyNamespace,
1010
} from '@metamask/messenger';
11+
import type { NetworkConfiguration } from '@metamask/network-controller';
1112
import {
1213
type NetworkClientId,
1314
type NetworkClientConfiguration,
@@ -167,7 +168,7 @@ describe('AccountTrackerController', () => {
167168
mockedGetTokenBalancesForMultipleAddresses.mockResolvedValueOnce({
168169
tokenBalances: {
169170
'0x0000000000000000000000000000000000000000': {
170-
[CHECKSUM_ADDRESS_1]: new BN('123456', 16),
171+
[ADDRESS_1]: new BN('123456', 16),
171172
},
172173
},
173174
stakedBalances: {},
@@ -208,7 +209,7 @@ describe('AccountTrackerController', () => {
208209
mockedGetTokenBalancesForMultipleAddresses.mockResolvedValueOnce({
209210
tokenBalances: {
210211
'0x0000000000000000000000000000000000000000': {
211-
[CHECKSUM_ADDRESS_1]: new BN('abcdef', 16),
212+
[ADDRESS_1]: new BN('abcdef', 16),
212213
},
213214
},
214215
stakedBalances: {},
@@ -239,7 +240,88 @@ describe('AccountTrackerController', () => {
239240
);
240241
});
241242

243+
it('refreshes addresses when network is added', async () => {
244+
await withController(
245+
{
246+
selectedAccount: ACCOUNT_1,
247+
listAccounts: [ACCOUNT_1],
248+
},
249+
async ({ controller, messenger }) => {
250+
mockedGetTokenBalancesForMultipleAddresses.mockResolvedValueOnce({
251+
tokenBalances: {
252+
'0x0000000000000000000000000000000000000000': {
253+
[CHECKSUM_ADDRESS_1]: new BN('abcdef', 16),
254+
},
255+
},
256+
stakedBalances: {},
257+
});
258+
259+
messenger.publish(
260+
'NetworkController:networkAdded',
261+
{} as NetworkConfiguration,
262+
);
263+
264+
await clock.tickAsync(1);
265+
266+
expect(
267+
controller.state.accountsByChainId['0x1'][CHECKSUM_ADDRESS_1].balance,
268+
).toBe('0xabcdef');
269+
},
270+
);
271+
});
272+
273+
it('refreshes addresses when keyring is unlocked', async () => {
274+
await withController(
275+
{
276+
selectedAccount: ACCOUNT_1,
277+
listAccounts: [ACCOUNT_1],
278+
},
279+
async ({ controller, messenger }) => {
280+
mockedGetTokenBalancesForMultipleAddresses.mockResolvedValueOnce({
281+
tokenBalances: {
282+
'0x0000000000000000000000000000000000000000': {
283+
[CHECKSUM_ADDRESS_1]: new BN('abcdef', 16),
284+
},
285+
},
286+
stakedBalances: {},
287+
});
288+
289+
messenger.publish('KeyringController:unlock');
290+
291+
await clock.tickAsync(1);
292+
293+
expect(
294+
controller.state.accountsByChainId['0x1'][CHECKSUM_ADDRESS_1].balance,
295+
).toBe('0xabcdef');
296+
},
297+
);
298+
});
299+
242300
describe('refresh', () => {
301+
it('does not refresh when fetching is disabled', async () => {
302+
const expectedState = {
303+
accountsByChainId: {
304+
'0x1': {
305+
[CHECKSUM_ADDRESS_1]: { balance: '0x0' },
306+
[CHECKSUM_ADDRESS_2]: { balance: '0x0' },
307+
},
308+
},
309+
};
310+
311+
await withController(
312+
{
313+
options: { fetchingEnabled: () => false },
314+
selectedAccount: ACCOUNT_1,
315+
listAccounts: [ACCOUNT_1, ACCOUNT_2],
316+
},
317+
async ({ controller, refresh }) => {
318+
await refresh(clock, ['mainnet'], true);
319+
320+
expect(controller.state).toStrictEqual(expectedState);
321+
},
322+
);
323+
});
324+
243325
describe('without networkClientId', () => {
244326
it('should sync addresses', async () => {
245327
await withController(
@@ -1613,6 +1695,8 @@ async function withController<ReturnValue>(
16131695
'AccountsController:selectedEvmAccountChange',
16141696
'TransactionController:unapprovedTransactionAdded',
16151697
'TransactionController:transactionConfirmed',
1698+
'NetworkController:networkAdded',
1699+
'KeyringController:unlock',
16161700
],
16171701
});
16181702

packages/assets-controllers/src/AccountTrackerController.ts

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {
33
AccountsControllerSelectedEvmAccountChangeEvent,
44
AccountsControllerGetSelectedAccountAction,
55
AccountsControllerListAccountsAction,
6-
AccountsControllerSelectedAccountChangeEvent,
76
} from '@metamask/accounts-controller';
87
import type {
98
ControllerStateChangeEvent,
@@ -16,16 +15,17 @@ import {
1615
toChecksumHexAddress,
1716
} from '@metamask/controller-utils';
1817
import EthQuery from '@metamask/eth-query';
18+
import type { KeyringControllerUnlockEvent } from '@metamask/keyring-controller';
1919
import type { InternalAccount } from '@metamask/keyring-internal-api';
2020
import type { Messenger } from '@metamask/messenger';
2121
import type {
2222
NetworkClient,
2323
NetworkClientId,
2424
NetworkControllerGetNetworkClientByIdAction,
2525
NetworkControllerGetStateAction,
26+
NetworkControllerNetworkAddedEvent,
2627
} from '@metamask/network-controller';
2728
import { StaticIntervalPollingController } from '@metamask/polling-controller';
28-
import type { PreferencesControllerGetStateAction } from '@metamask/preferences-controller';
2929
import type {
3030
TransactionControllerTransactionConfirmedEvent,
3131
TransactionControllerUnapprovedTransactionAddedEvent,
@@ -174,7 +174,10 @@ export type AccountTrackerControllerActions =
174174
*/
175175
export type AllowedActions =
176176
| AccountsControllerListAccountsAction
177-
| PreferencesControllerGetStateAction
177+
| {
178+
type: 'PreferencesController:getState';
179+
handler: () => { isMultiAccountBalancesEnabled: boolean };
180+
}
178181
| AccountsControllerGetSelectedAccountAction
179182
| NetworkControllerGetStateAction
180183
| NetworkControllerGetNetworkClientByIdAction;
@@ -199,9 +202,10 @@ export type AccountTrackerControllerEvents =
199202
*/
200203
export type AllowedEvents =
201204
| AccountsControllerSelectedEvmAccountChangeEvent
202-
| AccountsControllerSelectedAccountChangeEvent
203205
| TransactionControllerUnapprovedTransactionAddedEvent
204-
| TransactionControllerTransactionConfirmedEvent;
206+
| TransactionControllerTransactionConfirmedEvent
207+
| NetworkControllerNetworkAddedEvent
208+
| KeyringControllerUnlockEvent;
205209

206210
/**
207211
* The messenger of the {@link AccountTrackerController}.
@@ -236,6 +240,8 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
236240

237241
readonly #balanceFetchers: BalanceFetcher[];
238242

243+
readonly #fetchingEnabled: () => boolean;
244+
239245
/**
240246
* Creates an AccountTracker instance.
241247
*
@@ -247,6 +253,7 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
247253
* @param options.includeStakedAssets - Whether to include staked assets in the account balances.
248254
* @param options.accountsApiChainIds - Function that returns array of chainIds that should use Accounts-API strategy (if supported by API).
249255
* @param options.allowExternalServices - Disable external HTTP calls (privacy / offline mode).
256+
* @param options.fetchingEnabled - Function that returns whether the controller is fetching enabled.
250257
*/
251258
constructor({
252259
interval = 10000,
@@ -256,6 +263,7 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
256263
includeStakedAssets = false,
257264
accountsApiChainIds = () => [],
258265
allowExternalServices = () => true,
266+
fetchingEnabled = () => true,
259267
}: {
260268
interval?: number;
261269
state?: Partial<AccountTrackerControllerState>;
@@ -264,6 +272,7 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
264272
includeStakedAssets?: boolean;
265273
accountsApiChainIds?: () => ChainIdHex[];
266274
allowExternalServices?: () => boolean;
275+
fetchingEnabled?: () => boolean;
267276
}) {
268277
const { selectedNetworkClientId } = messenger.call(
269278
'NetworkController:getState',
@@ -302,6 +311,8 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
302311
),
303312
];
304313

314+
this.#fetchingEnabled = fetchingEnabled;
315+
305316
this.setIntervalLength(interval);
306317

307318
this.messenger.subscribe(
@@ -316,23 +327,39 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
316327
(event): string => event.address,
317328
);
318329

330+
this.messenger.subscribe('NetworkController:networkAdded', async () => {
331+
await this.refresh(this.#getNetworkClientIds());
332+
});
333+
334+
this.messenger.subscribe('KeyringController:unlock', async () => {
335+
await this.refresh(this.#getNetworkClientIds());
336+
});
337+
319338
this.messenger.subscribe(
320339
'TransactionController:unapprovedTransactionAdded',
321340
async (transactionMeta: TransactionMeta) => {
322-
await this.#refreshAddress(
323-
[transactionMeta.networkClientId],
324-
transactionMeta.txParams.from,
325-
);
341+
const addresses = [transactionMeta.txParams.from];
342+
if (transactionMeta.txParams.to) {
343+
addresses.push(transactionMeta.txParams.to);
344+
}
345+
await this.refreshAddresses({
346+
networkClientIds: [transactionMeta.networkClientId],
347+
addresses,
348+
});
326349
},
327350
);
328351

329352
this.messenger.subscribe(
330353
'TransactionController:transactionConfirmed',
331354
async (transactionMeta: TransactionMeta) => {
332-
await this.#refreshAddress(
333-
[transactionMeta.networkClientId],
334-
transactionMeta.txParams.from,
335-
);
355+
const addresses = [transactionMeta.txParams.from];
356+
if (transactionMeta.txParams.to) {
357+
addresses.push(transactionMeta.txParams.to);
358+
}
359+
await this.refreshAddresses({
360+
networkClientIds: [transactionMeta.networkClientId],
361+
addresses,
362+
});
336363
},
337364
);
338365

@@ -540,13 +567,28 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
540567
});
541568
}
542569

543-
async #refreshAddress(networkClientIds: NetworkClientId[], address: string) {
544-
const checksumAddress = toChecksumHexAddress(address) as ChecksumAddress;
570+
async refreshAddresses({
571+
networkClientIds,
572+
addresses,
573+
}: {
574+
networkClientIds: NetworkClientId[];
575+
addresses: string[];
576+
}) {
577+
const checksummedAddresses = addresses.map((address) =>
578+
toChecksumHexAddress(address),
579+
);
580+
581+
const accounts = this.messenger
582+
.call('AccountsController:listAccounts')
583+
.filter((account) =>
584+
checksummedAddresses.includes(toChecksumHexAddress(account.address)),
585+
);
586+
545587
await this.#refreshAccounts({
546588
networkClientIds,
547-
queryAllAccounts: false,
548-
selectedAccount: checksumAddress,
549-
allAccounts: [],
589+
queryAllAccounts: true,
590+
selectedAccount: '0x0',
591+
allAccounts: accounts,
550592
});
551593
}
552594

@@ -570,6 +612,10 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
570612

571613
this.syncAccounts(chainIds);
572614

615+
if (!this.#fetchingEnabled()) {
616+
return;
617+
}
618+
573619
// Use balance fetchers with fallback strategy
574620
const aggregated: ProcessedBalance[] = [];
575621
let remainingChains = [...chainIds] as ChainIdHex[];

0 commit comments

Comments
 (0)