@metamask/assets-controller 2.4.0 → 3.1.0

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 (74) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/AssetsController.cjs +30 -12
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +6 -20
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +6 -20
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +30 -12
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/RpcDataSource.cjs +37 -31
  11. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  12. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  13. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  14. package/dist/data-sources/RpcDataSource.mjs +37 -31
  15. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  16. package/dist/data-sources/TokenDataSource.cjs +52 -22
  17. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  18. package/dist/data-sources/TokenDataSource.d.cts +2 -0
  19. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  20. package/dist/data-sources/TokenDataSource.d.mts +2 -0
  21. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  22. package/dist/data-sources/TokenDataSource.mjs +52 -22
  23. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  24. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +22 -8
  25. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -1
  26. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -1
  27. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -1
  28. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +22 -8
  29. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -1
  30. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +4 -1
  31. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  32. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  33. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  34. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +4 -1
  35. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  36. package/dist/data-sources/evm-rpc-services/types/assets.cjs.map +1 -1
  37. package/dist/data-sources/evm-rpc-services/types/assets.d.cts +2 -2
  38. package/dist/data-sources/evm-rpc-services/types/assets.d.cts.map +1 -1
  39. package/dist/data-sources/evm-rpc-services/types/assets.d.mts +2 -2
  40. package/dist/data-sources/evm-rpc-services/types/assets.d.mts.map +1 -1
  41. package/dist/data-sources/evm-rpc-services/types/assets.mjs.map +1 -1
  42. package/dist/data-sources/evm-rpc-services/types/services.cjs.map +1 -1
  43. package/dist/data-sources/evm-rpc-services/types/services.d.cts +2 -2
  44. package/dist/data-sources/evm-rpc-services/types/services.d.cts.map +1 -1
  45. package/dist/data-sources/evm-rpc-services/types/services.d.mts +2 -2
  46. package/dist/data-sources/evm-rpc-services/types/services.d.mts.map +1 -1
  47. package/dist/data-sources/evm-rpc-services/types/services.mjs.map +1 -1
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +2 -2
  50. package/dist/index.d.cts.map +1 -1
  51. package/dist/index.d.mts +2 -2
  52. package/dist/index.d.mts.map +1 -1
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/middlewares/ParallelMiddleware.cjs +56 -16
  55. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
  56. package/dist/middlewares/ParallelMiddleware.d.cts +3 -7
  57. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
  58. package/dist/middlewares/ParallelMiddleware.d.mts +3 -7
  59. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
  60. package/dist/middlewares/ParallelMiddleware.mjs +56 -16
  61. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
  62. package/dist/middlewares/index.cjs.map +1 -1
  63. package/dist/middlewares/index.d.cts +2 -1
  64. package/dist/middlewares/index.d.cts.map +1 -1
  65. package/dist/middlewares/index.d.mts +2 -1
  66. package/dist/middlewares/index.d.mts.map +1 -1
  67. package/dist/middlewares/index.mjs.map +1 -1
  68. package/dist/types.cjs.map +1 -1
  69. package/dist/types.d.cts +15 -0
  70. package/dist/types.d.cts.map +1 -1
  71. package/dist/types.d.mts +15 -0
  72. package/dist/types.d.mts.map +1 -1
  73. package/dist/types.mjs.map +1 -1
  74. package/package.json +5 -5
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _TokenDataSource_instances, _TokenDataSource_apiClient, _TokenDataSource_getSupportedNetworks, _TokenDataSource_filterAssetsByNetwork;
13
+ var _TokenDataSource_instances, _TokenDataSource_apiClient, _TokenDataSource_getNativeAssetIds, _TokenDataSource_getSupportedNetworks, _TokenDataSource_filterAssetsByNetwork;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.TokenDataSource = void 0;
16
16
  const core_backend_1 = require("@metamask/core-backend");
@@ -22,6 +22,7 @@ const types_1 = require("../types.cjs");
22
22
  // CONSTANTS
23
23
  // ============================================================================
24
24
  const CONTROLLER_NAME = 'TokenDataSource';
25
+ const MIN_TOKEN_OCCURRENCES = 3;
25
26
  const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, CONTROLLER_NAME);
26
27
  // ============================================================================
27
28
  // HELPER FUNCTIONS
@@ -91,7 +92,10 @@ class TokenDataSource {
91
92
  this.name = CONTROLLER_NAME;
92
93
  /** ApiPlatformClient for cached API calls */
93
94
  _TokenDataSource_apiClient.set(this, void 0);
95
+ /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */
96
+ _TokenDataSource_getNativeAssetIds.set(this, void 0);
94
97
  __classPrivateFieldSet(this, _TokenDataSource_apiClient, options.queryApiClient, "f");
98
+ __classPrivateFieldSet(this, _TokenDataSource_getNativeAssetIds, options.getNativeAssetIds, "f");
95
99
  }
