-
-
Notifications
You must be signed in to change notification settings - Fork 268
feat(AssetController): add hiddenAssets state #7777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,19 +90,22 @@ 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 { | ||
| assetsMetadata: {}, | ||
| 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<AssetsControllerState> = { | |
| 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<string, string[]>; | ||
| 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<string, string[]>; | ||
| 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<string, string[]>; | ||
| 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 | ||
| ); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method
|
||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getAssets does not filter out hidden assetsHigh Severity The Additional Locations (1) |
||
| // ============================================================================ | ||
| // 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'); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,6 +179,7 @@ type AssetsControllerGetStateAction = { | |
| allIgnoredTokens: Record<string, Record<string, string[]>>; | ||
| assetsMetadata: Record<Caip19AssetId, AssetMetadata>; | ||
| assetsBalance: Record<string, Record<string, { amount: string }>>; | ||
| hiddenAssets: Record<string, string[]>; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit (not a blocker) - I wonder if we add the JSDOC like we did for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (optional, needs discussion), the Accounts team export a type alias, that could assist with readability export type AccountId = string;...Hesitant since it doesn't actually provide any additional type safety... maybe JSDOC is better?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I actually much more prefer the approach you used below in packages/assets-controller/src/AssetsController.ts type Foo = { [accountId: string]: string[] };I think this is easier to read/understand then |
||
| }; | ||
| }; | ||
|
|
||
|
|
@@ -346,13 +347,15 @@ export class RpcDataSource extends BaseController< | |
| _action: 'AssetsController:getState', | ||
| ): { | ||
| assetsBalance: Record<string, Record<string, { amount: string }>>; | ||
| hiddenAssets?: Record<string, string[]>; | ||
| } => { | ||
| const state = this.messenger.call('AssetsController:getState'); | ||
| return { | ||
| assetsBalance: (state.assetsBalance ?? {}) as Record< | ||
| string, | ||
| Record<string, { amount: string }> | ||
| >, | ||
| hiddenAssets: state.hiddenAssets ?? {}, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,13 +123,21 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalanceP | |
| return []; | ||
| } | ||
|
|
||
| // Get hidden assets for this account | ||
| const hiddenAssets = new Set(state.hiddenAssets?.[accountId] ?? []); | ||
|
|
||
| // Convert hex chainId to decimal for CAIP-2 matching | ||
| const chainIdDecimal = parseInt(chainId, 16); | ||
| const caipChainPrefix = `eip155:${chainIdDecimal}/`; | ||
|
|
||
| const tokenMap = new Map<string, TokenFetchInfo>(); | ||
|
|
||
| for (const assetId of Object.keys(accountBalances)) { | ||
| // Skip hidden assets | ||
| if (hiddenAssets.has(assetId)) { | ||
| continue; | ||
| } | ||
|
Comment on lines
+137
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm does this mean we don't fetch and update balances for hidden assets? High level, my initial thoughts are that "Hidden Assets" are a UI lever/control, we should still be performing data fetching if there are places that do show this asset. |
||
|
|
||
| // Only process ERC20 tokens on the current chain | ||
| if (assetId.startsWith(caipChainPrefix) && assetId.includes('/erc20:')) { | ||
| // Parse token address from CAIP-19: eip155:1/erc20:0x... | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some tests on the new exposed methods from this controller?