From ea074cb6d4ca1f886020492a81c32bce8caf2880 Mon Sep 17 00:00:00 2001 From: Kriys94 Date: Thu, 29 Jan 2026 16:09:55 +0100 Subject: [PATCH] feat(AssetController): add hiddenAssets state --- .../src/AssetsController.test.ts | 2 + .../assets-controller/src/AssetsController.ts | 142 +++++++++++++++++- .../src/data-sources/RpcDataSource.test.ts | 1 + .../src/data-sources/RpcDataSource.ts | 3 + .../services/BalanceFetcher.ts | 8 + .../evm-rpc-services/types/state.ts | 2 + packages/assets-controller/src/index.ts | 3 + packages/assets-controller/src/types.ts | 4 + 8 files changed, 163 insertions(+), 2 deletions(-) diff --git a/packages/assets-controller/src/AssetsController.test.ts b/packages/assets-controller/src/AssetsController.test.ts index ae42b3336a1..a354fc31662 100644 --- a/packages/assets-controller/src/AssetsController.test.ts +++ b/packages/assets-controller/src/AssetsController.test.ts @@ -201,6 +201,7 @@ describe('AssetsController', () => { assetsBalance: {}, assetsPrice: {}, customAssets: {}, + hiddenAssets: {}, }); }); }); @@ -213,6 +214,7 @@ describe('AssetsController', () => { assetsBalance: {}, assetsPrice: {}, customAssets: {}, + hiddenAssets: {}, }); }); }); diff --git a/packages/assets-controller/src/AssetsController.ts b/packages/assets-controller/src/AssetsController.ts index 6d70ba1ee92..cfcf18d975b 100644 --- a/packages/assets-controller/src/AssetsController.ts +++ b/packages/assets-controller/src/AssetsController.ts @@ -90,12 +90,14 @@ export type AssetsControllerState = { assetsPrice: { [assetId: string]: Json }; /** Custom assets added by users per account (CAIP-19 asset IDs) */ customAssets: { [accountId: string]: string[] }; + /** Hidden assets per account (CAIP-19 asset IDs) - assets user chose to hide */ + hiddenAssets: { [accountId: string]: string[] }; }; /** * Returns the default state for AssetsController. * - * @returns The default AssetsController state with empty metadata, balance, price, and customAssets maps. + * @returns The default AssetsController state with empty metadata, balance, price, customAssets, and hiddenAssets maps. */ export function getDefaultAssetsControllerState(): AssetsControllerState { return { @@ -103,6 +105,7 @@ export function getDefaultAssetsControllerState(): AssetsControllerState { assetsBalance: {}, assetsPrice: {}, customAssets: {}, + hiddenAssets: {}, }; } @@ -160,6 +163,21 @@ export type AssetsControllerGetCustomAssetsAction = { handler: AssetsController['getCustomAssets']; }; +export type AssetsControllerHideAssetAction = { + type: `${typeof CONTROLLER_NAME}:hideAsset`; + handler: AssetsController['hideAsset']; +}; + +export type AssetsControllerUnhideAssetAction = { + type: `${typeof CONTROLLER_NAME}:unhideAsset`; + handler: AssetsController['unhideAsset']; +}; + +export type AssetsControllerGetHiddenAssetsAction = { + type: `${typeof CONTROLLER_NAME}:getHiddenAssets`; + handler: AssetsController['getHiddenAssets']; +}; + export type AssetsControllerActions = | AssetsControllerGetStateAction | AssetsControllerGetAssetsAction @@ -170,7 +188,10 @@ export type AssetsControllerActions = | AssetsControllerAssetsUpdateAction | AssetsControllerAddCustomAssetAction | AssetsControllerRemoveCustomAssetAction - | AssetsControllerGetCustomAssetsAction; + | AssetsControllerGetCustomAssetsAction + | AssetsControllerHideAssetAction + | AssetsControllerUnhideAssetAction + | AssetsControllerGetHiddenAssetsAction; export type AssetsControllerStateChangeEvent = ControllerStateChangeEvent< typeof CONTROLLER_NAME, @@ -290,6 +311,12 @@ const stateMetadata: StateMetadata = { includeInDebugSnapshot: false, usedInUi: true, }, + hiddenAssets: { + persist: true, + includeInStateLogs: false, + includeInDebugSnapshot: false, + usedInUi: true, + }, }; // ============================================================================ @@ -615,6 +642,21 @@ export class AssetsController extends BaseController< 'AssetsController:getCustomAssets', this.getCustomAssets.bind(this), ); + + this.messenger.registerActionHandler( + 'AssetsController:hideAsset', + this.hideAsset.bind(this), + ); + + this.messenger.registerActionHandler( + 'AssetsController:unhideAsset', + this.unhideAsset.bind(this), + ); + + this.messenger.registerActionHandler( + 'AssetsController:getHiddenAssets', + this.getHiddenAssets.bind(this), + ); } // ============================================================================ @@ -860,6 +902,7 @@ export class AssetsController extends BaseController< /** * Add a custom asset for an account. * Custom assets are included in subscription and fetch operations. + * Adding a custom asset also unhides it if it was previously hidden. * * @param accountId - The account ID to add the custom asset for. * @param assetId - The CAIP-19 asset ID to add. @@ -882,6 +925,19 @@ export class AssetsController extends BaseController< if (!customAssets[accountId].includes(normalizedAssetId)) { customAssets[accountId].push(normalizedAssetId); } + + // Remove from hidden assets if it was hidden (unhide the asset) + const hiddenAssets = state.hiddenAssets as Record; + if (hiddenAssets[accountId]) { + hiddenAssets[accountId] = hiddenAssets[accountId].filter( + (id) => id !== normalizedAssetId, + ); + + // Clean up empty arrays + if (hiddenAssets[accountId].length === 0) { + delete hiddenAssets[accountId]; + } + } }); // Fetch data for the newly added custom asset @@ -931,6 +987,85 @@ export class AssetsController extends BaseController< return (this.state.customAssets[accountId] ?? []) as Caip19AssetId[]; } + // ============================================================================ + // HIDDEN ASSETS MANAGEMENT + // ============================================================================ + + /** + * Hide an asset for an account. + * Hidden assets are excluded from the asset list returned by getAssets. + * + * @param accountId - The account ID to hide the asset for. + * @param assetId - The CAIP-19 asset ID to hide. + */ + hideAsset(accountId: AccountId, assetId: Caip19AssetId): void { + const normalizedAssetId = normalizeAssetId(assetId); + + log('Hiding asset', { accountId, assetId: normalizedAssetId }); + + this.update((state) => { + const hiddenAssets = state.hiddenAssets as Record; + if (!hiddenAssets[accountId]) { + hiddenAssets[accountId] = []; + } + + // Only add if not already present + if (!hiddenAssets[accountId].includes(normalizedAssetId)) { + hiddenAssets[accountId].push(normalizedAssetId); + } + }); + } + + /** + * Unhide an asset for an account. + * + * @param accountId - The account ID to unhide the asset for. + * @param assetId - The CAIP-19 asset ID to unhide. + */ + unhideAsset(accountId: AccountId, assetId: Caip19AssetId): void { + const normalizedAssetId = normalizeAssetId(assetId); + + log('Unhiding asset', { accountId, assetId: normalizedAssetId }); + + this.update((state) => { + const hiddenAssets = state.hiddenAssets as Record; + if (hiddenAssets[accountId]) { + hiddenAssets[accountId] = hiddenAssets[accountId].filter( + (id) => id !== normalizedAssetId, + ); + + // Clean up empty arrays + if (hiddenAssets[accountId].length === 0) { + delete hiddenAssets[accountId]; + } + } + }); + } + + /** + * Get all hidden assets for an account. + * + * @param accountId - The account ID to get hidden assets for. + * @returns Array of CAIP-19 asset IDs for the account's hidden assets. + */ + getHiddenAssets(accountId: AccountId): Caip19AssetId[] { + return (this.state.hiddenAssets[accountId] ?? []) as Caip19AssetId[]; + } + + /** + * Check if an asset is hidden for an account. + * + * @param accountId - The account ID to check. + * @param assetId - The CAIP-19 asset ID to check. + * @returns True if the asset is hidden, false otherwise. + */ + isAssetHidden(accountId: AccountId, assetId: Caip19AssetId): boolean { + const normalizedAssetId = normalizeAssetId(assetId); + return ( + this.state.hiddenAssets[accountId]?.includes(normalizedAssetId) ?? false + ); + } + // ============================================================================ // SUBSCRIPTIONS // ============================================================================ @@ -1771,5 +1906,8 @@ export class AssetsController extends BaseController< 'AssetsController:removeCustomAsset', ); this.messenger.unregisterActionHandler('AssetsController:getCustomAssets'); + this.messenger.unregisterActionHandler('AssetsController:hideAsset'); + this.messenger.unregisterActionHandler('AssetsController:unhideAsset'); + this.messenger.unregisterActionHandler('AssetsController:getHiddenAssets'); } } diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.test.ts b/packages/assets-controller/src/data-sources/RpcDataSource.test.ts index d527bcd8bd2..425bc6c3d83 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.test.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.test.ts @@ -192,6 +192,7 @@ async function withController( allIgnoredTokens: {}, assetsMetadata: {}, assetsBalance: {}, + hiddenAssets: {}, })); // Mock TokenListController:getState diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.ts b/packages/assets-controller/src/data-sources/RpcDataSource.ts index 4492949b8f1..76c8e56787c 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.ts @@ -179,6 +179,7 @@ type AssetsControllerGetStateAction = { allIgnoredTokens: Record>; assetsMetadata: Record; assetsBalance: Record>; + hiddenAssets: Record; }; }; @@ -346,6 +347,7 @@ export class RpcDataSource extends BaseController< _action: 'AssetsController:getState', ): { assetsBalance: Record>; + hiddenAssets?: Record; } => { const state = this.messenger.call('AssetsController:getState'); return { @@ -353,6 +355,7 @@ export class RpcDataSource extends BaseController< string, Record >, + hiddenAssets: state.hiddenAssets ?? {}, }; }, }; diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 4e02e5a106e..3e89b33f793 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -123,6 +123,9 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly