@metamask-previews/assets-controller 16.1.0-preview-b1e33e9f1 → 16.1.0-preview-d2ea3cc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +0 -28
  2. package/dist/AssetsController-method-action-types.d.ts +1 -5
  3. package/dist/AssetsController-method-action-types.d.ts.map +1 -1
  4. package/dist/AssetsController-method-action-types.js.map +1 -1
  5. package/dist/AssetsController.d.ts +40 -8
  6. package/dist/AssetsController.d.ts.map +1 -1
  7. package/dist/AssetsController.js +235 -570
  8. package/dist/AssetsController.js.map +1 -1
  9. package/dist/data-sources/AbstractDataSource.d.ts +6 -1
  10. package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
  11. package/dist/data-sources/AbstractDataSource.js.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.ts +3 -17
  13. package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.js +125 -275
  15. package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
  16. package/dist/data-sources/PriceDataSource.d.ts +6 -7
  17. package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
  18. package/dist/data-sources/PriceDataSource.js +20 -15
  19. package/dist/data-sources/PriceDataSource.js.map +1 -1
  20. package/dist/data-sources/RpcDataSource.d.ts +1 -15
  21. package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
  22. package/dist/data-sources/RpcDataSource.js +107 -398
  23. package/dist/data-sources/RpcDataSource.js.map +1 -1
  24. package/dist/data-sources/SnapDataSource.d.ts +0 -5
  25. package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
  26. package/dist/data-sources/SnapDataSource.js +0 -114
  27. package/dist/data-sources/SnapDataSource.js.map +1 -1
  28. package/dist/data-sources/TokenDataSource.d.ts +1 -3
  29. package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
  30. package/dist/data-sources/TokenDataSource.js +2 -4
  31. package/dist/data-sources/TokenDataSource.js.map +1 -1
  32. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +0 -8
  33. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
  34. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +2 -47
  35. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts +1 -3
  40. package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
  41. package/dist/middlewares/CustomAssetGraduationMiddleware.js +1 -3
  42. package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
  43. package/dist/middlewares/DetectionMiddleware.d.ts +2 -10
  44. package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
  45. package/dist/middlewares/DetectionMiddleware.js +23 -5
  46. package/dist/middlewares/DetectionMiddleware.js.map +1 -1
  47. package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
  48. package/dist/middlewares/ParallelMiddleware.js +3 -0
  49. package/dist/middlewares/ParallelMiddleware.js.map +1 -1
  50. package/dist/middlewares/RpcFallbackMiddleware.d.ts +15 -16
  51. package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
  52. package/dist/middlewares/RpcFallbackMiddleware.js +107 -125
  53. package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
  54. package/dist/middlewares/index.d.ts +0 -1
  55. package/dist/middlewares/index.d.ts.map +1 -1
  56. package/dist/middlewares/index.js.map +1 -1
  57. package/dist/migrations/healAssetsInfoMetadata.d.ts +5 -5
  58. package/dist/migrations/healAssetsInfoMetadata.d.ts.map +1 -1
  59. package/dist/migrations/healAssetsInfoMetadata.js.map +1 -1
  60. package/dist/pipeline/buildFastFetchSources.d.ts +0 -5
  61. package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
  62. package/dist/pipeline/buildFastFetchSources.js +1 -7
  63. package/dist/pipeline/buildFastFetchSources.js.map +1 -1
  64. package/dist/pipeline/executeAssetsPipeline.d.ts +3 -1
  65. package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
  66. package/dist/pipeline/executeAssetsPipeline.js +2 -1
  67. package/dist/pipeline/executeAssetsPipeline.js.map +1 -1
  68. package/dist/selectors/balance.js +1 -1
  69. package/dist/selectors/balance.js.map +1 -1
  70. package/dist/types.d.ts +23 -29
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js.map +1 -1
  73. package/package.json +1 -1
  74. package/dist/utils/assetVisibility.d.ts +0 -29
  75. package/dist/utils/assetVisibility.d.ts.map +0 -1
  76. package/dist/utils/assetVisibility.js +0 -80
  77. package/dist/utils/assetVisibility.js.map +0 -1
  78. package/dist/utils/filterFailedChainBalances.d.ts +0 -11
  79. package/dist/utils/filterFailedChainBalances.d.ts.map +0 -1
  80. package/dist/utils/filterFailedChainBalances.js +0 -22
  81. package/dist/utils/filterFailedChainBalances.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"RpcFallbackMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { KnownCaipNamespace } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from '../data-sources/evm-rpc-services/index.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetBalance,\n AssetsControllerState,\n AssetsDataSource,\n Caip19AssetId,\n ChainId,\n Context,\n DataResponse,\n Middleware,\n NextFunction,\n} from '../types.js';\nimport { filterFailedChainBalances } from '../utils/filterFailedChainBalances.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n /**\n * When true, recover failed and unprocessed chains with a full RPC refetch\n * (Accounts API v6). When false, re-read stale tracked assets the v5 API\n * omitted (legacy path).\n */\n isBalanceV6Enabled?: () => boolean;\n /** Current AssetsController state. Used to find tracked assets the response left empty. */\n getAssetsState: () => AssetsControllerState;\n};\n\nconst noopNext = async (ctx: Context): Promise<Context> => ctx;\n\n/**\n * RpcFallbackMiddleware recovers what upstream sources left outstanding on RPC.\n *\n * Accounts API v5: chains in `response.errors`, plus EVM assets tracked in\n * state that this response left empty (the API omits tokens it does not index).\n *\n * Accounts API v6: one RPC retry on the chain axis. Chains in\n * `response.errors` are refetched in full (native + existing balances + pins +\n * default tracked assets). RPC reads those from state; the request is not\n * scoped via `customAssets`.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n readonly #isBalanceV6Enabled: () => boolean;\n\n readonly #getAssetsState: () => AssetsControllerState;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n this.#isBalanceV6Enabled =\n options.isBalanceV6Enabled ?? ((): boolean => false);\n this.#getAssetsState = options.getAssetsState;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n if (this.#isBalanceV6Enabled()) {\n return this.#recoverV6(ctx, next);\n }\n return this.#recoverV5(ctx, next);\n });\n }\n\n async #recoverV6(ctx: Context, next: NextFunction): Promise<Context> {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n const chainsToFetch = ctx.request.chainIds.filter((id) =>\n erroredChains.has(id),\n );\n\n if (chainsToFetch.length === 0) {\n return next(ctx);\n }\n\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: { ...ctx.request, chainIds: chainsToFetch },\n response: {},\n },\n noopNext,\n );\n\n const rpcFailedChains = new Set<ChainId>(\n Object.keys(rpcResult.response.errors ?? {}) as ChainId[],\n );\n const rpcAssetsBalance = filterFailedChainBalances(\n rpcResult.response.assetsBalance,\n rpcFailedChains,\n );\n\n const merged = mergeDataResponses([\n ctx.response,\n {\n ...rpcResult.response,\n assetsBalance: rpcAssetsBalance,\n },\n ]);\n\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n }\n\n async #recoverV5(ctx: Context, next: NextFunction): Promise<Context> {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n const staleAssets = collectStaleTrackedAssets(ctx, this.#getAssetsState());\n\n const chainsToFetch = [\n ...new Set([\n ...ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n // Already restricted to requested chains. Their chain may not be\n // errored: the Accounts API can answer for a chain while omitting a\n // token it does not index.\n ...staleAssets.map((assetId) => assetId.split('/')[0] as ChainId),\n ]),\n ];\n\n if (chainsToFetch.length === 0) {\n return next(ctx);\n }\n\n log('Re-reading balances on RPC', {\n erroredChains: [...erroredChains],\n staleAssets,\n chains: chainsToFetch,\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: chainsToFetch,\n customAssets: [\n ...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),\n ],\n };\n\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n // A chain RPC itself failed on contributed nothing trustworthy: its\n // balances are failure stubs (native 0) that would overwrite correct\n // upstream amounts and, with replaceCoveredChainBalances, wipe the\n // chain's token slice from state. Drop them before merging — this also\n // keeps failed chains from counting as \"recovered\" below.\n const rpcFailedChains = new Set<ChainId>(\n Object.keys(rpcResult.response.errors ?? {}) as ChainId[],\n );\n const rpcAssetsBalance = filterFailedChainBalances(\n rpcResult.response.assetsBalance,\n rpcFailedChains,\n );\n\n // RPC errors are kept only for chains that were already errored\n // upstream. For chains fetched solely for stale tracked assets the\n // upstream response succeeded and stays authoritative — the stale asset\n // keeps its previous amount and is retried on the next pass.\n const rpcErrors = Object.fromEntries(\n Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) =>\n erroredChains.has(chainId as ChainId),\n ),\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n {\n ...rpcResult.response,\n assetsBalance: rpcAssetsBalance,\n errors: rpcErrors,\n },\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect the (filtered) RPC balances — NOT merged — because\n // merged also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n }\n}\n\n/**\n * EVM assets tracked in state that this balance response left empty and RPC\n * should re-read.\n *\n * Limited to chains both requested and supported by the owning account:\n * RpcDataSource fetches per account and skips chains outside its supported\n * set, so anything else would be queued and then silently dropped.\n *\n * @param ctx - Pipeline context.\n * @param state - Current AssetsController state.\n * @returns Asset IDs to hand to the RPC data source.\n */\nfunction collectStaleTrackedAssets(\n ctx: Context,\n state: AssetsControllerState,\n): Caip19AssetId[] {\n const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } =\n state;\n\n const staleAssets = new Set<Caip19AssetId>();\n\n for (const { account, supportedChains } of ctx.request\n .accountsWithSupportedChains) {\n const accountId = account.id;\n const trackedAssetIds = new Set<Caip19AssetId>([\n ...(Object.keys(stateAssetsBalance[accountId] ?? {}) as Caip19AssetId[]),\n ...(stateCustomAssets?.[accountId] ?? []),\n ]);\n\n const chainsForAccount = supportedChains.filter((chainId) =>\n ctx.request.chainIds.includes(chainId),\n );\n\n for (const assetId of trackedAssetIds) {\n if (\n isEvmAssetOnChains(assetId, chainsForAccount) &&\n // Staked vault balances belong to StakedBalanceDataSource; an RPC\n // ERC-20 read of the share token would clobber them.\n !isStakingContractAssetId(assetId) &&\n isBalanceEmpty(ctx.response.assetsBalance?.[accountId], assetId)\n ) {\n staleAssets.add(assetId);\n }\n }\n }\n\n return [...staleAssets];\n}\n\n/**\n * Whether a balance response carries no positive amount for an asset. A\n * returned `0` cannot be distinguished from \"not indexed\", so both count as\n * empty. Asset IDs are matched case-insensitively: state keys ERC-20 assets by\n * checksummed address, while some data sources return them lower-cased.\n *\n * @param balances - Balance map for a single account from the response.\n * @param assetId - Asset ID to check.\n * @returns True when the response holds no positive amount for the asset.\n */\nfunction isBalanceEmpty(\n balances: Record<string, AssetBalance> | undefined,\n assetId: Caip19AssetId,\n): boolean {\n let amount = balances?.[assetId]?.amount;\n if (amount === undefined && balances) {\n const lowerCasedAssetId = assetId.toLowerCase();\n amount = Object.entries(balances).find(\n ([id]) => id.toLowerCase() === lowerCasedAssetId,\n )?.[1]?.amount;\n }\n return !(Number(amount) > 0);\n}\n\n/**\n * Whether an asset is an EVM asset on one of the given chains.\n *\n * @param assetId - CAIP-19 asset ID.\n * @param chainIds - Chains to match against.\n * @returns True for EVM assets whose chain is in `chainIds`.\n */\nfunction isEvmAssetOnChains(\n assetId: Caip19AssetId,\n chainIds: ChainId[],\n): boolean {\n if (!assetId.startsWith(`${KnownCaipNamespace.Eip155}:`)) {\n return false;\n }\n return chainIds.includes(assetId.split('/')[0] as ChainId);\n}\n"]}
1
+ {"version":3,"file":"RpcFallbackMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { KnownCaipNamespace } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from '../data-sources/evm-rpc-services/index.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetBalance,\n AssetsDataSource,\n Caip19AssetId,\n ChainId,\n Context,\n DataResponse,\n Middleware,\n} from '../types.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n};\n\n/**\n * RpcFallbackMiddleware re-reads balances from the RPC data source in two\n * cases:\n *\n * 1. Chains present in `response.errors` (network error, unprocessedNetworks,\n * timeout, …). Successful RPC results are merged into the response and\n * their entries are cleared from `response.errors`.\n * 2. EVM assets tracked in state (`state.assetsBalance` or\n * `state.customAssets`) that this balance response left empty. The Accounts\n * API omits tokens it does not index, and a returned `0` is\n * indistinguishable from \"not indexed\", so the `merge` state update would\n * otherwise keep a stale amount forever. They are passed to RPC as\n * `customAssets` so the balance fetcher includes them in its multicall.\n *\n * Place this immediately after `createParallelBalanceMiddleware` in the fast\n * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC\n * carries never trigger graduation.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n const staleAssets = collectStaleTrackedAssets(ctx);\n\n const chainsToFetch = [\n ...new Set([\n ...ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n // Already restricted to requested chains. Their chain may not be\n // errored: the Accounts API can answer for a chain while omitting a\n // token it does not index.\n ...staleAssets.map((assetId) => assetId.split('/')[0] as ChainId),\n ]),\n ];\n\n if (chainsToFetch.length === 0) {\n return next(ctx);\n }\n\n log('Re-reading balances on RPC', {\n erroredChains: [...erroredChains],\n staleAssets,\n chains: chainsToFetch,\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: chainsToFetch,\n customAssets: [\n ...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),\n ],\n };\n\n const noopNext = async (inner: typeof ctx): Promise<typeof ctx> => inner;\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n // A chain RPC itself failed on contributed nothing trustworthy: its\n // balances are failure stubs (native 0) that would overwrite correct\n // upstream amounts and, with replaceCoveredChainBalances, wipe the\n // chain's token slice from state. Drop them before merging — this also\n // keeps failed chains from counting as \"recovered\" below.\n const rpcFailedChains = new Set<ChainId>(\n Object.keys(rpcResult.response.errors ?? {}) as ChainId[],\n );\n const rpcAssetsBalance = filterOutChainBalances(\n rpcResult.response.assetsBalance,\n rpcFailedChains,\n );\n\n // RPC errors are kept only for chains that were already errored\n // upstream. For chains fetched solely for stale tracked assets the\n // upstream response succeeded and stays authoritative — the stale asset\n // keeps its previous amount and is retried on the next pass.\n const rpcErrors = Object.fromEntries(\n Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) =>\n erroredChains.has(chainId as ChainId),\n ),\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n {\n ...rpcResult.response,\n assetsBalance: rpcAssetsBalance,\n errors: rpcErrors,\n },\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect the (filtered) RPC balances — NOT merged — because\n // merged also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n });\n }\n}\n\n/**\n * Remove all balances that belong to the given chains.\n *\n * Used to discard results for chains the RPC source itself failed on, whose\n * entries are failure stubs (native `0`) rather than real readings.\n *\n * @param assetsBalance - Balances by account from the RPC response.\n * @param chainIds - Chains whose balances should be dropped.\n * @returns The filtered balance map, without accounts left empty.\n */\nfunction filterOutChainBalances(\n assetsBalance: DataResponse['assetsBalance'],\n chainIds: Set<ChainId>,\n): DataResponse['assetsBalance'] {\n if (!assetsBalance || chainIds.size === 0) {\n return assetsBalance;\n }\n\n const filtered: NonNullable<DataResponse['assetsBalance']> = {};\n for (const [accountId, accountBalances] of Object.entries(assetsBalance)) {\n const kept = Object.fromEntries(\n Object.entries(accountBalances).filter(\n ([assetId]) => !chainIds.has(assetId.split('/')[0] as ChainId),\n ),\n ) as Record<Caip19AssetId, AssetBalance>;\n if (Object.keys(kept).length > 0) {\n filtered[accountId] = kept;\n }\n }\n return filtered;\n}\n\n/**\n * EVM assets tracked in state that this balance response left empty and RPC\n * should re-read.\n *\n * Limited to chains both requested and supported by the owning account:\n * RpcDataSource fetches per account and skips chains outside its supported\n * set, so anything else would be queued and then silently dropped.\n *\n * @param ctx - Pipeline context.\n * @returns Asset IDs to hand to the RPC data source.\n */\nfunction collectStaleTrackedAssets(ctx: Context): Caip19AssetId[] {\n const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } =\n ctx.getAssetsState();\n\n const staleAssets = new Set<Caip19AssetId>();\n\n for (const { account, supportedChains } of ctx.request\n .accountsWithSupportedChains) {\n const accountId = account.id;\n const trackedAssetIds = new Set<Caip19AssetId>([\n ...(Object.keys(stateAssetsBalance[accountId] ?? {}) as Caip19AssetId[]),\n ...(stateCustomAssets?.[accountId] ?? []),\n ]);\n\n const chainsForAccount = supportedChains.filter((chainId) =>\n ctx.request.chainIds.includes(chainId),\n );\n\n for (const assetId of trackedAssetIds) {\n if (\n isEvmAssetOnChains(assetId, chainsForAccount) &&\n // Staked vault balances belong to StakedBalanceDataSource; an RPC\n // ERC-20 read of the share token would clobber them.\n !isStakingContractAssetId(assetId) &&\n isBalanceEmpty(ctx.response.assetsBalance?.[accountId], assetId)\n ) {\n staleAssets.add(assetId);\n }\n }\n }\n\n return [...staleAssets];\n}\n\n/**\n * Whether a balance response carries no positive amount for an asset. A\n * returned `0` cannot be distinguished from \"not indexed\", so both count as\n * empty. Asset IDs are matched case-insensitively: state keys ERC-20 assets by\n * checksummed address, while some data sources return them lower-cased.\n *\n * @param balances - Balance map for a single account from the response.\n * @param assetId - Asset ID to check.\n * @returns True when the response holds no positive amount for the asset.\n */\nfunction isBalanceEmpty(\n balances: Record<string, AssetBalance> | undefined,\n assetId: Caip19AssetId,\n): boolean {\n let amount = balances?.[assetId]?.amount;\n if (amount === undefined && balances) {\n const lowerCasedAssetId = assetId.toLowerCase();\n amount = Object.entries(balances).find(\n ([id]) => id.toLowerCase() === lowerCasedAssetId,\n )?.[1]?.amount;\n }\n return !(Number(amount) > 0);\n}\n\n/**\n * Whether an asset is an EVM asset on one of the given chains.\n *\n * @param assetId - CAIP-19 asset ID.\n * @param chainIds - Chains to match against.\n * @returns True for EVM assets whose chain is in `chainIds`.\n */\nfunction isEvmAssetOnChains(\n assetId: Caip19AssetId,\n chainIds: ChainId[],\n): boolean {\n if (!assetId.startsWith(`${KnownCaipNamespace.Eip155}:`)) {\n return false;\n }\n return chainIds.includes(assetId.split('/')[0] as ChainId);\n}\n"]}
@@ -1,7 +1,6 @@
1
1
  export { CustomAssetGraduationMiddleware } from './CustomAssetGraduationMiddleware.js';
2
2
  export type { CustomAssetGraduationMiddlewareOptions } from './CustomAssetGraduationMiddleware.js';
3
3
  export { DetectionMiddleware } from './DetectionMiddleware.js';
4
- export type { DetectionMiddlewareOptions } from './DetectionMiddleware.js';
5
4
  export { RpcFallbackMiddleware } from './RpcFallbackMiddleware.js';
6
5
  export type { RpcFallbackMiddlewareOptions } from './RpcFallbackMiddleware.js';
7
6
  export { createParallelBalanceMiddleware, createParallelMiddleware, mergeDataResponses, } from './ParallelMiddleware.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AACvF,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AACvF,YAAY,EAAE,sCAAsC,EAAE,MAAM,sCAAsC,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AAEvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC","sourcesContent":["export { CustomAssetGraduationMiddleware } from './CustomAssetGraduationMiddleware.js';\nexport type { CustomAssetGraduationMiddlewareOptions } from './CustomAssetGraduationMiddleware.js';\nexport { DetectionMiddleware } from './DetectionMiddleware.js';\nexport type { DetectionMiddlewareOptions } from './DetectionMiddleware.js';\nexport { RpcFallbackMiddleware } from './RpcFallbackMiddleware.js';\nexport type { RpcFallbackMiddlewareOptions } from './RpcFallbackMiddleware.js';\nexport {\n createParallelBalanceMiddleware,\n createParallelMiddleware,\n mergeDataResponses,\n} from './ParallelMiddleware.js';\nexport type { BalanceSource } from './ParallelMiddleware.js';\nexport type { AssetsDataSource } from '../types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AAEvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC","sourcesContent":["export { CustomAssetGraduationMiddleware } from './CustomAssetGraduationMiddleware.js';\nexport type { CustomAssetGraduationMiddlewareOptions } from './CustomAssetGraduationMiddleware.js';\nexport { DetectionMiddleware } from './DetectionMiddleware.js';\nexport { RpcFallbackMiddleware } from './RpcFallbackMiddleware.js';\nexport type { RpcFallbackMiddlewareOptions } from './RpcFallbackMiddleware.js';\nexport {\n createParallelBalanceMiddleware,\n createParallelMiddleware,\n mergeDataResponses,\n} from './ParallelMiddleware.js';\nexport type { BalanceSource } from './ParallelMiddleware.js';\nexport type { AssetsDataSource } from '../types.js';\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { AccountsControllerState } from '@metamask/accounts-controller';
2
2
  import type { TokensControllerState } from '@metamask/assets-controllers';
3
3
  import type { ApiPlatformClient } from '@metamask/core-backend';
4
- import type { AccountId, Caip19AssetId, FungibleAssetMetadata, AssetsControllerState } from '../types.js';
4
+ import type { AccountId, Caip19AssetId, FungibleAssetMetadata, AssetsControllerStateInternal } from '../types.js';
5
5
  /**
6
6
  * TEMPORARY MODULE — remove in a future release.
7
7
  *
@@ -27,7 +27,7 @@ import type { AccountId, Caip19AssetId, FungibleAssetMetadata, AssetsControllerS
27
27
  * The slices of current `AssetsController` state the healing computation
28
28
  * reads. Never mutated.
29
29
  */
30
- export type CurrentAssetsState = Pick<AssetsControllerState, 'assetsInfo' | 'assetsBalance' | 'customAssets' | 'assetPreferences'>;
30
+ export type CurrentAssetsState = Pick<AssetsControllerStateInternal, 'assetsInfo' | 'assetsBalance' | 'customAssets' | 'assetPreferences'>;
31
31
  /**
32
32
  * TEMPORARY — will be removed in a future release.
33
33
  *
@@ -63,7 +63,7 @@ export type AssetsInfoHealingPatch = {
63
63
  };
64
64
  export type TempHealAssetsInfoMetadataOptions = {
65
65
  /** Current `AssetsController` state the healing patch is computed against. */
66
- state: AssetsControllerState;
66
+ state: AssetsControllerStateInternal;
67
67
  /**
68
68
  * Host-provided getter for the untrusted legacy state root (see
69
69
  * `AssetsControllerOptions.tempMigrateAssetsInfoMetadataAssets3346`).
@@ -82,7 +82,7 @@ export type TempHealAssetsInfoMetadataOptions = {
82
82
  * @returns Updated controller state with the healing patch applied, or the
83
83
  * original state when there is nothing to heal or healing fails.
84
84
  */
85
- export declare function tempHealAssetsInfoMetadata({ state, getMigrationState, captureException, }: TempHealAssetsInfoMetadataOptions): AssetsControllerState;
85
+ export declare function tempHealAssetsInfoMetadata({ state, getMigrationState, captureException, }: TempHealAssetsInfoMetadataOptions): AssetsControllerStateInternal;
86
86
  /**
87
87
  * Compute the additions required to heal `assetsInfo` metadata (and custom
88
88
  * asset tracking) for tokens on niche EVM chains, based on the legacy
@@ -113,7 +113,7 @@ export declare function healAssetsInfoMetadata(legacyState: unknown, currentStat
113
113
  export declare function isUnlockCleanupEnabled(remoteFeatureFlags: unknown): boolean;
114
114
  /** The slices of Token API surface the cleanup uses. */
115
115
  export type SpamTokensApiClient = Pick<ApiPlatformClient, 'tokens' | 'token'>;
116
- export type CleanSpamAssetsState = Pick<AssetsControllerState, 'assetsInfo' | 'assetsBalance' | 'assetsPrice' | 'customAssets'>;
116
+ export type CleanSpamAssetsState = Pick<AssetsControllerStateInternal, 'assetsInfo' | 'assetsBalance' | 'assetsPrice' | 'customAssets'>;
117
117
  export type CleanSpamAssetsOptions = {
118
118
  state: CleanSpamAssetsState;
119
119
  apiClient: SpamTokensApiClient;
@@ -1 +1 @@
1
- {"version":3,"file":"healAssetsInfoMetadata.d.ts","sourceRoot":"","sources":["../../src/migrations/healAssetsInfoMetadata.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"healAssetsInfoMetadata.d.ts","sourceRoot":"","sources":["../../src/migrations/healAssetsInfoMetadata.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"healAssetsInfoMetadata.js","sourceRoot":"","sources":["../../src/migrations/healAssetsInfoMetadata.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,QAAQ,GACT,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAOjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AA8EhE,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;AAc5E;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,KAAK,EACL,iBAAiB,EACjB,gBAAgB,GACkB;IAClC,MAAM,WAAW,GAAG,CAAC,KAAc,EAAQ,EAAE;QAC3C,GAAG,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QACjD,gBAAgB,EAAE,CAChB,IAAI,KAAK,CACP,mEAAmE,eAAe,CAChF,KAAK,CACN,EAAE,CACJ,CACF,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,KAAK,GAAkC,IAAI,CAAC;IAChD,IAAI,CAAC;QACH,KAAK,GAAG,sBAAsB,CAAC,iBAAiB,EAAE,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAEpC,GAAG,CAAC,yDAAyD,EAAE;YAC7D,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM;YAC3D,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM;SACnE,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CACxB,KAAyB,EACzB,KAA6B;IAE7B,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACnE,KAAK,CAAC,UAAU,CAAC,OAAwB,CAAC,KAAK,QAAQ,CAAC;IAC1D,CAAC;IAED,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACvE,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACrD,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG;YAC9B,GAAG,QAAQ;YACX,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;SAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AAErC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,+BAA+B,GAAwB,IAAI,GAAG,CAAC;IACnE,UAAU,EAAE,mBAAmB;IAC/B,WAAW,EAAE,WAAW;IACxB,WAAW,EAAE,kBAAkB;IAC/B,YAAY,EAAE,UAAU;IACxB,YAAY,EAAE,QAAQ;IACtB,YAAY,EAAE,WAAW;IACzB,aAAa,EAAE,MAAM;IACrB,aAAa,EAAE,MAAM;IACrB,aAAa,EAAE,OAAO;IACtB,cAAc,EAAE,eAAe;IAC/B,cAAc,EAAE,YAAY;IAC5B,cAAc,EAAE,QAAQ;CACzB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAoB,EACpB,YAAgC;IAEhC,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC,CAAC;IAC3E,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,kBAAkB,GAAG,0BAA0B,CAAC,WAAW,CAAC,CAAC;IACnE,MAAM,cAAc,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAC3D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,WAAW,EAAE;QAC7C,kBAAkB;QAClB,kBAAkB;KACnB,CAAC,CAAC;IAEH,MAAM,mBAAmB,GAAiD,EAAE,CAAC;IAC7E,MAAM,oBAAoB,GAAuC,EAAE,CAAC;IAEpE,KAAK,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACpE,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YAC7B,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC5C,qEAAqE;QACrE,IAAI,CAAC,KAAK,IAAI,+BAA+B,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,SAAS;QACX,CAAC;QAED,KAAK,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClD,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;YAC/D,MAAM,gBAAgB,GAAG,uBAAuB,CAC9C,gBAAgB,EAChB,UAAU,EACV,UAAU,CACX,CAAC;YAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,UAAU,GAAG,kBAAkB,CACnC,KAAK,EACL,KAAK,EACL,gBAAgB,EAChB,cAAc,CACf,CAAC;gBACF,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,SAAS;gBACX,CAAC;gBACD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;gBAErC,sEAAsE;gBACtE,IACE,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,SAAS;oBAC9C,mBAAmB,CAAC,OAAO,CAAC,KAAK,SAAS,EAC1C,CAAC;oBACD,mBAAmB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;gBACtC,CAAC;gBAED,+CAA+C;gBAC/C,IAAI,SAAS,EAAE,CAAC;oBACd,sBAAsB,CACpB,YAAY,EACZ,oBAAoB,EACpB,SAAS,EACT,OAAO,CACR,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,IACE,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,MAAM,KAAK,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,KAAK,CAAC,EAC9C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,UAAU,EAAE,mBAAmB;QAC/B,YAAY,EAAE,oBAAoB;KACnC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,kBAAkB,CACzB,KAAc,EACd,KAAa,EACb,gBAA6B,EAC7B,cAA2B;IAE3B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,uDAAuD;IACvD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IACE,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QACjC,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EACtC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,uEAAuE;IACvE,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,OAAc,CAAC,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+DAA+D;IAC/D,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,iCAAiC;IACjC,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CACxB,KAA8B;IAE9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAgB,CAAC;IACtC,MAAM,IAAI,GACR,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACrD,CAAC,CAAC,KAAK,CAAC,IAAI;QACZ,CAAC,CAAC,MAAM,CAAC;IACb,MAAM,QAAQ,GACZ,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;QACnE,CAAC,CAAC,KAAK,CAAC,QAAQ;QAChB,CAAC,CAAC,CAAC,CAAC;IAER,MAAM,KAAK,GACT,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACvD,CAAC,CAAC,KAAK,CAAC,KAAK;QACb,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;QAClD,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CACtB,CAAC,UAAU,EAAwB,EAAE,CAAC,OAAO,UAAU,KAAK,QAAQ,CACrE;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO;QACL,IAAI,EAAE,OAAO;QACb,MAAM;QACN,IAAI;QACJ,QAAQ;QACR,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,0BAA0B,CACjC,WAAoB;IAEpB,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE;QACrC,oBAAoB;QACpB,kBAAkB;QAClB,UAAU;KACX,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAA8B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IACE,QAAQ,CAAC,OAAO,CAAC;YACjB,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;YACnC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAC1B,CAAC;YACD,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAAC,YAAgC;IAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAChD,YAAY,CAAC,gBAAgB,CAC9B,EAAE,CAAC;QACF,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YACvD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,uBAAuB,CAC9B,gBAAyB,EACzB,UAAkB,EAClB,cAAsB;IAEtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACzD,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;YAC3B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,UAAkB;IAC3C,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,mBAAmB,IAAI,OAAO,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CACxB,KAAa,EACb,YAAiB;IAEjB,IAAI,WAAmB,CAAC;IACxB,IAAI,CAAC;QACH,WAAW,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,KAAK,UAAU,WAAW,EAAmB,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,QAAQ,CAAC,IAAa,EAAE,IAAc;IAC7C,OAAO,IAAI,CAAC,MAAM,CAAU,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QAC1C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACnD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,sBAAsB,CAC7B,YAAgC,EAChC,SAA6C,EAC7C,SAAoB,EACpB,OAAsB;IAEtB,IAAI,YAAY,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,OAAO;IACT,CAAC;IACD,IAAI,YAAY,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5D,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IACD,SAAS,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,kBAA2B;IAChE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,kBAAkB,CAAC,gBAAgB,CAAC;IACjD,OAAO,CACL,QAAQ,CAAC,IAAI,CAAC;QACd,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC;QACrC,IAAI,CAAC,gBAAgB,KAAK,IAAI,CAC/B,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAgBnC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACpC,KAAK,EACL,SAAS,EACT,gBAAgB,GACO;IAIvB,MAAM,UAAU,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACvD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,8BAA8B,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,YAAY,GAAoB,EAAE,CAAC;QAEzC,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,UAAU,EAAE;YAChD,SAAS,EAAE,EAAE;SACd,CAAC,EAAE,CAAC;YACH,MAAM,kBAAkB,GAAG,MAAM,sBAAsB,CACrD,KAAK,EACL,MAAM,EACN,SAAS,CACV,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAClB,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,UAAU,CAAC,sCAAsC,EAAE;oBACjD,SAAS,EAAE,KAAK,CAAC,MAAM;oBACvB,SAAS,EAAE,kBAAkB,CAAC,MAAM;iBACrC,CAAC,CAAC;gBACH,YAAY,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8EAA8E;QAC9E,UAAU,CACR,yDAAyD,EACzD,KAAK,CACN,CAAC;QACF,gBAAgB,EAAE,CAChB,IAAI,KAAK,CACP,qDAAqD,eAAe,CAClE,KAAK,CACN,EAAE,CACJ,CACF,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,4BAA4B,CAAC,EACpC,UAAU,EACV,aAAa,EACb,YAAY,GAIb;IACC,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;SACxB,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAC3C,CAAC;IAEF,MAAM,cAAc,GAAG,CAAC,GAAG,+BAA+B,CAAC,MAAM,EAAE,CAAC;SACjE,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAE/B,MAAM,eAAe,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzD,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAoB,CAAC;IACrE,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CACxE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CACH,CAAC;IACrB,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,iBAAiB,EAAE,GAAG,eAAe,CAAC,EAAE,CAAC;QACjE,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACtC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QACtD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE5C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QACrD,MAAM,aAAa,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,gBAAgB,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACtD,MAAM,0BAA0B,GAC9B,+BAA+B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE/C,OAAO,CACL,OAAO,IAAI,aAAa,IAAI,gBAAgB,IAAI,0BAA0B,CAC3E,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,sBAAsB,CACnC,QAAyB,EACzB,MAA8B,EAC9B,SAA8B;IAE9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CACnC,GAAG,EAAE,CACH,SAAS,CAAC,MAAM,CAAC,aAAa,CAC5B,QAAQ,EACR,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAC5B,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAC5B,EACH,gBAAgB,CACjB,CAAC;QAEF,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAClC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CACxE,CAAC;QAEF,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;YACjC,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClE,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,wBAAwB,CAAC;YACjE,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,8BAA8B,CAC3C,SAA8B;IAE9B,IAAI,CAAC;QACH,OAAO,MAAM,gBAAgB,CAC3B,GAAG,EAAE,CACH,SAAS,CAAC,KAAK,CAAC,gCAAgC,CAAC;YAC/C,SAAS,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;SACV,CAAC,EACJ,gBAAgB,CACjB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;QACjE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CACxB,KAGC,EACD,KAEC;IAED,MAAM,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAE/B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC3E,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,OAAe,EAAW,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAE7E,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC,UAAU,CAAC,OAAwB,CAAC,CAAC;IACpD,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC1D,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3D,OAAO,QAAQ,CAAC,OAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACpE,OAAO,KAAK,CAAC,WAAW,CAAC,OAAwB,CAAC,CAAC;IACrD,CAAC;IAED,UAAU,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["import type { AccountsControllerState } from '@metamask/accounts-controller';\nimport type { TokensControllerState } from '@metamask/assets-controllers';\nimport type { ApiPlatformClient } from '@metamask/core-backend';\nimport type { Hex } from '@metamask/utils';\nimport {\n getChecksumAddress,\n getErrorMessage,\n hasProperty,\n isObject,\n} from '@metamask/utils';\nimport { cloneDeep } from 'lodash-es';\n\nimport { divideIntoBatches } from '../data-sources/evm-rpc-services/utils/batch.js';\nimport { DEFAULT_TRACKED_ASSETS_BY_CHAIN } from '../defaults.js';\nimport { createModuleLogger, projectLogger } from '../logger.js';\nimport type {\n AccountId,\n Caip19AssetId,\n FungibleAssetMetadata,\n AssetsControllerState,\n} from '../types.js';\nimport { fetchWithTimeout } from '../utils/fetchWithTimeout.js';\n\n/**\n * TEMPORARY MODULE — remove in a future release.\n *\n * Port of extension migration #215 (Assets Controller Metadata Healing).\n * Issue: https://consensyssoftware.atlassian.net/browse/ASSETS-3346\n * Incident: #incident-metamask-1731\n *\n * Background: after a prior defect in AssetsController, metadata for custom\n * tokens was wiped. Most popular chains support auto-detection and can\n * self-heal, however not all.\n *\n * This module computes the state additions needed to restore metadata for\n * custom tokens on niche EVM chains (chains that cannot auto-detect and\n * self-heal), using the legacy `TokensController.allTokens` state as the\n * source of truth. It is a pure function so it can be tested in isolation;\n * the controller applies the returned patch in its constructor.\n *\n * The legacy state is treated as fully untrusted (`unknown`): every shape is\n * validated before use, and any unexpected input results in that entry being\n * skipped (or `null` being returned when nothing is restorable).\n */\n\n/**\n * The slices of current `AssetsController` state the healing computation\n * reads. Never mutated.\n */\nexport type CurrentAssetsState = Pick<\n AssetsControllerState,\n 'assetsInfo' | 'assetsBalance' | 'customAssets' | 'assetPreferences'\n>;\n\n/**\n * TEMPORARY — will be removed in a future release.\n *\n * Shape of the legacy persisted state the ASSETS-3346 healing reads, derived\n * from the real `TokensControllerState` and `AccountsControllerState` types.\n * Use it to type the `tempMigrateAssetsInfoMetadataAssets3346` getter when\n * integrating in a client.\n *\n * Everything is optional and only the listed slices are read:\n *\n * - `TokensController.allTokens` — required for any healing to happen.\n * - `TokensController.allIgnoredTokens` — tokens the user hid/removed\n * (skipped during healing).\n * - `AccountsController.internalAccounts.accounts` — maps addresses to\n * account IDs so healed tokens are also tracked in `customAssets`\n * (without it only `assetsInfo` is healed).\n *\n * At runtime the value still crosses the boundary as `unknown`: every shape\n * is re-validated by the migration, so a partial or malformed object is safe\n * and simply skipped.\n */\nexport type Assets3346MigrationState = {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- must match the persisted controller state key\n TokensController?: Partial<\n Pick<TokensControllerState, 'allTokens' | 'allIgnoredTokens'>\n >;\n // eslint-disable-next-line @typescript-eslint/naming-convention -- must match the persisted controller state key\n AccountsController?: {\n internalAccounts?: {\n accounts?: Record<\n string,\n Pick<\n AccountsControllerState['internalAccounts']['accounts'][string],\n 'address'\n >\n >;\n };\n };\n};\n\nexport type AssetsInfoHealingPatch = {\n assetsInfo: Record<Caip19AssetId, FungibleAssetMetadata>;\n customAssets: Record<AccountId, Caip19AssetId[]>;\n};\n\nconst log = createModuleLogger(projectLogger, 'tempHealAssetsInfoMetadata');\n\nexport type TempHealAssetsInfoMetadataOptions = {\n /** Current `AssetsController` state the healing patch is computed against. */\n state: AssetsControllerState;\n /**\n * Host-provided getter for the untrusted legacy state root (see\n * `AssetsControllerOptions.tempMigrateAssetsInfoMetadataAssets3346`).\n */\n getMigrationState: () => unknown;\n /** Optional Sentry-compatible reporter for healing failures. */\n captureException?: (error: Error) => void;\n};\n\n/**\n * TEMPORARY — will be removed in a future release.\n *\n * @param options - The options bag.\n * @param options.state - Current controller state to compute the patch against.\n * @param options.getMigrationState - Getter for the untrusted legacy state root.\n * @param options.captureException - Optional reporter for healing failures.\n * @returns Updated controller state with the healing patch applied, or the\n * original state when there is nothing to heal or healing fails.\n */\nexport function tempHealAssetsInfoMetadata({\n state,\n getMigrationState,\n captureException,\n}: TempHealAssetsInfoMetadataOptions): AssetsControllerState {\n const reportError = (error: unknown): void => {\n log('Failed to heal assetsInfo metadata', error);\n captureException?.(\n new Error(\n `AssetsController: temporary assetsInfo metadata healing failed: ${getErrorMessage(\n error,\n )}`,\n ),\n );\n };\n\n let patch: AssetsInfoHealingPatch | null = null;\n try {\n patch = healAssetsInfoMetadata(getMigrationState(), state);\n } catch (error) {\n reportError(error);\n }\n\n if (!patch) {\n return state;\n }\n\n try {\n const nextState = cloneDeep(state);\n applyHealingPatch(nextState, patch);\n\n log('Healed wiped assetsInfo metadata for niche-chain tokens', {\n healedAssetsInfoCount: Object.keys(patch.assetsInfo).length,\n healedCustomAssetsAccounts: Object.keys(patch.customAssets).length,\n });\n\n return nextState;\n } catch (error) {\n reportError(error);\n return state;\n }\n}\n\n/**\n * Apply a healing patch to (draft) controller state. Defensive against\n * concurrent writes: fills `assetsInfo` gaps only and dedupes `customAssets`\n * against the current draft rather than trusting the patch blindly.\n *\n * @param state - Mutable controller state copy.\n * @param patch - The additions computed by {@link healAssetsInfoMetadata}.\n */\nfunction applyHealingPatch(\n state: CurrentAssetsState,\n patch: AssetsInfoHealingPatch,\n): void {\n for (const [assetId, metadata] of Object.entries(patch.assetsInfo)) {\n state.assetsInfo[assetId as Caip19AssetId] ??= metadata;\n }\n\n for (const [accountId, assetIds] of Object.entries(patch.customAssets)) {\n const existing = state.customAssets[accountId] ?? [];\n state.customAssets[accountId] = [\n ...existing,\n ...assetIds.filter((assetId) => !existing.includes(assetId)),\n ];\n }\n}\n\nconst EVM_CHAIN_NAMESPACE = 'eip155';\n\nconst EVM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/u;\n\n/**\n * Popular networks (covered by the Accounts API) can self-heal through\n * auto-detection, so their tokens must not be touched. Frozen snapshot from\n * extension migration #215.\n */\nconst ACCOUNT_API_SUPPORTED_CHAIN_IDS: ReadonlySet<string> = new Set([\n 'eip155:1', // Ethereum Mainnet\n 'eip155:10', // Optimism\n 'eip155:56', // BNB Smart Chain\n 'eip155:137', // Polygon\n 'eip155:143', // Monad\n 'eip155:999', // HyperEVM\n 'eip155:1329', // Sei\n 'eip155:5042', // Arc\n 'eip155:8453', // Base\n 'eip155:42161', // Arbitrum One\n 'eip155:43114', // Avalanche\n 'eip155:59144', // Linea\n]);\n\n/**\n * Compute the additions required to heal `assetsInfo` metadata (and custom\n * asset tracking) for tokens on niche EVM chains, based on the legacy\n * `TokensController` state.\n *\n * What it deliberately skips:\n *\n * - Accounts-API-supported chains — see `ACCOUNT_API_SUPPORTED_CHAIN_IDS`.\n * - Tokens the user hid/removed, detected via either the legacy\n * `TokensController.allIgnoredTokens` or the current\n * `assetPreferences` (`hidden: true`).\n * - Non-EVM assets, ERC-721s, and tokens with invalid addresses or metadata.\n *\n * @param legacyState - Untrusted legacy state root. Expected (but not\n * required) to contain `TokensController.allTokens`,\n * `TokensController.allIgnoredTokens`, and\n * `AccountsController.internalAccounts.accounts`.\n * @param currentState - Current `AssetsController` state slices (read-only).\n * @returns The additions to apply, or `null` when there is nothing to heal.\n */\nexport function healAssetsInfoMetadata(\n legacyState: unknown,\n currentState: CurrentAssetsState,\n): AssetsInfoHealingPatch | null {\n const allTokens = readPath(legacyState, ['TokensController', 'allTokens']);\n if (!isObject(allTokens)) {\n return null;\n }\n\n const addressToAccountId = buildAddressToAccountIdMap(legacyState);\n const hiddenAssetIds = collectHiddenAssetIds(currentState);\n const allIgnoredTokens = readPath(legacyState, [\n 'TokensController',\n 'allIgnoredTokens',\n ]);\n\n const assetsInfoAdditions: Record<Caip19AssetId, FungibleAssetMetadata> = {};\n const customAssetAdditions: Record<AccountId, Caip19AssetId[]> = {};\n\n for (const [hexChainId, accountTokens] of Object.entries(allTokens)) {\n if (!isObject(accountTokens)) {\n continue;\n }\n\n const caip2 = hexChainIdToCaip2(hexChainId);\n // Skip chains we can't parse or that self-heal via the accounts API.\n if (!caip2 || ACCOUNT_API_SUPPORTED_CHAIN_IDS.has(caip2)) {\n continue;\n }\n\n for (const [rawAddress, tokens] of Object.entries(accountTokens)) {\n if (!Array.isArray(tokens) || tokens.length === 0) {\n continue;\n }\n\n const accountId = addressToAccountId[rawAddress.toLowerCase()];\n const ignoredAddresses = collectIgnoredAddresses(\n allIgnoredTokens,\n hexChainId,\n rawAddress,\n );\n\n for (const token of tokens) {\n const restorable = getRestorableAsset(\n token,\n caip2,\n ignoredAddresses,\n hiddenAssetIds,\n );\n if (!restorable) {\n continue;\n }\n const { assetId, info } = restorable;\n\n // `assetsInfo` is a global registry; fill gaps only, never overwrite.\n if (\n currentState.assetsInfo[assetId] === undefined &&\n assetsInfoAdditions[assetId] === undefined\n ) {\n assetsInfoAdditions[assetId] = info;\n }\n\n // Ensure the asset is tracked for its account.\n if (accountId) {\n addCustomAssetAddition(\n currentState,\n customAssetAdditions,\n accountId,\n assetId,\n );\n }\n }\n }\n }\n\n if (\n Object.keys(assetsInfoAdditions).length === 0 &&\n Object.keys(customAssetAdditions).length === 0\n ) {\n return null;\n }\n\n return {\n assetsInfo: assetsInfoAdditions,\n customAssets: customAssetAdditions,\n };\n}\n\n/**\n * Resolve a raw `TokensController` token entry to the CAIP-19 asset ID and\n * metadata that should be healed, or `null` when the token must be skipped\n * (not an object, missing/invalid address or symbol, an ERC-721, or hidden\n * in either the legacy or the current controller).\n *\n * @param token - Raw token entry from `allTokens`.\n * @param caip2 - The CAIP-2 chain ID of the token's chain (e.g. 'eip155:14').\n * @param ignoredAddresses - Lowercase addresses hidden via legacy `allIgnoredTokens`.\n * @param hiddenAssetIds - Lowercase asset IDs hidden via current `assetPreferences`.\n * @returns The restorable asset, or `null` to skip.\n */\nfunction getRestorableAsset(\n token: unknown,\n caip2: string,\n ignoredAddresses: Set<string>,\n hiddenAssetIds: Set<string>,\n): { assetId: Caip19AssetId; info: FungibleAssetMetadata } | null {\n if (!isObject(token)) {\n return null;\n }\n // Skip NFTs — AssetsController tracks them separately.\n if (token.isERC721 === true) {\n return null;\n }\n if (\n typeof token.address !== 'string' ||\n !EVM_ADDRESS_REGEX.test(token.address)\n ) {\n return null;\n }\n // Healed metadata with no symbol would be unusable in the UI; skip it.\n if (typeof token.symbol !== 'string' || token.symbol.length === 0) {\n return null;\n }\n\n const assetId = buildErc20AssetId(caip2, token.address as Hex);\n if (!assetId) {\n return null;\n }\n\n // Skip tokens the user hid/removed — in the legacy controller…\n if (ignoredAddresses.has(token.address.toLowerCase())) {\n return null;\n }\n // …or in the current controller.\n if (hiddenAssetIds.has(assetId.toLowerCase())) {\n return null;\n }\n\n return { assetId, info: buildEvmAssetInfo(token) };\n}\n\n/**\n * Build a `FungibleAssetMetadata` from a raw `TokensController` token entry.\n * The caller has already validated `symbol` as a non-empty string.\n *\n * @param token - Raw token object.\n * @returns The metadata to write into `assetsInfo`.\n */\nfunction buildEvmAssetInfo(\n token: Record<string, unknown>,\n): FungibleAssetMetadata {\n const symbol = token.symbol as string;\n const name =\n typeof token.name === 'string' && token.name.length > 0\n ? token.name\n : symbol;\n const decimals =\n typeof token.decimals === 'number' && Number.isFinite(token.decimals)\n ? token.decimals\n : 0;\n\n const image =\n typeof token.image === 'string' && token.image.length > 0\n ? token.image\n : undefined;\n const aggregators = Array.isArray(token.aggregators)\n ? token.aggregators.filter(\n (aggregator): aggregator is string => typeof aggregator === 'string',\n )\n : undefined;\n\n return {\n type: 'erc20',\n symbol,\n name,\n decimals,\n ...(image ? { image } : {}),\n ...(aggregators ? { aggregators } : {}),\n };\n}\n\n/**\n * Build a map from lowercase account address to account UUID using the\n * legacy `AccountsController.internalAccounts.accounts`.\n *\n * @param legacyState - Untrusted legacy state root.\n * @returns Map of lowercase address to account ID (empty on invalid input).\n */\nfunction buildAddressToAccountIdMap(\n legacyState: unknown,\n): Record<string, AccountId> {\n const accounts = readPath(legacyState, [\n 'AccountsController',\n 'internalAccounts',\n 'accounts',\n ]);\n if (!isObject(accounts)) {\n return {};\n }\n\n const map: Record<string, AccountId> = {};\n for (const [id, account] of Object.entries(accounts)) {\n if (\n isObject(account) &&\n typeof account.address === 'string' &&\n account.address.length > 0\n ) {\n map[account.address.toLowerCase()] = id;\n }\n }\n return map;\n}\n\n/**\n * Collect the set of CAIP-19 asset IDs (lowercased) marked `hidden: true` in\n * the current `assetPreferences`. Lowercasing lets callers compare against\n * checksummed asset IDs case-insensitively.\n *\n * @param currentState - Current `AssetsController` state slices.\n * @returns Set of lowercase hidden asset IDs.\n */\nfunction collectHiddenAssetIds(currentState: CurrentAssetsState): Set<string> {\n const hidden = new Set<string>();\n for (const [assetId, preference] of Object.entries(\n currentState.assetPreferences,\n )) {\n if (isObject(preference) && preference.hidden === true) {\n hidden.add(assetId.toLowerCase());\n }\n }\n return hidden;\n}\n\n/**\n * Collect the lowercase token addresses ignored (hidden) in the legacy\n * `TokensController.allIgnoredTokens` for a given chain and account. The\n * account key is matched case-insensitively.\n *\n * @param allIgnoredTokens - The legacy `allIgnoredTokens` map (possibly missing).\n * @param hexChainId - The hex chain ID being processed.\n * @param accountAddress - The account address whose ignored list to read.\n * @returns Set of lowercase ignored token addresses.\n */\nfunction collectIgnoredAddresses(\n allIgnoredTokens: unknown,\n hexChainId: string,\n accountAddress: string,\n): Set<string> {\n const result = new Set<string>();\n if (!isObject(allIgnoredTokens)) {\n return result;\n }\n\n const chainEntry = allIgnoredTokens[hexChainId];\n if (!isObject(chainEntry)) {\n return result;\n }\n\n const lowerAccount = accountAddress.toLowerCase();\n for (const [address, list] of Object.entries(chainEntry)) {\n if (address.toLowerCase() !== lowerAccount || !Array.isArray(list)) {\n continue;\n }\n for (const ignored of list) {\n if (typeof ignored === 'string') {\n result.add(ignored.toLowerCase());\n }\n }\n }\n return result;\n}\n\n/**\n * Convert a hex chain ID (e.g. '0x1') to a CAIP-2 chain ID (e.g. 'eip155:1').\n *\n * @param hexChainId - The hex-encoded EVM chain ID.\n * @returns The CAIP-2 chain ID, or `null` when the input cannot be parsed.\n */\nfunction hexChainIdToCaip2(hexChainId: string): string | null {\n if (!/^0x[0-9a-fA-F]+$/u.test(hexChainId)) {\n return null;\n }\n const decimal = Number.parseInt(hexChainId, 16);\n if (!Number.isFinite(decimal)) {\n return null;\n }\n return `${EVM_CHAIN_NAMESPACE}:${decimal}`;\n}\n\n/**\n * Build the checksummed CAIP-19 asset ID for an ERC-20 token.\n *\n * @param caip2 - The CAIP-2 chain ID (e.g. 'eip155:14').\n * @param tokenAddress - The ERC-20 contract address.\n * @returns The asset ID, or `null` when the address cannot be checksummed.\n */\nfunction buildErc20AssetId(\n caip2: string,\n tokenAddress: Hex,\n): Caip19AssetId | null {\n let checksummed: string;\n try {\n checksummed = getChecksumAddress(tokenAddress);\n } catch {\n return null;\n }\n return `${caip2}/erc20:${checksummed}` as Caip19AssetId;\n}\n\n/**\n * Read a nested path on an unknown value, returning `undefined` if any\n * intermediate key is missing or not an object.\n *\n * @param root - The value to read from.\n * @param path - Sequence of keys to traverse.\n * @returns The value at the path, or `undefined`.\n */\nfunction readPath(root: unknown, path: string[]): unknown {\n return path.reduce<unknown>((cursor, key) => {\n if (!isObject(cursor) || !hasProperty(cursor, key)) {\n return undefined;\n }\n return cursor[key];\n }, root);\n}\n\n/**\n * Record `assetId` as a custom-asset addition for `accountId` unless it is\n * already tracked in the current `assetsBalance` (mutual exclusion), already\n * present in the current `customAssets`, or already queued as an addition.\n *\n * @param currentState - Current `AssetsController` state slices.\n * @param additions - The custom-asset additions accumulated so far (mutated).\n * @param accountId - The account UUID.\n * @param assetId - CAIP-19 asset identifier.\n */\nfunction addCustomAssetAddition(\n currentState: CurrentAssetsState,\n additions: Record<AccountId, Caip19AssetId[]>,\n accountId: AccountId,\n assetId: Caip19AssetId,\n): void {\n if (currentState.assetsBalance[accountId]?.[assetId]) {\n return;\n }\n if (currentState.customAssets[accountId]?.includes(assetId)) {\n return;\n }\n\n const queued = additions[accountId] ?? [];\n if (!queued.includes(assetId)) {\n queued.push(assetId);\n }\n additions[accountId] = queued;\n}\n\nconst cleanupLog = createModuleLogger(projectLogger, 'cleanSpamAssets');\n\n/**\n * TEMPORARY — feature flag for unlock spam cleanup\n *\n * @param remoteFeatureFlags - RemoteFeatureFlag state.\n * @returns `true` only when the flag explicitly enables the cleanup.\n */\nexport function isUnlockCleanupEnabled(remoteFeatureFlags: unknown): boolean {\n if (!isObject(remoteFeatureFlags)) {\n return false;\n }\n\n const flag = remoteFeatureFlags.assetsUnifyState;\n return (\n isObject(flag) &&\n hasProperty(flag, 'useUnlockCleanup') &&\n flag.useUnlockCleanup === true\n );\n}\n\nconst FETCH_TIMEOUT_MS = 15_000;\nconst DEFAULT_OCCURRENCE_FLOOR = 3;\n\n/** The slices of Token API surface the cleanup uses. */\nexport type SpamTokensApiClient = Pick<ApiPlatformClient, 'tokens' | 'token'>;\n\nexport type CleanSpamAssetsState = Pick<\n AssetsControllerState,\n 'assetsInfo' | 'assetsBalance' | 'assetsPrice' | 'customAssets'\n>;\n\nexport type CleanSpamAssetsOptions = {\n state: CleanSpamAssetsState;\n apiClient: SpamTokensApiClient;\n captureException?: (error: Error) => void;\n};\n\n/**\n * Cleanup of Spam Assets.\n * The state can be persisted with spam tokens through boundary faults (API loosened, WS passing in spam)\n *\n * @param options - Input params.\n * @param options.state - Current controller state (read-only).\n * @param options.apiClient - Token API client.\n * @param options.captureException - Optional reporter for failures.\n * @returns Updated or original controller state\n */\nexport async function cleanSpamAssets({\n state,\n apiClient,\n captureException,\n}: CleanSpamAssetsOptions): Promise<{\n spamAssetIds: Caip19AssetId[];\n applyPatch: typeof applyCleanupPatch;\n} | null> {\n const candidates = collectSpamCleanupCandidates(state);\n if (candidates.length === 0) {\n return null;\n }\n\n try {\n const floors = await fetchSuggestedOccurrenceFloors(apiClient);\n const spamAssetIds: Caip19AssetId[] = [];\n\n for (const batch of divideIntoBatches(candidates, {\n batchSize: 50,\n })) {\n const belowFloorAssetIds = await findBelowFloorAssetIds(\n batch,\n floors,\n apiClient,\n ).catch(() => []);\n if (belowFloorAssetIds.length > 0) {\n cleanupLog('Classified a batch of assets as spam', {\n batchSize: batch.length,\n spamCount: belowFloorAssetIds.length,\n });\n spamAssetIds.push(...belowFloorAssetIds);\n }\n }\n\n if (spamAssetIds.length === 0) {\n return null;\n }\n\n return { spamAssetIds, applyPatch: applyCleanupPatch };\n } catch (error) {\n // API calls failed, cleanup not performed. Will be retried when next invoked.\n cleanupLog(\n 'Spam cleanup failed; leaving remaining assets untouched',\n error,\n );\n captureException?.(\n new Error(\n `AssetsController: assetsInfo spam cleanup failed: ${getErrorMessage(\n error,\n )}`,\n ),\n );\n return null;\n }\n}\n\n/**\n * Collect the assets eligible for cleanup:\n * - ERC-20 token\n * - Not in excluded list\n * - Not a custom asset\n * - On Account API covered chains\n *\n * @param state - Current controller state.\n * @param state.assetsInfo - Tracked asset metadata, keyed by CAIP-19 ID.\n * @param state.assetsBalance - Per-account balances, keyed by CAIP-19 ID.\n * @param state.customAssets - Per-account custom asset IDs.\n * @returns Candidate asset IDs, one per asset whatever casing it is held in.\n */\nfunction collectSpamCleanupCandidates({\n assetsInfo,\n assetsBalance,\n customAssets,\n}: Pick<\n CleanSpamAssetsState,\n 'assetsInfo' | 'assetsBalance' | 'customAssets'\n>): Caip19AssetId[] {\n const customAssetIds = new Set(\n Object.values(customAssets)\n .flat()\n .map((assetId) => assetId.toLowerCase()),\n );\n\n const excludedAssets = [...DEFAULT_TRACKED_ASSETS_BY_CHAIN.values()]\n .flat()\n .map((a) => a.toLowerCase());\n\n const trackedAssetIds = new Map<string, Caip19AssetId>();\n const assetInfoAssetIds = Object.keys(assetsInfo) as Caip19AssetId[];\n const balanceAssetIds = Object.values(assetsBalance).flatMap((balances) =>\n Object.keys(balances),\n ) as Caip19AssetId[];\n for (const assetId of [...assetInfoAssetIds, ...balanceAssetIds]) {\n const lowerId = assetId.toLowerCase();\n if (!trackedAssetIds.has(lowerId)) {\n trackedAssetIds.set(lowerId, assetId);\n }\n }\n\n return [...trackedAssetIds.values()].filter((assetId) => {\n const lowerId = assetId.toLowerCase();\n const [chainId, asset] = lowerId.split('/');\n\n const isERC20 = Boolean(asset?.startsWith('erc20:'));\n const isNotExcluded = !excludedAssets.includes(lowerId);\n const isNotCustomAsset = !customAssetIds.has(lowerId);\n const isOnAccountAPICoveredChain =\n ACCOUNT_API_SUPPORTED_CHAIN_IDS.has(chainId);\n\n return (\n isERC20 && isNotExcluded && isNotCustomAsset && isOnAccountAPICoveredChain\n );\n });\n}\n\n/**\n * Fetch and filter assets by occurrence floor.\n * If fetch fails, error is propagated (fail-close, cleanup not performed)\n *\n * @param assetIds - A single batch of candidate asset IDs.\n * @param floors - Suggested occurrence floors keyed by decimal chain ID.\n * @param apiClient - Token API client.\n * @returns The subset of `assetIds` that falls below its chain's floor.\n */\nasync function findBelowFloorAssetIds(\n assetIds: Caip19AssetId[],\n floors: Record<string, number>,\n apiClient: SpamTokensApiClient,\n): Promise<Caip19AssetId[]> {\n try {\n const assets = await fetchWithTimeout(\n () =>\n apiClient.tokens.fetchV3Assets(\n assetIds,\n { includeOccurrences: true },\n { staleTime: 0, gcTime: 0 },\n ),\n FETCH_TIMEOUT_MS,\n );\n\n const occurrencesByLowerId = new Map(\n assets.map((asset) => [asset.assetId.toLowerCase(), asset.occurrences]),\n );\n\n return assetIds.filter((assetId) => {\n const chainReference = assetId.split(':')[1]?.split('/')[0] ?? '';\n const floor = floors[chainReference] ?? DEFAULT_OCCURRENCE_FLOOR;\n return (occurrencesByLowerId.get(assetId.toLowerCase()) ?? 0) < floor;\n });\n } catch (error) {\n cleanupLog('Failed to fetch assets', error);\n throw error;\n }\n}\n\n/**\n * Fetch per-chain suggested occurrence floors.\n * If fetch fails, error is propagated (fail-close, cleanup not performed)\n *\n * @param apiClient - Token API client.\n * @returns Map of decimal chain ID to suggested floor.\n */\nasync function fetchSuggestedOccurrenceFloors(\n apiClient: SpamTokensApiClient,\n): Promise<Record<string, number>> {\n try {\n return await fetchWithTimeout(\n () =>\n apiClient.token.fetchV1SuggestedOccurrenceFloors({\n staleTime: 0,\n gcTime: 0,\n }),\n FETCH_TIMEOUT_MS,\n );\n } catch (error) {\n cleanupLog('Failed to fetch suggested occurrence floors', error);\n throw error;\n }\n}\n\nfunction applyCleanupPatch(\n state: Pick<\n CleanSpamAssetsState,\n 'assetsInfo' | 'assetsBalance' | 'assetsPrice'\n >,\n patch: {\n spamAssetIds: Caip19AssetId[];\n },\n): void {\n const { spamAssetIds } = patch;\n\n const spam = new Set(spamAssetIds.map((assetId) => assetId.toLowerCase()));\n if (spam.size === 0) {\n return;\n }\n const isSpam = (assetId: string): boolean => spam.has(assetId.toLowerCase());\n\n for (const assetId of Object.keys(state.assetsInfo).filter(isSpam)) {\n delete state.assetsInfo[assetId as Caip19AssetId];\n }\n for (const balances of Object.values(state.assetsBalance)) {\n for (const assetId of Object.keys(balances).filter(isSpam)) {\n delete balances[assetId as Caip19AssetId];\n }\n }\n for (const assetId of Object.keys(state.assetsPrice).filter(isSpam)) {\n delete state.assetsPrice[assetId as Caip19AssetId];\n }\n\n cleanupLog('Removed spam assets', { count: spam.size });\n}\n"]}
1
+ {"version":3,"file":"healAssetsInfoMetadata.js","sourceRoot":"","sources":["../../src/migrations/healAssetsInfoMetadata.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountsControllerState } from '@metamask/accounts-controller';\nimport type { TokensControllerState } from '@metamask/assets-controllers';\nimport type { ApiPlatformClient } from '@metamask/core-backend';\nimport type { Hex } from '@metamask/utils';\nimport {\n getChecksumAddress,\n getErrorMessage,\n hasProperty,\n isObject,\n} from '@metamask/utils';\nimport { cloneDeep } from 'lodash-es';\n\nimport { divideIntoBatches } from '../data-sources/evm-rpc-services/utils/batch.js';\nimport { DEFAULT_TRACKED_ASSETS_BY_CHAIN } from '../defaults.js';\nimport { createModuleLogger, projectLogger } from '../logger.js';\nimport type {\n AccountId,\n Caip19AssetId,\n FungibleAssetMetadata,\n AssetsControllerStateInternal,\n} from '../types.js';\nimport { fetchWithTimeout } from '../utils/fetchWithTimeout.js';\n\n/**\n * TEMPORARY MODULE — remove in a future release.\n *\n * Port of extension migration #215 (Assets Controller Metadata Healing).\n * Issue: https://consensyssoftware.atlassian.net/browse/ASSETS-3346\n * Incident: #incident-metamask-1731\n *\n * Background: after a prior defect in AssetsController, metadata for custom\n * tokens was wiped. Most popular chains support auto-detection and can\n * self-heal, however not all.\n *\n * This module computes the state additions needed to restore metadata for\n * custom tokens on niche EVM chains (chains that cannot auto-detect and\n * self-heal), using the legacy `TokensController.allTokens` state as the\n * source of truth. It is a pure function so it can be tested in isolation;\n * the controller applies the returned patch in its constructor.\n *\n * The legacy state is treated as fully untrusted (`unknown`): every shape is\n * validated before use, and any unexpected input results in that entry being\n * skipped (or `null` being returned when nothing is restorable).\n */\n\n/**\n * The slices of current `AssetsController` state the healing computation\n * reads. Never mutated.\n */\nexport type CurrentAssetsState = Pick<\n AssetsControllerStateInternal,\n 'assetsInfo' | 'assetsBalance' | 'customAssets' | 'assetPreferences'\n>;\n\n/**\n * TEMPORARY — will be removed in a future release.\n *\n * Shape of the legacy persisted state the ASSETS-3346 healing reads, derived\n * from the real `TokensControllerState` and `AccountsControllerState` types.\n * Use it to type the `tempMigrateAssetsInfoMetadataAssets3346` getter when\n * integrating in a client.\n *\n * Everything is optional and only the listed slices are read:\n *\n * - `TokensController.allTokens` — required for any healing to happen.\n * - `TokensController.allIgnoredTokens` — tokens the user hid/removed\n * (skipped during healing).\n * - `AccountsController.internalAccounts.accounts` — maps addresses to\n * account IDs so healed tokens are also tracked in `customAssets`\n * (without it only `assetsInfo` is healed).\n *\n * At runtime the value still crosses the boundary as `unknown`: every shape\n * is re-validated by the migration, so a partial or malformed object is safe\n * and simply skipped.\n */\nexport type Assets3346MigrationState = {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- must match the persisted controller state key\n TokensController?: Partial<\n Pick<TokensControllerState, 'allTokens' | 'allIgnoredTokens'>\n >;\n // eslint-disable-next-line @typescript-eslint/naming-convention -- must match the persisted controller state key\n AccountsController?: {\n internalAccounts?: {\n accounts?: Record<\n string,\n Pick<\n AccountsControllerState['internalAccounts']['accounts'][string],\n 'address'\n >\n >;\n };\n };\n};\n\nexport type AssetsInfoHealingPatch = {\n assetsInfo: Record<Caip19AssetId, FungibleAssetMetadata>;\n customAssets: Record<AccountId, Caip19AssetId[]>;\n};\n\nconst log = createModuleLogger(projectLogger, 'tempHealAssetsInfoMetadata');\n\nexport type TempHealAssetsInfoMetadataOptions = {\n /** Current `AssetsController` state the healing patch is computed against. */\n state: AssetsControllerStateInternal;\n /**\n * Host-provided getter for the untrusted legacy state root (see\n * `AssetsControllerOptions.tempMigrateAssetsInfoMetadataAssets3346`).\n */\n getMigrationState: () => unknown;\n /** Optional Sentry-compatible reporter for healing failures. */\n captureException?: (error: Error) => void;\n};\n\n/**\n * TEMPORARY — will be removed in a future release.\n *\n * @param options - The options bag.\n * @param options.state - Current controller state to compute the patch against.\n * @param options.getMigrationState - Getter for the untrusted legacy state root.\n * @param options.captureException - Optional reporter for healing failures.\n * @returns Updated controller state with the healing patch applied, or the\n * original state when there is nothing to heal or healing fails.\n */\nexport function tempHealAssetsInfoMetadata({\n state,\n getMigrationState,\n captureException,\n}: TempHealAssetsInfoMetadataOptions): AssetsControllerStateInternal {\n const reportError = (error: unknown): void => {\n log('Failed to heal assetsInfo metadata', error);\n captureException?.(\n new Error(\n `AssetsController: temporary assetsInfo metadata healing failed: ${getErrorMessage(\n error,\n )}`,\n ),\n );\n };\n\n let patch: AssetsInfoHealingPatch | null = null;\n try {\n patch = healAssetsInfoMetadata(getMigrationState(), state);\n } catch (error) {\n reportError(error);\n }\n\n if (!patch) {\n return state;\n }\n\n try {\n const nextState = cloneDeep(state);\n applyHealingPatch(nextState, patch);\n\n log('Healed wiped assetsInfo metadata for niche-chain tokens', {\n healedAssetsInfoCount: Object.keys(patch.assetsInfo).length,\n healedCustomAssetsAccounts: Object.keys(patch.customAssets).length,\n });\n\n return nextState;\n } catch (error) {\n reportError(error);\n return state;\n }\n}\n\n/**\n * Apply a healing patch to (draft) controller state. Defensive against\n * concurrent writes: fills `assetsInfo` gaps only and dedupes `customAssets`\n * against the current draft rather than trusting the patch blindly.\n *\n * @param state - Mutable controller state copy.\n * @param patch - The additions computed by {@link healAssetsInfoMetadata}.\n */\nfunction applyHealingPatch(\n state: CurrentAssetsState,\n patch: AssetsInfoHealingPatch,\n): void {\n for (const [assetId, metadata] of Object.entries(patch.assetsInfo)) {\n state.assetsInfo[assetId as Caip19AssetId] ??= metadata;\n }\n\n for (const [accountId, assetIds] of Object.entries(patch.customAssets)) {\n const existing = state.customAssets[accountId] ?? [];\n state.customAssets[accountId] = [\n ...existing,\n ...assetIds.filter((assetId) => !existing.includes(assetId)),\n ];\n }\n}\n\nconst EVM_CHAIN_NAMESPACE = 'eip155';\n\nconst EVM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/u;\n\n/**\n * Popular networks (covered by the Accounts API) can self-heal through\n * auto-detection, so their tokens must not be touched. Frozen snapshot from\n * extension migration #215.\n */\nconst ACCOUNT_API_SUPPORTED_CHAIN_IDS: ReadonlySet<string> = new Set([\n 'eip155:1', // Ethereum Mainnet\n 'eip155:10', // Optimism\n 'eip155:56', // BNB Smart Chain\n 'eip155:137', // Polygon\n 'eip155:143', // Monad\n 'eip155:999', // HyperEVM\n 'eip155:1329', // Sei\n 'eip155:5042', // Arc\n 'eip155:8453', // Base\n 'eip155:42161', // Arbitrum One\n 'eip155:43114', // Avalanche\n 'eip155:59144', // Linea\n]);\n\n/**\n * Compute the additions required to heal `assetsInfo` metadata (and custom\n * asset tracking) for tokens on niche EVM chains, based on the legacy\n * `TokensController` state.\n *\n * What it deliberately skips:\n *\n * - Accounts-API-supported chains — see `ACCOUNT_API_SUPPORTED_CHAIN_IDS`.\n * - Tokens the user hid/removed, detected via either the legacy\n * `TokensController.allIgnoredTokens` or the current\n * `assetPreferences` (`hidden: true`).\n * - Non-EVM assets, ERC-721s, and tokens with invalid addresses or metadata.\n *\n * @param legacyState - Untrusted legacy state root. Expected (but not\n * required) to contain `TokensController.allTokens`,\n * `TokensController.allIgnoredTokens`, and\n * `AccountsController.internalAccounts.accounts`.\n * @param currentState - Current `AssetsController` state slices (read-only).\n * @returns The additions to apply, or `null` when there is nothing to heal.\n */\nexport function healAssetsInfoMetadata(\n legacyState: unknown,\n currentState: CurrentAssetsState,\n): AssetsInfoHealingPatch | null {\n const allTokens = readPath(legacyState, ['TokensController', 'allTokens']);\n if (!isObject(allTokens)) {\n return null;\n }\n\n const addressToAccountId = buildAddressToAccountIdMap(legacyState);\n const hiddenAssetIds = collectHiddenAssetIds(currentState);\n const allIgnoredTokens = readPath(legacyState, [\n 'TokensController',\n 'allIgnoredTokens',\n ]);\n\n const assetsInfoAdditions: Record<Caip19AssetId, FungibleAssetMetadata> = {};\n const customAssetAdditions: Record<AccountId, Caip19AssetId[]> = {};\n\n for (const [hexChainId, accountTokens] of Object.entries(allTokens)) {\n if (!isObject(accountTokens)) {\n continue;\n }\n\n const caip2 = hexChainIdToCaip2(hexChainId);\n // Skip chains we can't parse or that self-heal via the accounts API.\n if (!caip2 || ACCOUNT_API_SUPPORTED_CHAIN_IDS.has(caip2)) {\n continue;\n }\n\n for (const [rawAddress, tokens] of Object.entries(accountTokens)) {\n if (!Array.isArray(tokens) || tokens.length === 0) {\n continue;\n }\n\n const accountId = addressToAccountId[rawAddress.toLowerCase()];\n const ignoredAddresses = collectIgnoredAddresses(\n allIgnoredTokens,\n hexChainId,\n rawAddress,\n );\n\n for (const token of tokens) {\n const restorable = getRestorableAsset(\n token,\n caip2,\n ignoredAddresses,\n hiddenAssetIds,\n );\n if (!restorable) {\n continue;\n }\n const { assetId, info } = restorable;\n\n // `assetsInfo` is a global registry; fill gaps only, never overwrite.\n if (\n currentState.assetsInfo[assetId] === undefined &&\n assetsInfoAdditions[assetId] === undefined\n ) {\n assetsInfoAdditions[assetId] = info;\n }\n\n // Ensure the asset is tracked for its account.\n if (accountId) {\n addCustomAssetAddition(\n currentState,\n customAssetAdditions,\n accountId,\n assetId,\n );\n }\n }\n }\n }\n\n if (\n Object.keys(assetsInfoAdditions).length === 0 &&\n Object.keys(customAssetAdditions).length === 0\n ) {\n return null;\n }\n\n return {\n assetsInfo: assetsInfoAdditions,\n customAssets: customAssetAdditions,\n };\n}\n\n/**\n * Resolve a raw `TokensController` token entry to the CAIP-19 asset ID and\n * metadata that should be healed, or `null` when the token must be skipped\n * (not an object, missing/invalid address or symbol, an ERC-721, or hidden\n * in either the legacy or the current controller).\n *\n * @param token - Raw token entry from `allTokens`.\n * @param caip2 - The CAIP-2 chain ID of the token's chain (e.g. 'eip155:14').\n * @param ignoredAddresses - Lowercase addresses hidden via legacy `allIgnoredTokens`.\n * @param hiddenAssetIds - Lowercase asset IDs hidden via current `assetPreferences`.\n * @returns The restorable asset, or `null` to skip.\n */\nfunction getRestorableAsset(\n token: unknown,\n caip2: string,\n ignoredAddresses: Set<string>,\n hiddenAssetIds: Set<string>,\n): { assetId: Caip19AssetId; info: FungibleAssetMetadata } | null {\n if (!isObject(token)) {\n return null;\n }\n // Skip NFTs — AssetsController tracks them separately.\n if (token.isERC721 === true) {\n return null;\n }\n if (\n typeof token.address !== 'string' ||\n !EVM_ADDRESS_REGEX.test(token.address)\n ) {\n return null;\n }\n // Healed metadata with no symbol would be unusable in the UI; skip it.\n if (typeof token.symbol !== 'string' || token.symbol.length === 0) {\n return null;\n }\n\n const assetId = buildErc20AssetId(caip2, token.address as Hex);\n if (!assetId) {\n return null;\n }\n\n // Skip tokens the user hid/removed — in the legacy controller…\n if (ignoredAddresses.has(token.address.toLowerCase())) {\n return null;\n }\n // …or in the current controller.\n if (hiddenAssetIds.has(assetId.toLowerCase())) {\n return null;\n }\n\n return { assetId, info: buildEvmAssetInfo(token) };\n}\n\n/**\n * Build a `FungibleAssetMetadata` from a raw `TokensController` token entry.\n * The caller has already validated `symbol` as a non-empty string.\n *\n * @param token - Raw token object.\n * @returns The metadata to write into `assetsInfo`.\n */\nfunction buildEvmAssetInfo(\n token: Record<string, unknown>,\n): FungibleAssetMetadata {\n const symbol = token.symbol as string;\n const name =\n typeof token.name === 'string' && token.name.length > 0\n ? token.name\n : symbol;\n const decimals =\n typeof token.decimals === 'number' && Number.isFinite(token.decimals)\n ? token.decimals\n : 0;\n\n const image =\n typeof token.image === 'string' && token.image.length > 0\n ? token.image\n : undefined;\n const aggregators = Array.isArray(token.aggregators)\n ? token.aggregators.filter(\n (aggregator): aggregator is string => typeof aggregator === 'string',\n )\n : undefined;\n\n return {\n type: 'erc20',\n symbol,\n name,\n decimals,\n ...(image ? { image } : {}),\n ...(aggregators ? { aggregators } : {}),\n };\n}\n\n/**\n * Build a map from lowercase account address to account UUID using the\n * legacy `AccountsController.internalAccounts.accounts`.\n *\n * @param legacyState - Untrusted legacy state root.\n * @returns Map of lowercase address to account ID (empty on invalid input).\n */\nfunction buildAddressToAccountIdMap(\n legacyState: unknown,\n): Record<string, AccountId> {\n const accounts = readPath(legacyState, [\n 'AccountsController',\n 'internalAccounts',\n 'accounts',\n ]);\n if (!isObject(accounts)) {\n return {};\n }\n\n const map: Record<string, AccountId> = {};\n for (const [id, account] of Object.entries(accounts)) {\n if (\n isObject(account) &&\n typeof account.address === 'string' &&\n account.address.length > 0\n ) {\n map[account.address.toLowerCase()] = id;\n }\n }\n return map;\n}\n\n/**\n * Collect the set of CAIP-19 asset IDs (lowercased) marked `hidden: true` in\n * the current `assetPreferences`. Lowercasing lets callers compare against\n * checksummed asset IDs case-insensitively.\n *\n * @param currentState - Current `AssetsController` state slices.\n * @returns Set of lowercase hidden asset IDs.\n */\nfunction collectHiddenAssetIds(currentState: CurrentAssetsState): Set<string> {\n const hidden = new Set<string>();\n for (const [assetId, preference] of Object.entries(\n currentState.assetPreferences,\n )) {\n if (isObject(preference) && preference.hidden === true) {\n hidden.add(assetId.toLowerCase());\n }\n }\n return hidden;\n}\n\n/**\n * Collect the lowercase token addresses ignored (hidden) in the legacy\n * `TokensController.allIgnoredTokens` for a given chain and account. The\n * account key is matched case-insensitively.\n *\n * @param allIgnoredTokens - The legacy `allIgnoredTokens` map (possibly missing).\n * @param hexChainId - The hex chain ID being processed.\n * @param accountAddress - The account address whose ignored list to read.\n * @returns Set of lowercase ignored token addresses.\n */\nfunction collectIgnoredAddresses(\n allIgnoredTokens: unknown,\n hexChainId: string,\n accountAddress: string,\n): Set<string> {\n const result = new Set<string>();\n if (!isObject(allIgnoredTokens)) {\n return result;\n }\n\n const chainEntry = allIgnoredTokens[hexChainId];\n if (!isObject(chainEntry)) {\n return result;\n }\n\n const lowerAccount = accountAddress.toLowerCase();\n for (const [address, list] of Object.entries(chainEntry)) {\n if (address.toLowerCase() !== lowerAccount || !Array.isArray(list)) {\n continue;\n }\n for (const ignored of list) {\n if (typeof ignored === 'string') {\n result.add(ignored.toLowerCase());\n }\n }\n }\n return result;\n}\n\n/**\n * Convert a hex chain ID (e.g. '0x1') to a CAIP-2 chain ID (e.g. 'eip155:1').\n *\n * @param hexChainId - The hex-encoded EVM chain ID.\n * @returns The CAIP-2 chain ID, or `null` when the input cannot be parsed.\n */\nfunction hexChainIdToCaip2(hexChainId: string): string | null {\n if (!/^0x[0-9a-fA-F]+$/u.test(hexChainId)) {\n return null;\n }\n const decimal = Number.parseInt(hexChainId, 16);\n if (!Number.isFinite(decimal)) {\n return null;\n }\n return `${EVM_CHAIN_NAMESPACE}:${decimal}`;\n}\n\n/**\n * Build the checksummed CAIP-19 asset ID for an ERC-20 token.\n *\n * @param caip2 - The CAIP-2 chain ID (e.g. 'eip155:14').\n * @param tokenAddress - The ERC-20 contract address.\n * @returns The asset ID, or `null` when the address cannot be checksummed.\n */\nfunction buildErc20AssetId(\n caip2: string,\n tokenAddress: Hex,\n): Caip19AssetId | null {\n let checksummed: string;\n try {\n checksummed = getChecksumAddress(tokenAddress);\n } catch {\n return null;\n }\n return `${caip2}/erc20:${checksummed}` as Caip19AssetId;\n}\n\n/**\n * Read a nested path on an unknown value, returning `undefined` if any\n * intermediate key is missing or not an object.\n *\n * @param root - The value to read from.\n * @param path - Sequence of keys to traverse.\n * @returns The value at the path, or `undefined`.\n */\nfunction readPath(root: unknown, path: string[]): unknown {\n return path.reduce<unknown>((cursor, key) => {\n if (!isObject(cursor) || !hasProperty(cursor, key)) {\n return undefined;\n }\n return cursor[key];\n }, root);\n}\n\n/**\n * Record `assetId` as a custom-asset addition for `accountId` unless it is\n * already tracked in the current `assetsBalance` (mutual exclusion), already\n * present in the current `customAssets`, or already queued as an addition.\n *\n * @param currentState - Current `AssetsController` state slices.\n * @param additions - The custom-asset additions accumulated so far (mutated).\n * @param accountId - The account UUID.\n * @param assetId - CAIP-19 asset identifier.\n */\nfunction addCustomAssetAddition(\n currentState: CurrentAssetsState,\n additions: Record<AccountId, Caip19AssetId[]>,\n accountId: AccountId,\n assetId: Caip19AssetId,\n): void {\n if (currentState.assetsBalance[accountId]?.[assetId]) {\n return;\n }\n if (currentState.customAssets[accountId]?.includes(assetId)) {\n return;\n }\n\n const queued = additions[accountId] ?? [];\n if (!queued.includes(assetId)) {\n queued.push(assetId);\n }\n additions[accountId] = queued;\n}\n\nconst cleanupLog = createModuleLogger(projectLogger, 'cleanSpamAssets');\n\n/**\n * TEMPORARY — feature flag for unlock spam cleanup\n *\n * @param remoteFeatureFlags - RemoteFeatureFlag state.\n * @returns `true` only when the flag explicitly enables the cleanup.\n */\nexport function isUnlockCleanupEnabled(remoteFeatureFlags: unknown): boolean {\n if (!isObject(remoteFeatureFlags)) {\n return false;\n }\n\n const flag = remoteFeatureFlags.assetsUnifyState;\n return (\n isObject(flag) &&\n hasProperty(flag, 'useUnlockCleanup') &&\n flag.useUnlockCleanup === true\n );\n}\n\nconst FETCH_TIMEOUT_MS = 15_000;\nconst DEFAULT_OCCURRENCE_FLOOR = 3;\n\n/** The slices of Token API surface the cleanup uses. */\nexport type SpamTokensApiClient = Pick<ApiPlatformClient, 'tokens' | 'token'>;\n\nexport type CleanSpamAssetsState = Pick<\n AssetsControllerStateInternal,\n 'assetsInfo' | 'assetsBalance' | 'assetsPrice' | 'customAssets'\n>;\n\nexport type CleanSpamAssetsOptions = {\n state: CleanSpamAssetsState;\n apiClient: SpamTokensApiClient;\n captureException?: (error: Error) => void;\n};\n\n/**\n * Cleanup of Spam Assets.\n * The state can be persisted with spam tokens through boundary faults (API loosened, WS passing in spam)\n *\n * @param options - Input params.\n * @param options.state - Current controller state (read-only).\n * @param options.apiClient - Token API client.\n * @param options.captureException - Optional reporter for failures.\n * @returns Updated or original controller state\n */\nexport async function cleanSpamAssets({\n state,\n apiClient,\n captureException,\n}: CleanSpamAssetsOptions): Promise<{\n spamAssetIds: Caip19AssetId[];\n applyPatch: typeof applyCleanupPatch;\n} | null> {\n const candidates = collectSpamCleanupCandidates(state);\n if (candidates.length === 0) {\n return null;\n }\n\n try {\n const floors = await fetchSuggestedOccurrenceFloors(apiClient);\n const spamAssetIds: Caip19AssetId[] = [];\n\n for (const batch of divideIntoBatches(candidates, {\n batchSize: 50,\n })) {\n const belowFloorAssetIds = await findBelowFloorAssetIds(\n batch,\n floors,\n apiClient,\n ).catch(() => []);\n if (belowFloorAssetIds.length > 0) {\n cleanupLog('Classified a batch of assets as spam', {\n batchSize: batch.length,\n spamCount: belowFloorAssetIds.length,\n });\n spamAssetIds.push(...belowFloorAssetIds);\n }\n }\n\n if (spamAssetIds.length === 0) {\n return null;\n }\n\n return { spamAssetIds, applyPatch: applyCleanupPatch };\n } catch (error) {\n // API calls failed, cleanup not performed. Will be retried when next invoked.\n cleanupLog(\n 'Spam cleanup failed; leaving remaining assets untouched',\n error,\n );\n captureException?.(\n new Error(\n `AssetsController: assetsInfo spam cleanup failed: ${getErrorMessage(\n error,\n )}`,\n ),\n );\n return null;\n }\n}\n\n/**\n * Collect the assets eligible for cleanup:\n * - ERC-20 token\n * - Not in excluded list\n * - Not a custom asset\n * - On Account API covered chains\n *\n * @param state - Current controller state.\n * @param state.assetsInfo - Tracked asset metadata, keyed by CAIP-19 ID.\n * @param state.assetsBalance - Per-account balances, keyed by CAIP-19 ID.\n * @param state.customAssets - Per-account custom asset IDs.\n * @returns Candidate asset IDs, one per asset whatever casing it is held in.\n */\nfunction collectSpamCleanupCandidates({\n assetsInfo,\n assetsBalance,\n customAssets,\n}: Pick<\n CleanSpamAssetsState,\n 'assetsInfo' | 'assetsBalance' | 'customAssets'\n>): Caip19AssetId[] {\n const customAssetIds = new Set(\n Object.values(customAssets)\n .flat()\n .map((assetId) => assetId.toLowerCase()),\n );\n\n const excludedAssets = [...DEFAULT_TRACKED_ASSETS_BY_CHAIN.values()]\n .flat()\n .map((a) => a.toLowerCase());\n\n const trackedAssetIds = new Map<string, Caip19AssetId>();\n const assetInfoAssetIds = Object.keys(assetsInfo) as Caip19AssetId[];\n const balanceAssetIds = Object.values(assetsBalance).flatMap((balances) =>\n Object.keys(balances),\n ) as Caip19AssetId[];\n for (const assetId of [...assetInfoAssetIds, ...balanceAssetIds]) {\n const lowerId = assetId.toLowerCase();\n if (!trackedAssetIds.has(lowerId)) {\n trackedAssetIds.set(lowerId, assetId);\n }\n }\n\n return [...trackedAssetIds.values()].filter((assetId) => {\n const lowerId = assetId.toLowerCase();\n const [chainId, asset] = lowerId.split('/');\n\n const isERC20 = Boolean(asset?.startsWith('erc20:'));\n const isNotExcluded = !excludedAssets.includes(lowerId);\n const isNotCustomAsset = !customAssetIds.has(lowerId);\n const isOnAccountAPICoveredChain =\n ACCOUNT_API_SUPPORTED_CHAIN_IDS.has(chainId);\n\n return (\n isERC20 && isNotExcluded && isNotCustomAsset && isOnAccountAPICoveredChain\n );\n });\n}\n\n/**\n * Fetch and filter assets by occurrence floor.\n * If fetch fails, error is propagated (fail-close, cleanup not performed)\n *\n * @param assetIds - A single batch of candidate asset IDs.\n * @param floors - Suggested occurrence floors keyed by decimal chain ID.\n * @param apiClient - Token API client.\n * @returns The subset of `assetIds` that falls below its chain's floor.\n */\nasync function findBelowFloorAssetIds(\n assetIds: Caip19AssetId[],\n floors: Record<string, number>,\n apiClient: SpamTokensApiClient,\n): Promise<Caip19AssetId[]> {\n try {\n const assets = await fetchWithTimeout(\n () =>\n apiClient.tokens.fetchV3Assets(\n assetIds,\n { includeOccurrences: true },\n { staleTime: 0, gcTime: 0 },\n ),\n FETCH_TIMEOUT_MS,\n );\n\n const occurrencesByLowerId = new Map(\n assets.map((asset) => [asset.assetId.toLowerCase(), asset.occurrences]),\n );\n\n return assetIds.filter((assetId) => {\n const chainReference = assetId.split(':')[1]?.split('/')[0] ?? '';\n const floor = floors[chainReference] ?? DEFAULT_OCCURRENCE_FLOOR;\n return (occurrencesByLowerId.get(assetId.toLowerCase()) ?? 0) < floor;\n });\n } catch (error) {\n cleanupLog('Failed to fetch assets', error);\n throw error;\n }\n}\n\n/**\n * Fetch per-chain suggested occurrence floors.\n * If fetch fails, error is propagated (fail-close, cleanup not performed)\n *\n * @param apiClient - Token API client.\n * @returns Map of decimal chain ID to suggested floor.\n */\nasync function fetchSuggestedOccurrenceFloors(\n apiClient: SpamTokensApiClient,\n): Promise<Record<string, number>> {\n try {\n return await fetchWithTimeout(\n () =>\n apiClient.token.fetchV1SuggestedOccurrenceFloors({\n staleTime: 0,\n gcTime: 0,\n }),\n FETCH_TIMEOUT_MS,\n );\n } catch (error) {\n cleanupLog('Failed to fetch suggested occurrence floors', error);\n throw error;\n }\n}\n\nfunction applyCleanupPatch(\n state: Pick<\n CleanSpamAssetsState,\n 'assetsInfo' | 'assetsBalance' | 'assetsPrice'\n >,\n patch: {\n spamAssetIds: Caip19AssetId[];\n },\n): void {\n const { spamAssetIds } = patch;\n\n const spam = new Set(spamAssetIds.map((assetId) => assetId.toLowerCase()));\n if (spam.size === 0) {\n return;\n }\n const isSpam = (assetId: string): boolean => spam.has(assetId.toLowerCase());\n\n for (const assetId of Object.keys(state.assetsInfo).filter(isSpam)) {\n delete state.assetsInfo[assetId as Caip19AssetId];\n }\n for (const balances of Object.values(state.assetsBalance)) {\n for (const assetId of Object.keys(balances).filter(isSpam)) {\n delete balances[assetId as Caip19AssetId];\n }\n }\n for (const assetId of Object.keys(state.assetsPrice).filter(isSpam)) {\n delete state.assetsPrice[assetId as Caip19AssetId];\n }\n\n cleanupLog('Removed spam assets', { count: spam.size });\n}\n"]}
@@ -34,14 +34,9 @@ export type FastFetchSources = {
34
34
  * @param options - Lane options.
35
35
  * @param options.isBasicFunctionality - When false, only the staking balance and
36
36
  * detection run; no network-backed source is used.
37
- * @param options.includeCustomAssetGraduation - `true` on the Accounts API v5
38
- * lane. The v6 lane never graduates custom assets: it sends the pins to the
39
- * endpoint as `includeAssetIds`, and a chain that comes back without all of
40
- * them is reported as errored so the RPC fallback refetches it.
41
37
  * @returns The composed source list, ready for `executeAssetsPipeline`.
42
38
  */
43
39
  export declare function buildFastFetchSources(sources: FastFetchSources, options: {
44
40
  isBasicFunctionality: boolean;
45
- includeCustomAssetGraduation: boolean;
46
41
  }): AssetsDataSource[];
47
42
  //# sourceMappingURL=buildFastFetchSources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildFastFetchSources.d.ts","sourceRoot":"","sources":["../../src/pipeline/buildFastFetchSources.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB,EAAE,aAAa,CAAC;IACrC,uBAAuB,EAAE,aAAa,CAAC;IACvC,+BAA+B,EAAE,gBAAgB,CAAC;IAClD,qBAAqB,EAAE,gBAAgB,CAAC;IACxC,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,eAAe,EAAE,gBAAgB,CAAC;IAClC,eAAe,EAAE,gBAAgB,CAAC;CACnC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE;IACP,oBAAoB,EAAE,OAAO,CAAC;IAC9B,4BAA4B,EAAE,OAAO,CAAC;CACvC,GACA,gBAAgB,EAAE,CA2BpB"}
1
+ {"version":3,"file":"buildFastFetchSources.d.ts","sourceRoot":"","sources":["../../src/pipeline/buildFastFetchSources.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB,EAAE,aAAa,CAAC;IACrC,uBAAuB,EAAE,aAAa,CAAC;IACvC,+BAA+B,EAAE,gBAAgB,CAAC;IAClD,qBAAqB,EAAE,gBAAgB,CAAC;IACxC,mBAAmB,EAAE,gBAAgB,CAAC;IACtC,eAAe,EAAE,gBAAgB,CAAC;IAClC,eAAe,EAAE,gBAAgB,CAAC;CACnC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE;IAAE,oBAAoB,EAAE,OAAO,CAAA;CAAE,GACzC,gBAAgB,EAAE,CAyBpB"}
@@ -17,10 +17,6 @@ import { createParallelBalanceMiddleware, createParallelMiddleware, } from '../m
17
17
  * @param options - Lane options.
18
18
  * @param options.isBasicFunctionality - When false, only the staking balance and
19
19
  * detection run; no network-backed source is used.
20
- * @param options.includeCustomAssetGraduation - `true` on the Accounts API v5
21
- * lane. The v6 lane never graduates custom assets: it sends the pins to the
22
- * endpoint as `includeAssetIds`, and a chain that comes back without all of
23
- * them is reported as errored so the RPC fallback refetches it.
24
20
  * @returns The composed source list, ready for `executeAssetsPipeline`.
25
21
  */
26
22
  export function buildFastFetchSources(sources, options) {
@@ -33,9 +29,7 @@ export function buildFastFetchSources(sources, options) {
33
29
  accountsApiDataSource,
34
30
  stakedBalanceDataSource,
35
31
  ]),
36
- ...(options.includeCustomAssetGraduation
37
- ? [customAssetGraduationMiddleware]
38
- : []),
32
+ customAssetGraduationMiddleware,
39
33
  rpcFallbackMiddleware,
40
34
  detectionMiddleware,
41
35
  createParallelMiddleware([tokenDataSource, priceDataSource]),
@@ -1 +1 @@
1
- {"version":3,"file":"buildFastFetchSources.js","sourceRoot":"","sources":["../../src/pipeline/buildFastFetchSources.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,GACzB,MAAM,sCAAsC,CAAC;AAqB9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAyB,EACzB,OAGC;IAED,MAAM,EACJ,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,GAAG,OAAO,CAAC;IAEZ,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC;QAClC,OAAO,CAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAAC;IACxD,CAAC;IAED,OAAO;QACL,+BAA+B,CAAC;YAC9B,qBAAqB;YACrB,uBAAuB;SACxB,CAAC;QACF,GAAG,CAAC,OAAO,CAAC,4BAA4B;YACtC,CAAC,CAAC,CAAC,+BAA+B,CAAC;YACnC,CAAC,CAAC,EAAE,CAAC;QACP,qBAAqB;QACrB,mBAAmB;QACnB,wBAAwB,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;KAC7D,CAAC;AACJ,CAAC","sourcesContent":["import {\n createParallelBalanceMiddleware,\n createParallelMiddleware,\n} from '../middlewares/ParallelMiddleware.js';\nimport type { BalanceSource } from '../middlewares/ParallelMiddleware.js';\nimport type { AssetsDataSource } from '../types.js';\n\n/**\n * The sources the fast fetch lane composes, in the roles the lane assigns them.\n *\n * `AssetsController` constructs all of these unconditionally, so the lane can\n * assume every role is filled. `isBasicFunctionality` decides which of them\n * actually run, not which are supplied.\n */\nexport type FastFetchSources = {\n accountsApiDataSource: BalanceSource;\n stakedBalanceDataSource: BalanceSource;\n customAssetGraduationMiddleware: AssetsDataSource;\n rpcFallbackMiddleware: AssetsDataSource;\n detectionMiddleware: AssetsDataSource;\n tokenDataSource: AssetsDataSource;\n priceDataSource: AssetsDataSource;\n};\n\n/**\n * Compose the fast fetch lane: balances in parallel → custom-asset graduation →\n * RPC fallback → detection → token metadata and prices in parallel.\n *\n * Snap and RPC balance sources are deliberately absent — the controller runs\n * those in a background lane because of their latency.\n *\n * Ordering carries two invariants:\n * - Graduation runs BEFORE the RPC fallback so it only ever sees Accounts\n * API / websocket balances. RPC intentionally carries custom assets and must\n * never trigger graduation.\n * - Detection runs before token and price enrichment, which both read\n * `response.detectedAssets`.\n *\n * @param sources - The sources to place into the lane.\n * @param options - Lane options.\n * @param options.isBasicFunctionality - When false, only the staking balance and\n * detection run; no network-backed source is used.\n * @param options.includeCustomAssetGraduation - `true` on the Accounts API v5\n * lane. The v6 lane never graduates custom assets: it sends the pins to the\n * endpoint as `includeAssetIds`, and a chain that comes back without all of\n * them is reported as errored so the RPC fallback refetches it.\n * @returns The composed source list, ready for `executeAssetsPipeline`.\n */\nexport function buildFastFetchSources(\n sources: FastFetchSources,\n options: {\n isBasicFunctionality: boolean;\n includeCustomAssetGraduation: boolean;\n },\n): AssetsDataSource[] {\n const {\n accountsApiDataSource,\n stakedBalanceDataSource,\n customAssetGraduationMiddleware,\n rpcFallbackMiddleware,\n detectionMiddleware,\n tokenDataSource,\n priceDataSource,\n } = sources;\n\n if (!options.isBasicFunctionality) {\n return [stakedBalanceDataSource, detectionMiddleware];\n }\n\n return [\n createParallelBalanceMiddleware([\n accountsApiDataSource,\n stakedBalanceDataSource,\n ]),\n ...(options.includeCustomAssetGraduation\n ? [customAssetGraduationMiddleware]\n : []),\n rpcFallbackMiddleware,\n detectionMiddleware,\n createParallelMiddleware([tokenDataSource, priceDataSource]),\n ];\n}\n"]}
1
+ {"version":3,"file":"buildFastFetchSources.js","sourceRoot":"","sources":["../../src/pipeline/buildFastFetchSources.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,GACzB,MAAM,sCAAsC,CAAC;AAqB9C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAyB,EACzB,OAA0C;IAE1C,MAAM,EACJ,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,GAAG,OAAO,CAAC;IAEZ,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC;QAClC,OAAO,CAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAAC;IACxD,CAAC;IAED,OAAO;QACL,+BAA+B,CAAC;YAC9B,qBAAqB;YACrB,uBAAuB;SACxB,CAAC;QACF,+BAA+B;QAC/B,qBAAqB;QACrB,mBAAmB;QACnB,wBAAwB,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;KAC7D,CAAC;AACJ,CAAC","sourcesContent":["import {\n createParallelBalanceMiddleware,\n createParallelMiddleware,\n} from '../middlewares/ParallelMiddleware.js';\nimport type { BalanceSource } from '../middlewares/ParallelMiddleware.js';\nimport type { AssetsDataSource } from '../types.js';\n\n/**\n * The sources the fast fetch lane composes, in the roles the lane assigns them.\n *\n * `AssetsController` constructs all of these unconditionally, so the lane can\n * assume every role is filled. `isBasicFunctionality` decides which of them\n * actually run, not which are supplied.\n */\nexport type FastFetchSources = {\n accountsApiDataSource: BalanceSource;\n stakedBalanceDataSource: BalanceSource;\n customAssetGraduationMiddleware: AssetsDataSource;\n rpcFallbackMiddleware: AssetsDataSource;\n detectionMiddleware: AssetsDataSource;\n tokenDataSource: AssetsDataSource;\n priceDataSource: AssetsDataSource;\n};\n\n/**\n * Compose the fast fetch lane: balances in parallel → custom-asset graduation →\n * RPC fallback → detection → token metadata and prices in parallel.\n *\n * Snap and RPC balance sources are deliberately absent — the controller runs\n * those in a background lane because of their latency.\n *\n * Ordering carries two invariants:\n * - Graduation runs BEFORE the RPC fallback so it only ever sees Accounts\n * API / websocket balances. RPC intentionally carries custom assets and must\n * never trigger graduation.\n * - Detection runs before token and price enrichment, which both read\n * `response.detectedAssets`.\n *\n * @param sources - The sources to place into the lane.\n * @param options - Lane options.\n * @param options.isBasicFunctionality - When false, only the staking balance and\n * detection run; no network-backed source is used.\n * @returns The composed source list, ready for `executeAssetsPipeline`.\n */\nexport function buildFastFetchSources(\n sources: FastFetchSources,\n options: { isBasicFunctionality: boolean },\n): AssetsDataSource[] {\n const {\n accountsApiDataSource,\n stakedBalanceDataSource,\n customAssetGraduationMiddleware,\n rpcFallbackMiddleware,\n detectionMiddleware,\n tokenDataSource,\n priceDataSource,\n } = sources;\n\n if (!options.isBasicFunctionality) {\n return [stakedBalanceDataSource, detectionMiddleware];\n }\n\n return [\n createParallelBalanceMiddleware([\n accountsApiDataSource,\n stakedBalanceDataSource,\n ]),\n customAssetGraduationMiddleware,\n rpcFallbackMiddleware,\n detectionMiddleware,\n createParallelMiddleware([tokenDataSource, priceDataSource]),\n ];\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { TraceCallback, TraceContext } from '@metamask/controller-utils';
2
- import type { AssetsDataSource, DataRequest, DataResponse } from '../types.js';
2
+ import type { AssetsControllerStateInternal, AssetsDataSource, DataRequest, DataResponse } from '../types.js';
3
3
  export type ExecuteAssetsPipelineParams = {
4
4
  /** Data sources or middlewares with getName() and assetsMiddleware. */
5
5
  sources: AssetsDataSource[];
@@ -7,6 +7,8 @@ export type ExecuteAssetsPipelineParams = {
7
7
  request: DataRequest;
8
8
  /** Optional initial response (for enriching existing data). */
9
9
  initialResponse?: DataResponse;
10
+ /** Reads the current controller state, exposed to every middleware via context. */
11
+ getAssetsState: () => AssetsControllerStateInternal;
10
12
  /** Reports middleware failures as an issue. Never allowed to throw. */
11
13
  captureException?: (error: Error) => void;
12
14
  /** Optional parent Sentry span; per-source timings nest under it. */
@@ -1 +1 @@
1
- {"version":3,"file":"executeAssetsPipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/executeAssetsPipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,KAAK,EACV,gBAAgB,EAEhB,WAAW,EACX,YAAY,EAKb,MAAM,aAAa,CAAC;AAMrB,MAAM,MAAM,2BAA2B,GAAG;IACxC,uEAAuE;IACvE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,wBAAwB;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,YAAY,CAAC;IAC/B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC1C,qEAAqE;IACrE,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,oEAAoE;IACpE,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC;IACT,QAAQ,EAAE,YAAY,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C,CAAC,CAgHD"}
1
+ {"version":3,"file":"executeAssetsPipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/executeAssetsPipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,WAAW,EACX,YAAY,EAKb,MAAM,aAAa,CAAC;AAMrB,MAAM,MAAM,2BAA2B,GAAG;IACxC,uEAAuE;IACvE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,wBAAwB;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,YAAY,CAAC;IAC/B,mFAAmF;IACnF,cAAc,EAAE,MAAM,6BAA6B,CAAC;IACpD,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC1C,qEAAqE;IACrE,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,oEAAoE;IACpE,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC;IACT,QAAQ,EAAE,YAAY,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C,CAAC,CA+HD"}
@@ -13,7 +13,7 @@ const TRACE_DATA_SOURCE_ERROR = 'AssetsDataSourceError';
13
13
  * @returns Response and durationByDataSource (exclusive ms per source name).
14
14
  */
15
15
  export async function executeAssetsPipeline(params) {
16
- const { sources, request, initialResponse = {}, captureException, parentContext, trace, } = params;
16
+ const { sources, request, initialResponse = {}, getAssetsState, captureException, parentContext, trace, } = params;
17
17
  const names = sources.map((source) => source.getName());
18
18
  const middlewares = sources.map((source) => source.assetsMiddleware);
19
19
  const inclusive = [];
@@ -41,6 +41,7 @@ export async function executeAssetsPipeline(params) {
41
41
  const result = await chain({
42
42
  request,
43
43
  response: initialResponse,
44
+ getAssetsState,
44
45
  });
45
46
  const durationByDataSource = {};
46
47
  for (let i = 0; i < inclusive.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"executeAssetsPipeline.js","sourceRoot":"","sources":["../../src/pipeline/executeAssetsPipeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TraceCallback, TraceContext } from '@metamask/controller-utils';\n\nimport { AssetsDataSourceError } from '../errors.js';\nimport type {\n AssetsDataSource,\n Context,\n DataRequest,\n DataResponse,\n FetchContext,\n FetchNextFunction,\n Middleware,\n NextFunction,\n} from '../types.js';\nimport { emitTrace } from '../utils/trace.js';\n\nconst TRACE_DATA_SOURCE_TIMING = 'AssetsDataSourceTiming';\nconst TRACE_DATA_SOURCE_ERROR = 'AssetsDataSourceError';\n\nexport type ExecuteAssetsPipelineParams = {\n /** Data sources or middlewares with getName() and assetsMiddleware. */\n sources: AssetsDataSource[];\n /** The data request. */\n request: DataRequest;\n /** Optional initial response (for enriching existing data). */\n initialResponse?: DataResponse;\n /** Reports middleware failures as an issue. Never allowed to throw. */\n captureException?: (error: Error) => void;\n /** Optional parent Sentry span; per-source timings nest under it. */\n parentContext?: TraceContext;\n /** Omit after unlock/first-init so timing spans are not emitted. */\n trace?: TraceCallback;\n};\n\n/**\n * Execute middlewares with request/response context.\n * Returns response and exclusive duration per source (sum ≈ wall time).\n *\n * Extracted from `AssetsController` so a pipeline can be composed and driven\n * without booting the controller (see `buildFastFetchSources`).\n *\n * @param params - Middleware execution options.\n * @returns Response and durationByDataSource (exclusive ms per source name).\n */\nexport async function executeAssetsPipeline(\n params: ExecuteAssetsPipelineParams,\n): Promise<{\n response: DataResponse;\n durationByDataSource: Record<string, number>;\n}> {\n const {\n sources,\n request,\n initialResponse = {},\n captureException,\n parentContext,\n trace,\n } = params;\n const names = sources.map((source) => source.getName());\n const middlewares = sources.map((source) => source.assetsMiddleware);\n const inclusive: number[] = [];\n const wrapped = middlewares.map(\n (middleware, i) =>\n (async (ctx: FetchContext, next: FetchNextFunction): Promise<Context> => {\n const start = performance.now();\n try {\n return await middleware(ctx, next);\n } finally {\n inclusive[i] = performance.now() - start;\n }\n }) as Middleware,\n );\n\n const middlewareErrors: string[] = [];\n const chain = wrapped.reduceRight<NextFunction>(\n (next, middleware, index) =>\n async (ctx): Promise<Context> => {\n try {\n return await middleware(ctx, next);\n } catch (error) {\n const sourceName = names[index] ?? `middleware_${index}`;\n middlewareErrors.push(sourceName);\n console.error('[AssetsController] Middleware failed:', error);\n return next(ctx);\n }\n },\n async (ctx) => ctx,\n );\n\n const result = await chain({\n request,\n response: initialResponse,\n });\n\n const durationByDataSource: Record<string, number> = {};\n for (let i = 0; i < inclusive.length; i++) {\n const nextInc = i + 1 < inclusive.length ? (inclusive[i + 1] ?? 0) : 0;\n const exclusive = Math.max(0, (inclusive[i] ?? 0) - nextInc);\n const name = names[i];\n if (name !== undefined) {\n durationByDataSource[name] = exclusive;\n }\n }\n if (result.durationByDataSource) {\n for (const [key, ms] of Object.entries(result.durationByDataSource)) {\n durationByDataSource[key] = ms;\n }\n }\n\n // Emit per-source timing as subspans under the parent fetch/update span\n // (no-op when `trace` is omitted — unlock/first-init only).\n for (const [sourceName, durationMs] of Object.entries(durationByDataSource)) {\n emitTrace({\n name: TRACE_DATA_SOURCE_TIMING,\n trace,\n data: {\n source: sourceName,\n duration_ms: durationMs,\n chain_count: request.chainIds.length,\n account_count: request.accountsWithSupportedChains.length,\n },\n tags: {\n controller: 'AssetsController',\n // String tag so Spans widgets can group by `source`.\n source: sourceName,\n },\n parentContext,\n });\n }\n\n // Failed middlewares: Issues (optional) + perf/Dashboard spans\n if (middlewareErrors.length > 0) {\n const failedSources = middlewareErrors.join(',');\n const assetsError = new AssetsDataSourceError({\n failedSources,\n errorCount: middlewareErrors.length,\n chainCount: request.chainIds.length,\n });\n try {\n captureException?.(assetsError);\n } catch {\n // Never let telemetry throw.\n }\n emitTrace({\n name: TRACE_DATA_SOURCE_ERROR,\n trace,\n data: {\n failed_sources: failedSources,\n error_count: middlewareErrors.length,\n chain_count: request.chainIds.length,\n },\n tags: {\n controller: 'AssetsController',\n severity: 'error',\n error_type: assetsError.name,\n },\n parentContext,\n });\n }\n\n return { response: result.response, durationByDataSource };\n}\n"]}
1
+ {"version":3,"file":"executeAssetsPipeline.js","sourceRoot":"","sources":["../../src/pipeline/executeAssetsPipeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TraceCallback, TraceContext } from '@metamask/controller-utils';\n\nimport { AssetsDataSourceError } from '../errors.js';\nimport type {\n AssetsControllerStateInternal,\n AssetsDataSource,\n DataRequest,\n DataResponse,\n FetchContext,\n FetchNextFunction,\n Middleware,\n NextFunction,\n} from '../types.js';\nimport { emitTrace } from '../utils/trace.js';\n\nconst TRACE_DATA_SOURCE_TIMING = 'AssetsDataSourceTiming';\nconst TRACE_DATA_SOURCE_ERROR = 'AssetsDataSourceError';\n\nexport type ExecuteAssetsPipelineParams = {\n /** Data sources or middlewares with getName() and assetsMiddleware. */\n sources: AssetsDataSource[];\n /** The data request. */\n request: DataRequest;\n /** Optional initial response (for enriching existing data). */\n initialResponse?: DataResponse;\n /** Reads the current controller state, exposed to every middleware via context. */\n getAssetsState: () => AssetsControllerStateInternal;\n /** Reports middleware failures as an issue. Never allowed to throw. */\n captureException?: (error: Error) => void;\n /** Optional parent Sentry span; per-source timings nest under it. */\n parentContext?: TraceContext;\n /** Omit after unlock/first-init so timing spans are not emitted. */\n trace?: TraceCallback;\n};\n\n/**\n * Execute middlewares with request/response context.\n * Returns response and exclusive duration per source (sum ≈ wall time).\n *\n * Extracted from `AssetsController` so a pipeline can be composed and driven\n * without booting the controller (see `buildFastFetchSources`).\n *\n * @param params - Middleware execution options.\n * @returns Response and durationByDataSource (exclusive ms per source name).\n */\nexport async function executeAssetsPipeline(\n params: ExecuteAssetsPipelineParams,\n): Promise<{\n response: DataResponse;\n durationByDataSource: Record<string, number>;\n}> {\n const {\n sources,\n request,\n initialResponse = {},\n getAssetsState,\n captureException,\n parentContext,\n trace,\n } = params;\n const names = sources.map((source) => source.getName());\n const middlewares = sources.map((source) => source.assetsMiddleware);\n const inclusive: number[] = [];\n const wrapped = middlewares.map(\n (middleware, i) =>\n (async (\n ctx: FetchContext,\n next: FetchNextFunction,\n ): Promise<{\n request: DataRequest;\n response: DataResponse;\n getAssetsState: () => AssetsControllerStateInternal;\n }> => {\n const start = performance.now();\n try {\n return await middleware(ctx, next);\n } finally {\n inclusive[i] = performance.now() - start;\n }\n }) as Middleware,\n );\n\n const middlewareErrors: string[] = [];\n const chain = wrapped.reduceRight<NextFunction>(\n (next, middleware, index) =>\n async (\n ctx,\n ): Promise<{\n request: DataRequest;\n response: DataResponse;\n getAssetsState: () => AssetsControllerStateInternal;\n }> => {\n try {\n return await middleware(ctx, next);\n } catch (error) {\n const sourceName = names[index] ?? `middleware_${index}`;\n middlewareErrors.push(sourceName);\n console.error('[AssetsController] Middleware failed:', error);\n return next(ctx);\n }\n },\n async (ctx) => ctx,\n );\n\n const result = await chain({\n request,\n response: initialResponse,\n getAssetsState,\n });\n\n const durationByDataSource: Record<string, number> = {};\n for (let i = 0; i < inclusive.length; i++) {\n const nextInc = i + 1 < inclusive.length ? (inclusive[i + 1] ?? 0) : 0;\n const exclusive = Math.max(0, (inclusive[i] ?? 0) - nextInc);\n const name = names[i];\n if (name !== undefined) {\n durationByDataSource[name] = exclusive;\n }\n }\n if (result.durationByDataSource) {\n for (const [key, ms] of Object.entries(result.durationByDataSource)) {\n durationByDataSource[key] = ms;\n }\n }\n\n // Emit per-source timing as subspans under the parent fetch/update span\n // (no-op when `trace` is omitted — unlock/first-init only).\n for (const [sourceName, durationMs] of Object.entries(durationByDataSource)) {\n emitTrace({\n name: TRACE_DATA_SOURCE_TIMING,\n trace,\n data: {\n source: sourceName,\n duration_ms: durationMs,\n chain_count: request.chainIds.length,\n account_count: request.accountsWithSupportedChains.length,\n },\n tags: {\n controller: 'AssetsController',\n // String tag so Spans widgets can group by `source`.\n source: sourceName,\n },\n parentContext,\n });\n }\n\n // Failed middlewares: Issues (optional) + perf/Dashboard spans\n if (middlewareErrors.length > 0) {\n const failedSources = middlewareErrors.join(',');\n const assetsError = new AssetsDataSourceError({\n failedSources,\n errorCount: middlewareErrors.length,\n chainCount: request.chainIds.length,\n });\n try {\n captureException?.(assetsError);\n } catch {\n // Never let telemetry throw.\n }\n emitTrace({\n name: TRACE_DATA_SOURCE_ERROR,\n trace,\n data: {\n failed_sources: failedSources,\n error_count: middlewareErrors.length,\n chain_count: request.chainIds.length,\n },\n tags: {\n controller: 'AssetsController',\n severity: 'error',\n error_type: assetsError.name,\n },\n parentContext,\n });\n }\n\n return { response: result.response, durationByDataSource };\n}\n"]}
@@ -287,7 +287,7 @@ export function getAccountIdsForGroup(accountTreeState, groupId) {
287
287
  function aggregateBalances(state, accounts, enabledNetworkMap, trace) {
288
288
  const startTime = trace ? performance.now() : 0;
289
289
  const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;
290
- const metadata = assetsInfo;
290
+ const metadata = (assetsInfo ?? {});
291
291
  const hasPrices = Boolean(assetsPrice) &&
292
292
  (() => {
293
293
  for (const key in assetsPrice) {