diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 30d3fa59a8b..3f784352634 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -552,7 +552,7 @@ }, "packages/assets-controllers/src/selectors/token-selectors.ts": { "@typescript-eslint/explicit-function-return-type": { - "count": 25 + "count": 24 }, "@typescript-eslint/prefer-nullish-coalescing": { "count": 1 diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 48312a9ce0e..21fd6bc8681 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Add `useExternalServices` option to `selectAssetsBySelectedAccountGroup` and `selectAllAssets` selectors ([#7545](https://github.com/MetaMask/core/pull/7545)) + - When `useExternalServices` is `false`, non-EVM (multichain) assets are excluded from the result + - This allows callers to respect the basic functionality / external services toggle + - Defaults to `true` to maintain backward compatibility - **BREAKING:** `AccountTrackerController` now requires `KeyringController:getState` action and `KeyringController:lock` event in addition to existing allowed actions and events ([#7492](https://github.com/MetaMask/core/pull/7492)) - Added `#isLocked` property to track keyring lock state, initialized from `KeyringController:getState` - Added `isActive` getter that returns `true` when keyring is unlocked and user is onboarded diff --git a/packages/assets-controllers/src/selectors/token-selectors.test.ts b/packages/assets-controllers/src/selectors/token-selectors.test.ts index 0bd2892f3bb..a8593ec1d35 100644 --- a/packages/assets-controllers/src/selectors/token-selectors.test.ts +++ b/packages/assets-controllers/src/selectors/token-selectors.test.ts @@ -922,6 +922,54 @@ describe('token-selectors', () => { expect(result).toStrictEqual(expectedMockResult); }); + describe('useExternalServices option', () => { + it('excludes non-EVM (multichain) assets when useExternalServices is false', () => { + const result = selectAssetsBySelectedAccountGroup(mockedMergedState, { + filterTronStakedTokens: true, + useExternalServices: false, + }); + + // Should only include EVM chains (0x1, 0xa) - no Solana + expect(Object.keys(result)).toStrictEqual(['0x1', '0xa']); + + // Verify EVM assets are still present + expect(result['0x1']).toHaveLength(3); // GHO, SUSHI, ETH native + expect(result['0xa']).toHaveLength(2); // USDC, ETH native + + // Verify Solana assets are excluded + expect( + result['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + ).toBeUndefined(); + }); + + it('includes non-EVM (multichain) assets when useExternalServices is true', () => { + const result = selectAssetsBySelectedAccountGroup(mockedMergedState, { + filterTronStakedTokens: true, + useExternalServices: true, + }); + + // Should include all chains including Solana + expect(Object.keys(result)).toContain( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + + // Verify Solana assets are present + expect(result['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']).toHaveLength( + 2, + ); // SOL native, JUP token + }); + + it('uses default useExternalServices: true when not specified', () => { + // Call without opts - should use defaults (useExternalServices: true) + const result = selectAssetsBySelectedAccountGroup(mockedMergedState); + + // Should include Solana assets (multichain) + expect(Object.keys(result)).toContain( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + ); + }); + }); + const arrangeTronState = () => { const state = cloneDeep(mockedMergedState); @@ -1018,6 +1066,7 @@ describe('token-selectors', () => { const result = selectAssetsBySelectedAccountGroup(state, { filterTronStakedTokens: false, + useExternalServices: true, }); expect(result[TrxScope.Mainnet].length > 1).toBe(true); diff --git a/packages/assets-controllers/src/selectors/token-selectors.ts b/packages/assets-controllers/src/selectors/token-selectors.ts index a1772dfa6cd..51272252cd4 100644 --- a/packages/assets-controllers/src/selectors/token-selectors.ts +++ b/packages/assets-controllers/src/selectors/token-selectors.ts @@ -334,6 +334,18 @@ const selectAllEvmAssets = createAssetListSelector( }, ); +export type SelectAllAssetsOpts = { + /** + * When false, non-EVM (multichain) assets are excluded. + * This should be set to false when basic functionality / external services are disabled. + */ + useExternalServices: boolean; +}; + +const defaultSelectAllAssetsOpts: SelectAllAssetsOpts = { + useExternalServices: true, +}; + const selectAllMultichainAssets = createAssetListSelector( [ selectAccountsToGroupIdMap, @@ -343,6 +355,10 @@ const selectAllMultichainAssets = createAssetListSelector( (state) => state.balances, (state) => state.conversionRates, (state) => state.currentCurrency, + ( + _state, + opts: SelectAllAssetsOpts = defaultSelectAllAssetsOpts, + ): SelectAllAssetsOpts => opts, ], ( accountsMap, @@ -352,7 +368,13 @@ const selectAllMultichainAssets = createAssetListSelector( multichainBalances, multichainConversionRates, currentCurrency, + opts, ) => { + // Short-circuit when external services are disabled - no multichain assets needed + if (!opts.useExternalServices) { + return {}; + } + const groupAssets: AssetsByAccountGroup = {}; for (const [accountId, accountAssets] of Object.entries(multichainTokens)) { @@ -444,7 +466,10 @@ const selectAllMultichainAssets = createAssetListSelector( export const selectAllAssets = createAssetListSelector( [ selectAllEvmAssets, - selectAllMultichainAssets, + ( + state, + opts: SelectAllAssetsOpts = defaultSelectAllAssetsOpts, + ): AssetsByAccountGroup => selectAllMultichainAssets(state, opts), selectAllEvmAccountNativeBalances, ], (evmAssets, multichainAssets, evmAccountNativeBalances) => { @@ -462,10 +487,16 @@ export const selectAllAssets = createAssetListSelector( export type SelectAccountGroupAssetOpts = { filterTronStakedTokens: boolean; + /** + * When false, non-EVM chains are filtered out. + * This should be set to false when basic functionality / external services are disabled. + */ + useExternalServices: boolean; }; const defaultSelectAccountGroupAssetOpts: SelectAccountGroupAssetOpts = { filterTronStakedTokens: true, + useExternalServices: true, }; const filterTronStakedTokens = (assetsByAccountGroup: AccountGroupAssets) => { @@ -496,12 +527,16 @@ const filterTronStakedTokens = (assetsByAccountGroup: AccountGroupAssets) => { export const selectAssetsBySelectedAccountGroup = createAssetListSelector( [ - selectAllAssets, + ( + state, + opts: SelectAccountGroupAssetOpts = defaultSelectAccountGroupAssetOpts, + ): AssetsByAccountGroup => + selectAllAssets(state, { useExternalServices: opts.useExternalServices }), (state) => state.accountTree, ( _state, opts: SelectAccountGroupAssetOpts = defaultSelectAccountGroupAssetOpts, - ) => opts, + ): SelectAccountGroupAssetOpts => opts, ], (groupAssets, accountTree, opts) => { const { selectedAccountGroup } = accountTree;