Skip to content

Commit 5c58a8c

Browse files
authored
feat: add assets migration selectors (#29423)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> - Adds assets migration selectors (same ones used for extension). - Replaces use of multiple instances that directly access the state with one of the selectors. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-2827 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I've included tests if applicable - [X] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches multiple core selectors that feed balances, token lists, and pricing into Bridge/asset UI; while gated by the `assetsUnifyState` flag, any selector mismatch could surface incorrect amounts/rates across the app. > > **Overview** > Introduces a new `selectors/assets/assets-migration.ts` layer that, when `assetsUnifyState` is enabled, derives legacy controller-shaped data (tokens, token balances, account tracker balances, multichain balances/assets, currency rates, token market data, and conversion rates) from the new `AssetsController` unified state; when disabled it falls back to the existing controller state. > > Updates Bridge selectors, multichain selectors, asset list selectors, currency/token balance/token rate selectors, AccountTracker selectors, and Sentry trace tagging to consume these migration selectors instead of directly reading `engine.backgroundState` controller slices. > > Adjusts `assetsControllerInit` to pass through persisted `AssetsController` state as-is (undefined when absent rather than a synthesized empty object) and simplifies the `assetsUnifyState` feature-flag evaluation by removing app `minimumVersion` checks; corresponding tests are updated and a comprehensive test suite is added for the migration selectors. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a700851. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent cc44460 commit 5c58a8c

19 files changed

Lines changed: 3408 additions & 370 deletions

app/core/Engine/controllers/assets-controller/assets-controller-init.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ describe('assetsControllerInit', () => {
160160
expect(controllerMock).toHaveBeenCalledWith(
161161
expect.objectContaining({
162162
messenger: expect.any(Object),
163-
state: expect.any(Object),
164163
isBasicFunctionality: expect.any(Function),
165164
isEnabled: expect.any(Function),
166165
isOnboarded: expect.any(Function),
@@ -199,7 +198,7 @@ describe('assetsControllerInit', () => {
199198
);
200199
});
201200

202-
it('uses empty state when persisted state is not available', () => {
201+
it('passes undefined state when persisted state is not available', () => {
203202
const requestMock = getInitRequestMock();
204203
requestMock.persistedState = {};
205204

@@ -208,7 +207,7 @@ describe('assetsControllerInit', () => {
208207
const controllerMock = jest.mocked(AssetsController);
209208
expect(controllerMock).toHaveBeenCalledWith(
210209
expect.objectContaining({
211-
state: expect.any(Object),
210+
state: undefined,
212211
}),
213212
);
214213
});

app/core/Engine/controllers/assets-controller/assets-controller-init.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,7 @@ export const assetsControllerInit: MessengerClientInitFunction<
129129
// Create the controller - it now creates all data sources internally
130130
const controller = new AssetsController({
131131
messenger: controllerMessenger,
132-
state: persistedState?.AssetsController ?? {
133-
assetPreferences: {},
134-
assetsInfo: {},
135-
assetsBalance: {},
136-
},
132+
state: persistedState?.AssetsController,
137133
isBasicFunctionality: () =>
138134
selectBasicFunctionalityEnabled(store.getState()),
139135
isEnabled,

app/core/redux/slices/bridge/index.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ import {
3232
import { analytics } from '../../../../util/analytics/analytics';
3333
import { selectRemoteFeatureFlags } from '../../../../selectors/featureFlagController';
3434
import { getTokenExchangeRate } from '../../../../components/UI/Bridge/utils/exchange-rates';
35+
import {
36+
getMultichainAssetsRatesControllerConversionRates,
37+
getTokenRatesControllerMarketData,
38+
getCurrencyRateControllerCurrencyRates,
39+
getCurrencyRateControllerCurrentCurrency,
40+
} from '../../../../selectors/assets/assets-migration';
3541
import { selectCanSignTransactions } from '../../../../selectors/accountsController';
3642
import { selectBasicFunctionalityEnabled } from '../../../../selectors/settings';
3743
import { hasMinimumRequiredVersion } from './utils/hasMinimumRequiredVersion';
@@ -467,9 +473,17 @@ const selectControllerFields = (state: RootState) => ({
467473
gasFeeEstimatesByChainId:
468474
state.engine.backgroundState.GasFeeController.gasFeeEstimatesByChainId ??
469475
{},
470-
...state.engine.backgroundState.MultichainAssetsRatesController,
471-
...state.engine.backgroundState.TokenRatesController,
472-
...state.engine.backgroundState.CurrencyRateController,
476+
...{
477+
conversionRates: getMultichainAssetsRatesControllerConversionRates(state),
478+
historicalPrices: {},
479+
},
480+
...{
481+
marketData: getTokenRatesControllerMarketData(state),
482+
},
483+
...{
484+
currencyRates: getCurrencyRateControllerCurrencyRates(state),
485+
currentCurrency: getCurrencyRateControllerCurrentCurrency(state),
486+
},
473487
participateInMetaMetrics: analytics.isEnabled(),
474488
remoteFeatureFlags: {
475489
bridgeConfig: selectRemoteFeatureFlags(state).bridgeConfig,

app/selectors/accountTrackerController.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
import { createSelector } from 'reselect';
2-
import { AccountTrackerControllerState } from '@metamask/assets-controllers';
32
import { RootState } from '../reducers';
43
import { createDeepEqualSelector } from './util';
54
import { selectEvmChainId } from './networkController';
65
import { selectSelectedInternalAccountFormattedAddress } from './accountsController';
76
import { Hex } from '@metamask/utils';
7+
import { getAccountTrackerControllerAccountsByChainId } from './assets/assets-migration';
88

9-
const selectAccountTrackerControllerState = (state: RootState) =>
10-
state.engine.backgroundState.AccountTrackerController;
11-
12-
export const selectAccountsByChainId = createDeepEqualSelector(
13-
selectAccountTrackerControllerState,
14-
(accountTrackerControllerState: AccountTrackerControllerState) =>
15-
accountTrackerControllerState?.accountsByChainId ?? {},
16-
);
9+
export { getAccountTrackerControllerAccountsByChainId as selectAccountsByChainId };
1710

1811
export const selectAccounts = createDeepEqualSelector(
19-
selectAccountsByChainId,
12+
getAccountTrackerControllerAccountsByChainId,
2013
selectEvmChainId,
21-
selectSelectedInternalAccountFormattedAddress,
2214
(accountsByChainId, chainId) => accountsByChainId?.[chainId] || {},
2315
);
2416

@@ -28,7 +20,7 @@ export const selectAccountsLength = createSelector(
2820
);
2921

3022
export const selectAccountBalanceByChainId = createDeepEqualSelector(
31-
selectAccountsByChainId,
23+
getAccountTrackerControllerAccountsByChainId,
3224
selectEvmChainId,
3325
selectSelectedInternalAccountFormattedAddress,
3426
(_state: RootState, chainId?: Hex) => chainId,

app/selectors/assets/assets-list.ts

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ import { selectAllTokens } from '../tokensController';
4343
import { selectSelectedInternalAccountAddress } from '../accountsController';
4444
import { selectSelectedInternalAccountByScope } from '../multichainAccounts/accounts';
4545
import { getLocaleLanguageCode } from '../../components/hooks/useFormatters';
46+
import {
47+
getMultichainAssetsRatesControllerConversionRates,
48+
getTokenRatesControllerMarketData,
49+
getCurrencyRateControllerCurrencyRates,
50+
getCurrencyRateControllerCurrentCurrency,
51+
getTokensControllerAllTokens,
52+
getTokensControllerAllIgnoredTokens,
53+
getAccountTrackerControllerAccountsByChainId,
54+
getTokenBalancesControllerTokenBalances,
55+
getMultiChainBalancesControllerBalances,
56+
getMultiChainAssetsControllerAccountsAssets,
57+
getMultiChainAssetsControllerAllIgnoredAssets,
58+
getMultiChainAssetsControllerAssetsMetadata,
59+
} from './assets-migration';
4660

4761
/**
4862
* Structured map of Tron special assets for efficient access.
@@ -92,58 +106,35 @@ const EMPTY_TRON_SPECIAL_ASSETS_MAP: TronSpecialAssetsMap = Object.freeze({
92106
});
93107

94108
const getStateForAssetSelector = (state: RootState) => {
95-
const {
96-
AccountTreeController,
97-
AccountsController,
98-
TokensController,
99-
TokenBalancesController,
100-
TokenRatesController,
101-
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
102-
MultichainAssetsController,
103-
MultichainBalancesController,
104-
MultichainAssetsRatesController,
105-
///: END:ONLY_INCLUDE_IF
106-
CurrencyRateController,
107-
NetworkController,
108-
AccountTrackerController,
109-
} = state.engine.backgroundState;
110-
111-
let multichainState = {
112-
accountsAssets: {},
113-
assetsMetadata: {},
114-
allIgnoredAssets: {},
115-
balances: {},
116-
conversionRates: {},
117-
};
118-
119-
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
120-
multichainState = {
121-
...MultichainAssetsController,
122-
...MultichainBalancesController,
123-
...MultichainAssetsRatesController,
124-
};
125-
///: END:ONLY_INCLUDE_IF
109+
const { AccountTreeController, AccountsController, NetworkController } =
110+
state.engine.backgroundState;
126111

127112
return {
128113
...AccountTreeController,
129114
...AccountsController,
130-
...TokensController,
131-
...TokenBalancesController,
132-
...TokenRatesController,
133-
...multichainState,
134-
...CurrencyRateController,
115+
allTokens: getTokensControllerAllTokens(state),
116+
allIgnoredTokens: getTokensControllerAllIgnoredTokens(state),
117+
tokenBalances: getTokenBalancesControllerTokenBalances(state),
118+
marketData: getTokenRatesControllerMarketData(state),
119+
assetsMetadata: getMultiChainAssetsControllerAssetsMetadata(state),
120+
accountsAssets: getMultiChainAssetsControllerAccountsAssets(state),
121+
allIgnoredAssets: getMultiChainAssetsControllerAllIgnoredAssets(state),
122+
balances: getMultiChainBalancesControllerBalances(state),
123+
conversionRates: getMultichainAssetsRatesControllerConversionRates(state),
124+
currencyRates: getCurrencyRateControllerCurrencyRates(state),
125+
currentCurrency: getCurrencyRateControllerCurrentCurrency(state),
135126
...NetworkController,
136-
...(AccountTrackerController as {
137-
accountsByChainId: Record<
127+
accountsByChainId: getAccountTrackerControllerAccountsByChainId(
128+
state,
129+
) as Record<
130+
Hex,
131+
Record<
138132
Hex,
139-
Record<
140-
Hex,
141-
{
142-
balance: Hex | null;
143-
}
144-
>
145-
>;
146-
}),
133+
{
134+
balance: Hex | null;
135+
}
136+
>
137+
>,
147138
};
148139
};
149140

0 commit comments

Comments
 (0)