96
100
  /**
97
101
  * Get the middleware for enriching responses with token metadata.
@@ -108,29 +112,32 @@ class TokenDataSource {
108
112
  return (0, types_1.forDataTypes)(['metadata'], async (ctx, next) => {
109
113
  // Extract response from context
110
114
  const { response } = ctx;
111
- // Only fetch metadata for detected assets (assets without metadata)
112
- if (!response.detectedAssets) {
113
- return next(ctx);
114
- }
115
115
  const { assetsInfo: stateMetadata } = ctx.getAssetsState();
116
116
  const assetIdsNeedingMetadata = new Set();
117
- for (const detectedIds of Object.values(response.detectedAssets)) {
118
- for (const assetId of detectedIds) {
119
- // Skip if response already has metadata with image
120
- const responseMetadata = response.assetsInfo?.[assetId];
121
- if (responseMetadata?.image) {
122
- continue;
123
- }
124
- // Skip if state already has metadata with image
125
- const existingMetadata = stateMetadata[assetId];
126
- if (existingMetadata?.image) {
127
- continue;
128
- }
129
- // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API
130
- if ((0, evm_rpc_services_1.isStakingContractAssetId)(assetId)) {
131
- continue;
117
+ // Always include native asset IDs from NetworkEnablementController
118
+ for (const nativeAssetId of __classPrivateFieldGet(this, _TokenDataSource_getNativeAssetIds, "f").call(this)) {
119
+ assetIdsNeedingMetadata.add(nativeAssetId);
120
+ }
121
+ // Also fetch metadata for detected assets that are missing it
122
+ if (response.detectedAssets) {
123
+ for (const detectedIds of Object.values(response.detectedAssets)) {
124
+ for (const assetId of detectedIds) {
125
+ // Skip if response already has metadata with image
126
+ const responseMetadata = response.assetsInfo?.[assetId];
127
+ if (responseMetadata?.image) {
128
+ continue;
129
+ }
130
+ // Skip if state already has metadata with image
131
+ const existingMetadata = stateMetadata[assetId];
132
+ if (existingMetadata?.image) {
133
+ continue;
134
+ }
135
+ // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API
136
+ if ((0, evm_rpc_services_1.isStakingContractAssetId)(assetId)) {
137
+ continue;
138
+ }
139
+ assetIdsNeedingMetadata.add(assetId);
132
140
  }
133
- assetIdsNeedingMetadata.add(assetId);
134
141
  }
135
142
  }
136
143
  if (assetIdsNeedingMetadata.size === 0) {
@@ -152,12 +159,35 @@ class TokenDataSource {
152
159
  includeLabels: true,
153
160
  includeRwaData: true,
154
161
  includeAggregators: true,
162
+ includeOccurrences: true,
155
163
  });
156
164
  response.assetsInfo ?? (response.assetsInfo = {});
165
+ const filteredOutAssets = new Set();
157
166
  for (const assetData of metadataResponse) {
167
+ const parsed = (0, utils_1.parseCaipAssetType)(assetData.assetId);
168
+ const isNative = parsed.assetNamespace === 'slip44';
169
+ if (!isNative &&
170
+ (assetData.occurrences ?? 0) < MIN_TOKEN_OCCURRENCES) {
171
+ filteredOutAssets.add(assetData.assetId);
172
+ continue;
173
+ }
158
174
  const caipAssetId = assetData.assetId;
159
175
  response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(assetData.assetId, assetData);
160
176
  }
177
+ if (filteredOutAssets.size > 0) {
178
+ if (response.assetsBalance) {
179
+ for (const accountBalances of Object.values(response.assetsBalance)) {
180
+ for (const assetId of filteredOutAssets) {
181
+ delete accountBalances[assetId];
182
+ }
183
+ }
184
+ }
185
+ if (response.detectedAssets) {
186
+ for (const [accountId, assetIds] of Object.entries(response.detectedAssets)) {
187
+ response.detectedAssets[accountId] = assetIds.filter((id) => !filteredOutAssets.has(id));
188
+ }
189
+ }
190
+ }
161
191
  }
162
192
  catch (error) {
163
193
  log('Failed to fetch metadata', { error });
@@ -168,7 +198,7 @@ class TokenDataSource {
168
198
  }
169
199
  }
170
200
  exports.TokenDataSource = TokenDataSource;
171
- _TokenDataSource_apiClient = new WeakMap(), _TokenDataSource_instances = new WeakSet(), _TokenDataSource_getSupportedNetworks =
201
+ _TokenDataSource_apiClient = new WeakMap(), _TokenDataSource_getNativeAssetIds = new WeakMap(), _TokenDataSource_instances = new WeakSet(), _TokenDataSource_getSupportedNetworks =
172
202
  /**
173
203
  * Gets the supported networks from the API.
174
204
  * Caching is handled by ApiPlatformClient.
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from './evm-rpc-services';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/**\n * TokenDataSource does not call external messenger actions.\n * It uses ApiPlatformClient directly.\n */\nexport type TokenDataSourceAllowedActions = never;\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: string,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (parsed.assetNamespace === 'slip44') {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Usage: Create with queryApiClient and use assetsMiddleware; no messenger required.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n constructor(options: TokenDataSourceOptions) {\n this.#apiClient = options.queryApiClient;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n // Only fetch metadata for detected assets (assets without metadata)\n if (!response.detectedAssets) {\n return next(ctx);\n }\n\n const { assetsInfo: stateMetadata } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n // Use ApiPlatformClient for fetching asset metadata\n // API returns an array with assetId as a property on each item\n const metadataResponse = await this.#apiClient.tokens.fetchV3Assets(\n supportedAssetIds,\n {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n },\n );\n\n response.assetsInfo ??= {};\n\n for (const assetData of metadataResponse) {\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n assetData.assetId,\n assetData,\n );\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"TokenDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,yDAA2D;AAC3D,2CAAqD;AAGrD,mEAA8D;AAC9D,0CAA8D;AAC9D,wCAAwC;AAQxC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAE1C,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,MAAM,GAAG,GAAG,IAAA,2BAAkB,EAAC,sBAAa,EAAE,eAAe,CAAC,CAAC;AAuB/D,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,SAAS,kCAAkC,CACzC,OAAe,EACf,SAA0B;IAE1B,MAAM,MAAM,GAAG,IAAA,0BAAkB,EAAC,OAAwB,CAAC,CAAC;IAC5D,IAAI,SAAS,GAA+B,OAAO,CAAC;IAEpD,IAAI,MAAM,CAAC,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,SAAS,GAAG,QAAQ,CAAC;IACvB,CAAC;SAAM,IAAI,MAAM,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAC3C,SAAS,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,QAAQ,GAA0B;QACtC,4BAA4B;QAC5B,IAAI,EAAE,SAAS;QACf,2BAA2B;QAC3B,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,KAAK,EAAE,SAAS,CAAC,OAAO;QACxB,wBAAwB;QACxB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,kBAAkB,EAAE,SAAS,CAAC,kBAAkB;QAChD,WAAW,EAAE,SAAS,CAAC,WAAW;KACnC,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,MAAa,eAAe;IAG1B,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAQD,YAAY,OAA+B;;QAZlC,SAAI,GAAG,eAAe,CAAC;QAMhC,6CAA6C;QACpC,6CAA8B;QAEvC,8EAA8E;QACrE,qDAAmC;QAG1C,uBAAA,IAAI,8BAAc,OAAO,CAAC,cAAc,MAAA,CAAC;QACzC,uBAAA,IAAI,sCAAsB,OAAO,CAAC,iBAAiB,MAAA,CAAC;IACtD,CAAC;IAkDD;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB;QAClB,OAAO,IAAA,oBAAY,EAAC,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACpD,gCAAgC;YAChC,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;YAEzB,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;YAC3D,MAAM,uBAAuB,GAAG,IAAI,GAAG,EAAU,CAAC;YAElD,mEAAmE;YACnE,KAAK,MAAM,aAAa,IAAI,uBAAA,IAAI,0CAAmB,MAAvB,IAAI,CAAqB,EAAE,CAAC;gBACtD,uBAAuB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC7C,CAAC;YAED,8DAA8D;YAC9D,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;gBAC5B,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;oBACjE,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;wBAClC,mDAAmD;wBACnD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;wBACxD,IAAI,gBAAgB,EAAE,KAAK,EAAE,CAAC;4BAC5B,SAAS;wBACX,CAAC;wBAED,gDAAgD;wBAChD,MAAM,gBAAgB,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;wBAChD,IAAI,gBAAgB,EAAE,KAAK,EAAE,CAAC;4BAC5B,SAAS;wBACX,CAAC;wBAED,wFAAwF;wBACxF,IAAI,IAAA,2CAAwB,EAAC,OAAO,CAAC,EAAE,CAAC;4BACtC,SAAS;wBACX,CAAC;wBAED,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACvC,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,uBAAuB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,sDAAsD;YACtD,MAAM,iBAAiB,GAAG,MAAM,uBAAA,IAAI,yEAAsB,MAA1B,IAAI,CAAwB,CAAC;YAC7D,MAAM,iBAAiB,GAAG,uBAAA,IAAI,0EAAuB,MAA3B,IAAI,EAC5B,CAAC,GAAG,uBAAuB,CAAC,EAC5B,iBAAiB,CAClB,CAAC;YAEF,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,IAAI,CAAC;gBACH,oDAAoD;gBACpD,+DAA+D;gBAC/D,MAAM,gBAAgB,GAAG,MAAM,uBAAA,IAAI,kCAAW,CAAC,MAAM,CAAC,aAAa,CACjE,iBAAiB,EACjB;oBACE,cAAc,EAAE,IAAI;oBACpB,iBAAiB,EAAE,IAAI;oBACvB,eAAe,EAAE,IAAI;oBACrB,aAAa,EAAE,IAAI;oBACnB,cAAc,EAAE,IAAI;oBACpB,kBAAkB,EAAE,IAAI;oBACxB,kBAAkB,EAAE,IAAI;iBACzB,CACF,CAAC;gBAEF,QAAQ,CAAC,UAAU,KAAnB,QAAQ,CAAC,UAAU,GAAK,EAAE,EAAC;gBAE3B,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;gBAE5C,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;oBACzC,MAAM,MAAM,GAAG,IAAA,0BAAkB,EAAC,SAAS,CAAC,OAAwB,CAAC,CAAC;oBACtE,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;oBAEpD,IACE,CAAC,QAAQ;wBACT,CAAC,SAAS,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,qBAAqB,EACpD,CAAC;wBACD,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;wBACzC,SAAS;oBACX,CAAC;oBAED,MAAM,WAAW,GAAG,SAAS,CAAC,OAAwB,CAAC;oBACvD,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,kCAAkC,CACnE,SAAS,CAAC,OAAO,EACjB,SAAS,CACV,CAAC;gBACJ,CAAC;gBAED,IAAI,iBAAiB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;oBAC/B,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;wBAC3B,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,MAAM,CACzC,QAAQ,CAAC,aAAa,CACvB,EAAE,CAAC;4BACF,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;gCACxC,OAAQ,eAA2C,CAAC,OAAO,CAAC,CAAC;4BAC/D,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;wBAC5B,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAChD,QAAQ,CAAC,cAAc,CACxB,EAAE,CAAC;4BACF,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,MAAM,CAClD,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,CACnC,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,GAAG,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7C,CAAC;YAED,0DAA0D;YAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAvMD,0CAuMC;;AArLC;;;;;GAKG;AACH,KAAK;IACH,IAAI,CAAC;QACH,wDAAwD;QACxD,oCAAoC;QACpC,MAAM,QAAQ,GACZ,MAAM,uBAAA,IAAI,kCAAW,CAAC,MAAM,CAAC,6BAA6B,EAAE,CAAC;QAE/D,4CAA4C;QAC5C,MAAM,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;QAE1E,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC,2FAUC,QAAkB,EAClB,iBAA8B;IAE9B,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAA,0BAAkB,EAAC,OAAwB,CAAC,CAAC;YAC5D,sDAAsD;YACtD,sDAAsD;YACtD,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACtE,OAAO,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;YAChD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from './evm-rpc-services';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst MIN_TOKEN_OCCURRENCES = 3;\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/**\n * TokenDataSource does not call external messenger actions.\n * It uses ApiPlatformClient directly.\n */\nexport type TokenDataSourceAllowedActions = never;\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n getNativeAssetIds: () => string[];\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: string,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (parsed.assetNamespace === 'slip44') {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Usage: Create with queryApiClient and use assetsMiddleware; no messenger required.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n readonly #getNativeAssetIds: () => string[];\n\n constructor(options: TokenDataSourceOptions) {\n this.#apiClient = options.queryApiClient;\n this.#getNativeAssetIds = options.getNativeAssetIds;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n const { assetsInfo: stateMetadata } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n // Always include native asset IDs from NetworkEnablementController\n for (const nativeAssetId of this.#getNativeAssetIds()) {\n assetIdsNeedingMetadata.add(nativeAssetId);\n }\n\n // Also fetch metadata for detected assets that are missing it\n if (response.detectedAssets) {\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n // Use ApiPlatformClient for fetching asset metadata\n // API returns an array with assetId as a property on each item\n const metadataResponse = await this.#apiClient.tokens.fetchV3Assets(\n supportedAssetIds,\n {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n includeOccurrences: true,\n },\n );\n\n response.assetsInfo ??= {};\n\n const filteredOutAssets = new Set<string>();\n\n for (const assetData of metadataResponse) {\n const parsed = parseCaipAssetType(assetData.assetId as CaipAssetType);\n const isNative = parsed.assetNamespace === 'slip44';\n\n if (\n !isNative &&\n (assetData.occurrences ?? 0) < MIN_TOKEN_OCCURRENCES\n ) {\n filteredOutAssets.add(assetData.assetId);\n continue;\n }\n\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n assetData.assetId,\n assetData,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(\n response.assetsBalance,\n )) {\n for (const assetId of filteredOutAssets) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !filteredOutAssets.has(id),\n );\n }\n }\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
@@ -8,6 +8,8 @@ export type TokenDataSourceAllowedActions = never;
8
8
  export type TokenDataSourceOptions = {
9
9
  /** ApiPlatformClient for API calls with caching */
10
10
  queryApiClient: ApiPlatformClient;
11
+ /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */
12
+ getNativeAssetIds: () => string[];
11
13
  };
12
14
  /**
13
15
  * TokenDataSource enriches responses with token metadata from the Tokens API.
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,+BAA+B;AAO3D,OAAO,KAAK,EAGV,UAAU,EAEX,qBAAiB;AAclB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAMlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;CACnC,CAAC;AA2DF;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,IAAI,qBAAmB;IAEhC,OAAO,IAAI,MAAM;gBAOL,OAAO,EAAE,sBAAsB;IAoD3C;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAkFjC;CACF"}
1
+ {"version":3,"file":"TokenDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,+BAA+B;AAO3D,OAAO,KAAK,EAGV,UAAU,EAEX,qBAAiB;AAgBlB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAMlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,MAAM,EAAE,CAAC;CACnC,CAAC;AA2DF;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,IAAI,qBAAmB;IAEhC,OAAO,IAAI,MAAM;gBAUL,OAAO,EAAE,sBAAsB;IAqD3C;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAyHjC;CACF"}
@@ -8,6 +8,8 @@ export type TokenDataSourceAllowedActions = never;
8
8
  export type TokenDataSourceOptions = {
9
9
  /** ApiPlatformClient for API calls with caching */
10
10
  queryApiClient: ApiPlatformClient;
11
+ /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */
12
+ getNativeAssetIds: () => string[];
11
13
  };
12
14
  /**
13
15
  * TokenDataSource enriches responses with token metadata from the Tokens API.
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,+BAA+B;AAO3D,OAAO,KAAK,EAGV,UAAU,EAEX,qBAAiB;AAclB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAMlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;CACnC,CAAC;AA2DF;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,IAAI,qBAAmB;IAEhC,OAAO,IAAI,MAAM;gBAOL,OAAO,EAAE,sBAAsB;IAoD3C;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAkFjC;CACF"}
1
+ {"version":3,"file":"TokenDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,+BAA+B;AAO3D,OAAO,KAAK,EAGV,UAAU,EAEX,qBAAiB;AAgBlB;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAMlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,MAAM,EAAE,CAAC;CACnC,CAAC;AA2DF;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,IAAI,qBAAmB;IAEhC,OAAO,IAAI,MAAM;gBAUL,OAAO,EAAE,sBAAsB;IAqD3C;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAyHjC;CACF"}
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _TokenDataSource_instances, _TokenDataSource_apiClient, _TokenDataSource_getSupportedNetworks, _TokenDataSource_filterAssetsByNetwork;
12
+ var _TokenDataSource_instances, _TokenDataSource_apiClient, _TokenDataSource_getNativeAssetIds, _TokenDataSource_getSupportedNetworks, _TokenDataSource_filterAssetsByNetwork;
13
13
  import { ApiPlatformClient } from "@metamask/core-backend";
14
14
  import { parseCaipAssetType } from "@metamask/utils";
15
15
  import { isStakingContractAssetId } from "./evm-rpc-services/index.mjs";
@@ -19,6 +19,7 @@ import { forDataTypes } from "../types.mjs";
19
19
  // CONSTANTS
20
20
  // ============================================================================
21
21
  const CONTROLLER_NAME = 'TokenDataSource';
22
+ const MIN_TOKEN_OCCURRENCES = 3;
22
23
  const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
23
24
  // ============================================================================
24
25
  // HELPER FUNCTIONS
@@ -88,7 +89,10 @@ export class TokenDataSource {
88
89
  this.name = CONTROLLER_NAME;
89
90
  /** ApiPlatformClient for cached API calls */
90
91
  _TokenDataSource_apiClient.set(this, void 0);
92
+ /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */
93
+ _TokenDataSource_getNativeAssetIds.set(this, void 0);
91
94
  __classPrivateFieldSet(this, _TokenDataSource_apiClient, options.queryApiClient, "f");
95
+ __classPrivateFieldSet(this, _TokenDataSource_getNativeAssetIds, options.getNativeAssetIds, "f");
92
96
  }
93
97
  /**
94
98
  * Get the middleware for enriching responses with token metadata.
@@ -105,29 +109,32 @@ export class TokenDataSource {
105
109
  return forDataTypes(['metadata'], async (ctx, next) => {
106
110
  // Extract response from context
107
111
  const { response } = ctx;
108
- // Only fetch metadata for detected assets (assets without metadata)
109
- if (!response.detectedAssets) {
110
- return next(ctx);
111
- }
112
112
  const { assetsInfo: stateMetadata } = ctx.getAssetsState();
113
113
  const assetIdsNeedingMetadata = new Set();
114
- for (const detectedIds of Object.values(response.detectedAssets)) {
115
- for (const assetId of detectedIds) {
116
- // Skip if response already has metadata with image
117
- const responseMetadata = response.assetsInfo?.[assetId];
118
- if (responseMetadata?.image) {
119
- continue;
120
- }
121
- // Skip if state already has metadata with image
122
- const existingMetadata = stateMetadata[assetId];
123
- if (existingMetadata?.image) {
124
- continue;
125
- }
126
- // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API
127
- if (isStakingContractAssetId(assetId)) {
128
- continue;
114
+ // Always include native asset IDs from NetworkEnablementController
115
+ for (const nativeAssetId of __classPrivateFieldGet(this, _TokenDataSource_getNativeAssetIds, "f").call(this)) {
116
+ assetIdsNeedingMetadata.add(nativeAssetId);
117
+ }
118
+ // Also fetch metadata for detected assets that are missing it
119
+ if (response.detectedAssets) {
120
+ for (const detectedIds of Object.values(response.detectedAssets)) {
121
+ for (const assetId of detectedIds) {
122
+ // Skip if response already has metadata with image
123
+ const responseMetadata = response.assetsInfo?.[assetId];
124
+ if (responseMetadata?.image) {
125
+ continue;
126
+ }
127
+ // Skip if state already has metadata with image
128
+ const existingMetadata = stateMetadata[assetId];
129
+ if (existingMetadata?.image) {
130
+ continue;
131
+ }
132
+ // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API
133
+ if (isStakingContractAssetId(assetId)) {
134
+ continue;
135
+ }
136
+ assetIdsNeedingMetadata.add(assetId);
129
137
  }
130
- assetIdsNeedingMetadata.add(assetId);
131
138
  }
132
139
  }
133
140
  if (assetIdsNeedingMetadata.size === 0) {
@@ -149,12 +156,35 @@ export class TokenDataSource {
149
156
  includeLabels: true,
150
157
  includeRwaData: true,
151
158
  includeAggregators: true,
159
+ includeOccurrences: true,
152
160
  });
153
161
  response.assetsInfo ?? (response.assetsInfo = {});
162
+ const filteredOutAssets = new Set();
154
163
  for (const assetData of metadataResponse) {
164
+ const parsed = parseCaipAssetType(assetData.assetId);
165
+ const isNative = parsed.assetNamespace === 'slip44';
166
+ if (!isNative &&
167
+ (assetData.occurrences ?? 0) < MIN_TOKEN_OCCURRENCES) {
168
+ filteredOutAssets.add(assetData.assetId);
169
+ continue;
170
+ }
155
171
  const caipAssetId = assetData.assetId;
156
172
  response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(assetData.assetId, assetData);
157
173
  }
174
+ if (filteredOutAssets.size > 0) {
175
+ if (response.assetsBalance) {
176
+ for (const accountBalances of Object.values(response.assetsBalance)) {
177
+ for (const assetId of filteredOutAssets) {
178
+ delete accountBalances[assetId];
179
+ }
180
+ }
181
+ }
182
+ if (response.detectedAssets) {
183
+ for (const [accountId, assetIds] of Object.entries(response.detectedAssets)) {
184
+ response.detectedAssets[accountId] = assetIds.filter((id) => !filteredOutAssets.has(id));
185
+ }
186
+ }
187
+ }
158
188
  }
159
189
  catch (error) {
160
190
  log('Failed to fetch metadata', { error });
@@ -164,7 +194,7 @@ export class TokenDataSource {
164
194
  });
165
195
  }
166
196
  }
167
- _TokenDataSource_apiClient = new WeakMap(), _TokenDataSource_instances = new WeakSet(), _TokenDataSource_getSupportedNetworks =
197
+ _TokenDataSource_apiClient = new WeakMap(), _TokenDataSource_getNativeAssetIds = new WeakMap(), _TokenDataSource_instances = new WeakSet(), _TokenDataSource_getSupportedNetworks =
168
198
  /**
169
199
  * Gets the supported networks from the API.
170
200
  * Caching is handled by ApiPlatformClient.
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,iBAAiB,EAAE,+BAA+B;AAC3D,OAAO,EAAE,kBAAkB,EAAE,wBAAwB;AAGrD,OAAO,EAAE,wBAAwB,EAAE,qCAA2B;AAC9D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,sBAAkB;AAC9D,OAAO,EAAE,YAAY,EAAE,qBAAiB;AAQxC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAE1C,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;AAqB/D,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,SAAS,kCAAkC,CACzC,OAAe,EACf,SAA0B;IAE1B,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAwB,CAAC,CAAC;IAC5D,IAAI,SAAS,GAA+B,OAAO,CAAC;IAEpD,IAAI,MAAM,CAAC,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,SAAS,GAAG,QAAQ,CAAC;IACvB,CAAC;SAAM,IAAI,MAAM,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAC3C,SAAS,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,QAAQ,GAA0B;QACtC,4BAA4B;QAC5B,IAAI,EAAE,SAAS;QACf,2BAA2B;QAC3B,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,KAAK,EAAE,SAAS,CAAC,OAAO;QACxB,wBAAwB;QACxB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,cAAc,EAAE,SAAS,CAAC,cAAc;QACxC,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,kBAAkB,EAAE,SAAS,CAAC,kBAAkB;QAChD,WAAW,EAAE,SAAS,CAAC,WAAW;KACnC,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,MAAM,OAAO,eAAe;IAG1B,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAKD,YAAY,OAA+B;;QATlC,SAAI,GAAG,eAAe,CAAC;QAMhC,6CAA6C;QACpC,6CAA8B;QAGrC,uBAAA,IAAI,8BAAc,OAAO,CAAC,cAAc,MAAA,CAAC;IAC3C,CAAC;IAkDD;;;;;;;;;;OAUG;IACH,IAAI,gBAAgB;QAClB,OAAO,YAAY,CAAC,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACpD,gCAAgC;YAChC,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;YAEzB,oEAAoE;YACpE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;YAC3D,MAAM,uBAAuB,GAAG,IAAI,GAAG,EAAU,CAAC;YAElD,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjE,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;oBAClC,mDAAmD;oBACnD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;oBACxD,IAAI,gBAAgB,EAAE,KAAK,EAAE,CAAC;wBAC5B,SAAS;oBACX,CAAC;oBAED,gDAAgD;oBAChD,MAAM,gBAAgB,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;oBAChD,IAAI,gBAAgB,EAAE,KAAK,EAAE,CAAC;wBAC5B,SAAS;oBACX,CAAC;oBAED,wFAAwF;oBACxF,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;wBACtC,SAAS;oBACX,CAAC;oBAED,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;YAED,IAAI,uBAAuB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,sDAAsD;YACtD,MAAM,iBAAiB,GAAG,MAAM,uBAAA,IAAI,yEAAsB,MAA1B,IAAI,CAAwB,CAAC;YAC7D,MAAM,iBAAiB,GAAG,uBAAA,IAAI,0EAAuB,MAA3B,IAAI,EAC5B,CAAC,GAAG,uBAAuB,CAAC,EAC5B,iBAAiB,CAClB,CAAC;YAEF,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,IAAI,CAAC;gBACH,oDAAoD;gBACpD,+DAA+D;gBAC/D,MAAM,gBAAgB,GAAG,MAAM,uBAAA,IAAI,kCAAW,CAAC,MAAM,CAAC,aAAa,CACjE,iBAAiB,EACjB;oBACE,cAAc,EAAE,IAAI;oBACpB,iBAAiB,EAAE,IAAI;oBACvB,eAAe,EAAE,IAAI;oBACrB,aAAa,EAAE,IAAI;oBACnB,cAAc,EAAE,IAAI;oBACpB,kBAAkB,EAAE,IAAI;iBACzB,CACF,CAAC;gBAEF,QAAQ,CAAC,UAAU,KAAnB,QAAQ,CAAC,UAAU,GAAK,EAAE,EAAC;gBAE3B,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;oBACzC,MAAM,WAAW,GAAG,SAAS,CAAC,OAAwB,CAAC;oBACvD,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,kCAAkC,CACnE,SAAS,CAAC,OAAO,EACjB,SAAS,CACV,CAAC;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,GAAG,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7C,CAAC;YAED,0DAA0D;YAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;;AA9IC;;;;;GAKG;AACH,KAAK;IACH,IAAI,CAAC;QACH,wDAAwD;QACxD,oCAAoC;QACpC,MAAM,QAAQ,GACZ,MAAM,uBAAA,IAAI,kCAAW,CAAC,MAAM,CAAC,6BAA6B,EAAE,CAAC;QAE/D,4CAA4C;QAC5C,MAAM,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;QAE1E,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC,2FAUC,QAAkB,EAClB,iBAA8B;IAE9B,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAwB,CAAC,CAAC;YAC5D,sDAAsD;YACtD,sDAAsD;YACtD,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACtE,OAAO,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;YAChD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from './evm-rpc-services';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/**\n * TokenDataSource does not call external messenger actions.\n * It uses ApiPlatformClient directly.\n */\nexport type TokenDataSourceAllowedActions = never;\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: string,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (parsed.assetNamespace === 'slip44') {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Usage: Create with queryApiClient and use assetsMiddleware; no messenger required.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n constructor(options: TokenDataSourceOptions) {\n this.#apiClient = options.queryApiClient;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n // Only fetch metadata for detected assets (assets without metadata)\n if (!response.detectedAssets) {\n return next(ctx);\n }\n\n const { assetsInfo: stateMetadata } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n // Use ApiPlatformClient for fetching asset metadata\n // API returns an array with assetId as a property on each item\n const metadataResponse = await this.#apiClient.tokens.fetchV3Assets(\n supportedAssetIds,\n {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n },\n );\n\n response.assetsInfo ??= {};\n\n for (const assetData of metadataResponse) {\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n assetData.assetId,\n assetData,\n );\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"TokenDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from './evm-rpc-services';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst MIN_TOKEN_OCCURRENCES = 3;\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/**\n * TokenDataSource does not call external messenger actions.\n * It uses ApiPlatformClient directly.\n */\nexport type TokenDataSourceAllowedActions = never;\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n getNativeAssetIds: () => string[];\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: string,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (parsed.assetNamespace === 'slip44') {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Usage: Create with queryApiClient and use assetsMiddleware; no messenger required.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n readonly #getNativeAssetIds: () => string[];\n\n constructor(options: TokenDataSourceOptions) {\n this.#apiClient = options.queryApiClient;\n this.#getNativeAssetIds = options.getNativeAssetIds;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n const { assetsInfo: stateMetadata } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n // Always include native asset IDs from NetworkEnablementController\n for (const nativeAssetId of this.#getNativeAssetIds()) {\n assetIdsNeedingMetadata.add(nativeAssetId);\n }\n\n // Also fetch metadata for detected assets that are missing it\n if (response.detectedAssets) {\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n // Use ApiPlatformClient for fetching asset metadata\n // API returns an array with assetId as a property on each item\n const metadataResponse = await this.#apiClient.tokens.fetchV3Assets(\n supportedAssetIds,\n {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n includeOccurrences: true,\n },\n );\n\n response.assetsInfo ??= {};\n\n const filteredOutAssets = new Set<string>();\n\n for (const assetData of metadataResponse) {\n const parsed = parseCaipAssetType(assetData.assetId as CaipAssetType);\n const isNative = parsed.assetNamespace === 'slip44';\n\n if (\n !isNative &&\n (assetData.occurrences ?? 0) < MIN_TOKEN_OCCURRENCES\n ) {\n filteredOutAssets.add(assetData.assetId);\n continue;\n }\n\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n assetData.assetId,\n assetData,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(\n response.assetsBalance,\n )) {\n for (const assetId of filteredOutAssets) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !filteredOutAssets.has(id),\n );\n }\n }\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
@@ -82,8 +82,6 @@ class BalanceFetcher extends (0, polling_controller_1.StaticIntervalPollingContr
82
82
  if (!tokenMap.has(lowerAddress)) {
83
83
  tokenMap.set(lowerAddress, {
84
84
  address: tokenAddress,
85
- // Decimals will be fetched from metadata or defaulted
86
- decimals: 18,
87
85
  symbol: '',
88
86
  });
89
87
  }
@@ -160,23 +158,39 @@ _BalanceFetcher_multicallClient = new WeakMap(), _BalanceFetcher_messenger = new
160
158
  continue;
161
159
  }
162
160
  const balance = response.balance ?? '0';
163
- const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());
164
- const decimals = tokenInfo?.decimals ?? 18;
165
- const formattedBalance = __classPrivateFieldGet(this, _BalanceFetcher_instances, "m", _BalanceFetcher_formatBalance).call(this, balance, decimals);
166
161
  const chainIdDecimal = parseInt(chainId, 16);
167
162
  const isNative = response.tokenAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase();
163
+ const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());
164
+ // Native uses 18. ERC-20: format when decimals are known; otherwise pass raw
165
+ // `balance` through so RpcDataSource can resolve decimals (state → list → RPC).
166
+ let decimals;
167
+ let formattedBalance;
168
+ if (isNative) {
169
+ decimals = 18;
170
+ formattedBalance = __classPrivateFieldGet(this, _BalanceFetcher_instances, "m", _BalanceFetcher_formatBalance).call(this, balance, decimals);
171
+ }
172
+ else if (tokenInfo?.decimals === undefined) {
173
+ formattedBalance = balance;
174
+ }
175
+ else {
176
+ decimals = tokenInfo.decimals;
177
+ formattedBalance = __classPrivateFieldGet(this, _BalanceFetcher_instances, "m", _BalanceFetcher_formatBalance).call(this, balance, decimals);
178
+ }
168
179
  const assetId = isNative
169
180
  ? `eip155:${chainIdDecimal}/slip44:60`
170
181
  : `eip155:${chainIdDecimal}/erc20:${response.tokenAddress.toLowerCase()}`;
171
- balances.push({
182
+ const balanceEntry = {
172
183
  assetId,
173
184
  accountId,
174
185
  chainId,
175
186
  balance,
176
187
  formattedBalance,
177
- decimals,
178
188
  timestamp,
179
- });
189
+ };
190
+ if (typeof decimals === 'number') {
191
+ balanceEntry.decimals = decimals;
192
+ }
193
+ balances.push(balanceEntry);
180
194
  }
181
195
  return { balances, failedAddresses };
182
196
  }, _BalanceFetcher_formatBalance = function _BalanceFetcher_formatBalance(rawBalance, decimals) {
@@ -1 +1 @@
1
- {"version":3,"file":"BalanceFetcher.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetsBalanceState,\n BalanceFetchOptions,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenFetchInfo,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\nconst ZERO_ADDRESS: Address =\n '0x0000000000000000000000000000000000000000' as Address;\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n includeNativeByDefault?: boolean;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n includeNativeByDefault: config?.includeNativeByDefault ?? true,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n getTokensToFetch(chainId: ChainId, accountId: AccountId): TokenFetchInfo[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n if (!state?.assetsBalance) {\n return [];\n }\n\n const accountBalances = state.assetsBalance[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n const chainIdDecimal = parseInt(chainId, 16);\n const caipChainPrefix = `eip155:${chainIdDecimal}/`;\n\n const tokenMap = new Map<string, TokenFetchInfo>();\n\n for (const assetId of Object.keys(accountBalances)) {\n // Only process ERC20 tokens on the current chain\n if (assetId.startsWith(caipChainPrefix) && assetId.includes('/erc20:')) {\n // Parse token address from CAIP-19: eip155:1/erc20:0x...\n const tokenAddress = assetId.split('/erc20:')[1] as Address;\n if (tokenAddress) {\n const lowerAddress = tokenAddress.toLowerCase();\n if (!tokenMap.has(lowerAddress)) {\n tokenMap.set(lowerAddress, {\n address: tokenAddress,\n // Decimals will be fetched from metadata or defaulted\n decimals: 18,\n symbol: '',\n });\n }\n }\n }\n }\n\n return Array.from(tokenMap.values());\n }\n\n async fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: BalanceFetchOptions,\n ): Promise<BalanceFetchResult> {\n const tokens = this.getTokensToFetch(chainId, accountId);\n const tokenAddresses = tokens.map((token) => token.address);\n\n return this.fetchBalancesForTokens(\n chainId,\n accountId,\n accountAddress,\n tokenAddresses,\n options,\n tokens,\n );\n }\n\n async fetchBalancesForTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n tokenAddresses: Address[],\n options?: BalanceFetchOptions,\n tokenInfos?: TokenFetchInfo[],\n ): Promise<BalanceFetchResult> {\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const includeNative =\n options?.includeNative ?? this.#config.includeNativeByDefault;\n const timestamp = Date.now();\n\n const tokenInfoMap = new Map<string, TokenFetchInfo>();\n if (tokenInfos) {\n for (const info of tokenInfos) {\n tokenInfoMap.set(info.address.toLowerCase(), info);\n }\n }\n\n const balanceRequests: BalanceOfRequest[] = [];\n\n if (includeNative) {\n balanceRequests.push({\n tokenAddress: ZERO_ADDRESS,\n accountAddress,\n });\n }\n\n for (const tokenAddress of tokenAddresses) {\n balanceRequests.push({\n tokenAddress,\n accountAddress,\n });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n tokenInfoMap,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n tokenInfoMap: Map<string, TokenFetchInfo>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const balance = response.balance ?? '0';\n const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());\n const decimals = tokenInfo?.decimals ?? 18;\n const formattedBalance = this.#formatBalance(balance, decimals);\n\n const chainIdDecimal = parseInt(chainId, 16);\n const isNative =\n response.tokenAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase();\n const assetId: CaipAssetType = isNative\n ? (`eip155:${chainIdDecimal}/slip44:60` as CaipAssetType)\n : (`eip155:${chainIdDecimal}/erc20:${response.tokenAddress.toLowerCase()}` as CaipAssetType);\n\n balances.push({\n assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"BalanceFetcher.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetsBalanceState,\n BalanceFetchOptions,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenFetchInfo,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\nconst ZERO_ADDRESS: Address =\n '0x0000000000000000000000000000000000000000' as Address;\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n includeNativeByDefault?: boolean;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n includeNativeByDefault: config?.includeNativeByDefault ?? true,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n getTokensToFetch(chainId: ChainId, accountId: AccountId): TokenFetchInfo[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n if (!state?.assetsBalance) {\n return [];\n }\n\n const accountBalances = state.assetsBalance[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n const chainIdDecimal = parseInt(chainId, 16);\n const caipChainPrefix = `eip155:${chainIdDecimal}/`;\n\n const tokenMap = new Map<string, TokenFetchInfo>();\n\n for (const assetId of Object.keys(accountBalances)) {\n // Only process ERC20 tokens on the current chain\n if (assetId.startsWith(caipChainPrefix) && assetId.includes('/erc20:')) {\n // Parse token address from CAIP-19: eip155:1/erc20:0x...\n const tokenAddress = assetId.split('/erc20:')[1] as Address;\n if (tokenAddress) {\n const lowerAddress = tokenAddress.toLowerCase();\n if (!tokenMap.has(lowerAddress)) {\n tokenMap.set(lowerAddress, {\n address: tokenAddress,\n symbol: '',\n });\n }\n }\n }\n }\n\n return Array.from(tokenMap.values());\n }\n\n async fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: BalanceFetchOptions,\n ): Promise<BalanceFetchResult> {\n const tokens = this.getTokensToFetch(chainId, accountId);\n const tokenAddresses = tokens.map((token) => token.address);\n\n return this.fetchBalancesForTokens(\n chainId,\n accountId,\n accountAddress,\n tokenAddresses,\n options,\n tokens,\n );\n }\n\n async fetchBalancesForTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n tokenAddresses: Address[],\n options?: BalanceFetchOptions,\n tokenInfos?: TokenFetchInfo[],\n ): Promise<BalanceFetchResult> {\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const includeNative =\n options?.includeNative ?? this.#config.includeNativeByDefault;\n const timestamp = Date.now();\n\n const tokenInfoMap = new Map<string, TokenFetchInfo>();\n if (tokenInfos) {\n for (const info of tokenInfos) {\n tokenInfoMap.set(info.address.toLowerCase(), info);\n }\n }\n\n const balanceRequests: BalanceOfRequest[] = [];\n\n if (includeNative) {\n balanceRequests.push({\n tokenAddress: ZERO_ADDRESS,\n accountAddress,\n });\n }\n\n for (const tokenAddress of tokenAddresses) {\n balanceRequests.push({\n tokenAddress,\n accountAddress,\n });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n tokenInfoMap,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n tokenInfoMap: Map<string, TokenFetchInfo>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const balance = response.balance ?? '0';\n const chainIdDecimal = parseInt(chainId, 16);\n const isNative =\n response.tokenAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase();\n const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());\n\n // Native uses 18. ERC-20: format when decimals are known; otherwise pass raw\n // `balance` through so RpcDataSource can resolve decimals (state → list → RPC).\n let decimals: number | undefined;\n let formattedBalance: string;\n if (isNative) {\n decimals = 18;\n formattedBalance = this.#formatBalance(balance, decimals);\n } else if (tokenInfo?.decimals === undefined) {\n formattedBalance = balance;\n } else {\n decimals = tokenInfo.decimals;\n formattedBalance = this.#formatBalance(balance, decimals);\n }\n const assetId: CaipAssetType = isNative\n ? (`eip155:${chainIdDecimal}/slip44:60` as CaipAssetType)\n : (`eip155:${chainIdDecimal}/erc20:${response.tokenAddress.toLowerCase()}` as CaipAssetType);\n\n const balanceEntry: AssetBalance = {\n assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n timestamp,\n };\n if (typeof decimals === 'number') {\n balanceEntry.decimals = decimals;\n }\n balances.push(balanceEntry);\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"BalanceFetcher.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAGlB,OAAO,EACP,cAAc,EACf,2BAAiB;AAQlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;2DAjB1B;;OAEG;;;;;AAiBH;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAe/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,cAAc,EAAE;IAwCpE,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAcxB,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CAwJ/B"}
1
+ {"version":3,"file":"BalanceFetcher.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAGlB,OAAO,EACP,cAAc,EACf,2BAAiB;AAQlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;2DAjB1B;;OAEG;;;;;AAiBH;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAe/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,cAAc,EAAE;IAsCpE,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAcxB,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CAsK/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAGlB,OAAO,EACP,cAAc,EACf,2BAAiB;AAQlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;2DAjB1B;;OAEG;;;;;AAiBH;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAe/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,cAAc,EAAE;IAwCpE,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAcxB,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CAwJ/B"}
1
+ {"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAGlB,OAAO,EACP,cAAc,EACf,2BAAiB;AAQlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;2DAjB1B;;OAEG;;;;;AAiBH;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAe/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,cAAc,EAAE;IAsCpE,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAcxB,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CAsK/B"}