@metamask-previews/assets-controller 18.0.1-preview-44b7ded21 → 18.0.1-preview-cbae429b5

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 (25) hide show
  1. package/CHANGELOG.md +0 -6
  2. package/dist/AssetsController.d.ts.map +1 -1
  3. package/dist/AssetsController.js +5 -1
  4. package/dist/AssetsController.js.map +1 -1
  5. package/dist/data-sources/RpcDataSource.d.ts +12 -3
  6. package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
  7. package/dist/data-sources/RpcDataSource.js +9 -2
  8. package/dist/data-sources/RpcDataSource.js.map +1 -1
  9. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.ts +61 -0
  10. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.ts.map +1 -0
  11. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.js +297 -0
  12. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.js.map +1 -0
  13. package/dist/data-sources/evm-rpc-services/clients/index.d.ts +1 -0
  14. package/dist/data-sources/evm-rpc-services/clients/index.d.ts.map +1 -1
  15. package/dist/data-sources/evm-rpc-services/clients/index.js +1 -0
  16. package/dist/data-sources/evm-rpc-services/clients/index.js.map +1 -1
  17. package/dist/data-sources/evm-rpc-services/index.d.ts +1 -1
  18. package/dist/data-sources/evm-rpc-services/index.d.ts.map +1 -1
  19. package/dist/data-sources/evm-rpc-services/index.js +1 -1
  20. package/dist/data-sources/evm-rpc-services/index.js.map +1 -1
  21. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.ts +3 -15
  22. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.ts.map +1 -1
  23. package/dist/data-sources/evm-rpc-services/services/TokenDetector.js +20 -153
  24. package/dist/data-sources/evm-rpc-services/services/TokenDetector.js.map +1 -1
  25. package/package.json +7 -7
@@ -0,0 +1,297 @@
1
+ import { ChainId as ControllerChainId, convertHexToDecimal, } from '@metamask/controller-utils';
2
+ /**
3
+ * Same host + path that `TokenListController` (assets-controllers) uses
4
+ * (`token-service.ts` → `getTokensURL`). Sharing this endpoint keeps the
5
+ * RPC token detector aligned with the rest of the wallet's token listing
6
+ * (occurrence floors, aggregator filters, icon URLs, etc.).
7
+ */
8
+ const TOKEN_END_POINT_API = 'https://token.api.cx.metamask.io';
9
+ /**
10
+ * Endpoint that returns which CAIP chain IDs the Tokens API supports.
11
+ * Used to skip detection (and avoid unnecessary network requests) for chains
12
+ * that are not in the supported-networks list.
13
+ */
14
+ const SUPPORTED_NETWORKS_URL = `${TOKEN_END_POINT_API}/v2/supportedNetworks`;
15
+ /**
16
+ * Per-chain suggested occurrence floors used as the `occurrenceFloor` query
17
+ * param when fetching token lists (and aligned with TokenDataSource spam
18
+ * filtering). Keys are decimal chain IDs.
19
+ */
20
+ const SUGGESTED_OCCURRENCE_FLOORS_URL = `${TOKEN_END_POINT_API}/v1/suggestedOccurrenceFloors`;
21
+ /**
22
+ * Fallback `occurrenceFloor` when `/v1/suggestedOccurrenceFloors` has no entry
23
+ * for the chain, or the floors request fails.
24
+ */
25
+ const DEFAULT_OCCURRENCE_FLOOR = 3;
26
+ /**
27
+ * How long to keep the supported-networks response in the instance-level
28
+ * cache before refreshing it. One hour is sufficient — the list rarely
29
+ * changes and a stale cache just means we may do an unnecessary token-list
30
+ * fetch for a newly-unsupported chain (harmless).
31
+ */
32
+ const SUPPORTED_NETWORKS_CACHE_TTL_MS = 60 * 60_000;
33
+ /**
34
+ * How long to keep suggested occurrence floors cached. Same TTL as supported
35
+ * networks — floors change infrequently.
36
+ */
37
+ const SUGGESTED_OCCURRENCE_FLOORS_CACHE_TTL_MS = 60 * 60_000;
38
+ /**
39
+ * TanStack-Query cache config for the cached `fetchTokenList` path.
40
+ *
41
+ * The Tokens API per-chain list barely changes between polls, so we keep
42
+ * results fresh for a few minutes (`staleTime`) and retain them in cache for
43
+ * an hour (`gcTime`) so re-detections across accounts/chains hit the cache.
44
+ * These tunings only apply when a `queryClient` is provided to the client;
45
+ * the uncached fallback path (used in standalone tests) is unaffected.
46
+ */
47
+ const TOKEN_LIST_STALE_TIME_MS = 5 * 60_000;
48
+ const TOKEN_LIST_GC_TIME_MS = 60 * 60_000;
49
+ /**
50
+ * Client for the MetaMask Tokens API.
51
+ *
52
+ * Fetches the per-chain ERC-20 token list from the same endpoint that
53
+ * `TokenListController` uses (`token.api.cx.metamask.io/tokens/{chainId}`),
54
+ * with the same query parameters. The `occurrenceFloor` query param comes from
55
+ * Token API `/v1/suggestedOccurrenceFloors` (fallback {@link DEFAULT_OCCURRENCE_FLOOR}),
56
+ * matching `TokenDataSource` spam filtering. Linea's post-response aggregator
57
+ * filter is still applied client-side.
58
+ *
59
+ * Before fetching a chain's token list, the client checks
60
+ * `/v2/supportedNetworks` and returns `[]` immediately for chains that are
61
+ * not in the supported list, avoiding unnecessary API calls. The supported-
62
+ * networks response is cached in-memory for one hour.
63
+ *
64
+ * When constructed with a `queryClient`, token-list results are cached and
65
+ * deduped via TanStack Query (5 min staleTime, 1 h gcTime), so concurrent
66
+ * detection cycles for the same chain coalesce into a single network request.
67
+ */
68
+ export class TokensApiClient {
69
+ #fetch;
70
+ #queryClient;
71
+ /** CAIP chain IDs returned by `/v2/supportedNetworks` (both tiers). */
72
+ #supportedChainIds;
73
+ /** Timestamp of the last successful `/v2/supportedNetworks` fetch. */
74
+ #supportedChainIdsCachedAt = 0;
75
+ /**
76
+ * In-flight `/v2/supportedNetworks` request shared across concurrent callers
77
+ * so only one network request is made even when multiple `fetchTokenList`
78
+ * calls arrive before the first one resolves.
79
+ */
80
+ #supportedChainIdsRefreshPromise;
81
+ /** Decimal chain ID → suggested occurrence floor from Token API. */
82
+ #suggestedOccurrenceFloors;
83
+ /** Timestamp of the last successful floors fetch. */
84
+ #suggestedOccurrenceFloorsCachedAt = 0;
85
+ /** In-flight floors request shared across concurrent callers. */
86
+ #suggestedOccurrenceFloorsRefreshPromise;
87
+ constructor(config) {
88
+ this.#fetch = config?.fetch ?? globalThis.fetch.bind(globalThis);
89
+ this.#queryClient = config?.queryClient;
90
+ }
91
+ /**
92
+ * Fetch the list of ERC-20 tokens for a chain from the Tokens API.
93
+ *
94
+ * Returns `[]` immediately (without hitting the token-list endpoint) when
95
+ * the chain is not in the `/v2/supportedNetworks` response, or when the
96
+ * supported-networks check fails for any reason.
97
+ *
98
+ * @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
99
+ * @returns Array of token list entries with address and metadata, or an empty
100
+ * array if the chain is unsupported or the request fails.
101
+ */
102
+ async fetchTokenList(hexChainId) {
103
+ // Treat any error from the supported-networks check as "not supported":
104
+ // the token-list endpoint is only contacted for known-supported chains.
105
+ if (!(await this.#isSupportedChain(hexChainId).catch(() => false))) {
106
+ return [];
107
+ }
108
+ const queryClient = this.#queryClient;
109
+ if (queryClient === undefined) {
110
+ return this.#fetchTokenListUncached(hexChainId);
111
+ }
112
+ return queryClient.fetchQuery({
113
+ // Namespacing keeps this key from colliding with other clients that
114
+ // share the same QueryClient (e.g. core-backend's ApiPlatformClient).
115
+ queryKey: [
116
+ 'assets-controller',
117
+ 'rpc-detection',
118
+ 'token-list',
119
+ { chainId: hexChainId },
120
+ ],
121
+ queryFn: () => this.#fetchTokenListUncached(hexChainId),
122
+ staleTime: TOKEN_LIST_STALE_TIME_MS,
123
+ gcTime: TOKEN_LIST_GC_TIME_MS,
124
+ });
125
+ }
126
+ /**
127
+ * Check whether the given chain is present in the supported-networks list.
128
+ *
129
+ * Uses an instance-level cache (1 h TTL). Throws if the network request
130
+ * fails — callers should handle this (e.g. via `.catch(() => false)`).
131
+ *
132
+ * @param hexChainId - Hex chain ID to check.
133
+ * @returns `true` when the chain is in the supported-networks list.
134
+ */
135
+ async #isSupportedChain(hexChainId) {
136
+ const now = Date.now();
137
+ if (this.#supportedChainIds === undefined ||
138
+ now - this.#supportedChainIdsCachedAt >= SUPPORTED_NETWORKS_CACHE_TTL_MS) {
139
+ await this.#refreshSupportedChainIds(now);
140
+ }
141
+ const caipChainId = `eip155:${convertHexToDecimal(hexChainId)}`;
142
+ return this.#supportedChainIds?.has(caipChainId) ?? false;
143
+ }
144
+ /**
145
+ * Fetch `/v2/supportedNetworks` and update the instance cache.
146
+ * Concurrent callers share the same in-flight Promise so only one network
147
+ * request is made. If the response is not 2xx the cache is left unchanged
148
+ * (the chain will be treated as unsupported this cycle).
149
+ *
150
+ * @param now - Current timestamp used to stamp the cache entry.
151
+ * @returns A promise that resolves once the cache has been refreshed.
152
+ */
153
+ async #refreshSupportedChainIds(now) {
154
+ if (this.#supportedChainIdsRefreshPromise !== undefined) {
155
+ return this.#supportedChainIdsRefreshPromise;
156
+ }
157
+ this.#supportedChainIdsRefreshPromise = (async () => {
158
+ try {
159
+ const response = await this.#fetch(SUPPORTED_NETWORKS_URL);
160
+ if (response.ok) {
161
+ const data = (await response.json());
162
+ this.#supportedChainIds = new Set([
163
+ ...(data.fullSupport ?? []),
164
+ ...(data.partialSupport ?? []),
165
+ ]);
166
+ this.#supportedChainIdsCachedAt = now;
167
+ }
168
+ }
169
+ finally {
170
+ this.#supportedChainIdsRefreshPromise = undefined;
171
+ }
172
+ })();
173
+ return this.#supportedChainIdsRefreshPromise;
174
+ }
175
+ /**
176
+ * Resolve the `occurrenceFloor` query param for a chain from Token API
177
+ * `/v1/suggestedOccurrenceFloors`. Falls back to
178
+ * {@link DEFAULT_OCCURRENCE_FLOOR} when the chain is missing or the request
179
+ * fails.
180
+ *
181
+ * @param hexChainId - Hex chain ID.
182
+ * @returns Occurrence floor to send to `/tokens/{chainId}`.
183
+ */
184
+ async #getOccurrenceFloor(hexChainId) {
185
+ const now = Date.now();
186
+ if (this.#suggestedOccurrenceFloors === undefined ||
187
+ now - this.#suggestedOccurrenceFloorsCachedAt >=
188
+ SUGGESTED_OCCURRENCE_FLOORS_CACHE_TTL_MS) {
189
+ await this.#refreshSuggestedOccurrenceFloors(now);
190
+ }
191
+ const decimalChainId = String(convertHexToDecimal(hexChainId));
192
+ return (this.#suggestedOccurrenceFloors?.[decimalChainId] ??
193
+ DEFAULT_OCCURRENCE_FLOOR);
194
+ }
195
+ /**
196
+ * Fetch `/v1/suggestedOccurrenceFloors` and update the instance cache.
197
+ * Concurrent callers share the in-flight Promise. Failures leave the cache
198
+ * empty so {@link #getOccurrenceFloor} falls back to the default.
199
+ *
200
+ * @param now - Current timestamp used to stamp the cache entry.
201
+ * @returns A promise that resolves once the cache has been refreshed.
202
+ */
203
+ async #refreshSuggestedOccurrenceFloors(now) {
204
+ if (this.#suggestedOccurrenceFloorsRefreshPromise !== undefined) {
205
+ return this.#suggestedOccurrenceFloorsRefreshPromise;
206
+ }
207
+ this.#suggestedOccurrenceFloorsRefreshPromise =
208
+ (async () => {
209
+ try {
210
+ const response = await this.#fetch(SUGGESTED_OCCURRENCE_FLOORS_URL);
211
+ if (response.ok) {
212
+ const data = (await response.json());
213
+ this.#suggestedOccurrenceFloors =
214
+ data && typeof data === 'object' && !Array.isArray(data)
215
+ ? data
216
+ : {};
217
+ this.#suggestedOccurrenceFloorsCachedAt = now;
218
+ }
219
+ else {
220
+ this.#suggestedOccurrenceFloors = {};
221
+ this.#suggestedOccurrenceFloorsCachedAt = now;
222
+ }
223
+ }
224
+ catch {
225
+ this.#suggestedOccurrenceFloors = {};
226
+ this.#suggestedOccurrenceFloorsCachedAt = now;
227
+ }
228
+ finally {
229
+ this.#suggestedOccurrenceFloorsRefreshPromise = undefined;
230
+ }
231
+ })();
232
+ return this.#suggestedOccurrenceFloorsRefreshPromise;
233
+ }
234
+ async #fetchTokenListUncached(hexChainId) {
235
+ const decimalChainId = convertHexToDecimal(hexChainId);
236
+ const occurrenceFloor = await this.#getOccurrenceFloor(hexChainId);
237
+ // Same query shape as `TokenListController.getTokensURL` (token-service.ts),
238
+ // but `occurrenceFloor` comes from `/v1/suggestedOccurrenceFloors`.
239
+ // No `first=...` cap — the API returns the full per-chain list bounded
240
+ // server-side by `occurrenceFloor`.
241
+ const url = `${TOKEN_END_POINT_API}/tokens/${decimalChainId}` +
242
+ `?occurrenceFloor=${occurrenceFloor}` +
243
+ `&includeNativeAssets=false` +
244
+ `&includeTokenFees=false` +
245
+ `&includeAssetType=false` +
246
+ `&includeERC20Permit=false` +
247
+ `&includeStorage=false` +
248
+ `&includeRwaData=true`;
249
+ let response;
250
+ try {
251
+ response = await this.#fetch(url);
252
+ }
253
+ catch (error) {
254
+ console.error(`Tokens API request failed for chain ${hexChainId}:`, error);
255
+ return [];
256
+ }
257
+ if (!response.ok) {
258
+ console.error(`Tokens API responded with ${response.status} for chain ${hexChainId}`);
259
+ return [];
260
+ }
261
+ const raw = (await response.json());
262
+ const items = Array.isArray(raw)
263
+ ? raw
264
+ : [];
265
+ const filtered = applyChainSpecificFilters(hexChainId, items);
266
+ return filtered.map((item) => ({
267
+ address: item.address,
268
+ symbol: item.symbol ?? '',
269
+ name: item.name ?? '',
270
+ decimals: item.decimals ?? 18,
271
+ iconUrl: item.iconUrl,
272
+ aggregators: item.aggregators,
273
+ occurrences: item.occurrences,
274
+ }));
275
+ }
276
+ }
277
+ /**
278
+ * Apply chain-specific filters to a raw token list response, mirroring
279
+ * `fetchTokenListByChainId` in `assets-controllers/src/token-service.ts`.
280
+ *
281
+ * For Linea mainnet, the API returns extras with low aggregator coverage, so
282
+ * we keep only entries flagged by Linea's own team or seen by ≥3 aggregators.
283
+ *
284
+ * @param hexChainId - Hex chain ID.
285
+ * @param items - Raw items from the API response.
286
+ * @returns Items after chain-specific filtering.
287
+ */
288
+ function applyChainSpecificFilters(hexChainId, items) {
289
+ if (hexChainId === ControllerChainId['linea-mainnet']) {
290
+ return items.filter((item) => {
291
+ const aggregators = item.aggregators ?? [];
292
+ return aggregators.includes('lineaTeam') || aggregators.length >= 3;
293
+ });
294
+ }
295
+ return items;
296
+ }
297
+ //# sourceMappingURL=TokensApiClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokensApiClient.js","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChainId as ControllerChainId,\n convertHexToDecimal,\n} from '@metamask/controller-utils';\n\nimport type { ChainId, TokenListEntry } from '../types/index.js';\n\n/**\n * Same host + path that `TokenListController` (assets-controllers) uses\n * (`token-service.ts` → `getTokensURL`). Sharing this endpoint keeps the\n * RPC token detector aligned with the rest of the wallet's token listing\n * (occurrence floors, aggregator filters, icon URLs, etc.).\n */\nconst TOKEN_END_POINT_API = 'https://token.api.cx.metamask.io';\n\n/**\n * Endpoint that returns which CAIP chain IDs the Tokens API supports.\n * Used to skip detection (and avoid unnecessary network requests) for chains\n * that are not in the supported-networks list.\n */\nconst SUPPORTED_NETWORKS_URL = `${TOKEN_END_POINT_API}/v2/supportedNetworks`;\n\n/**\n * Per-chain suggested occurrence floors used as the `occurrenceFloor` query\n * param when fetching token lists (and aligned with TokenDataSource spam\n * filtering). Keys are decimal chain IDs.\n */\nconst SUGGESTED_OCCURRENCE_FLOORS_URL = `${TOKEN_END_POINT_API}/v1/suggestedOccurrenceFloors`;\n\n/**\n * Fallback `occurrenceFloor` when `/v1/suggestedOccurrenceFloors` has no entry\n * for the chain, or the floors request fails.\n */\nconst DEFAULT_OCCURRENCE_FLOOR = 3;\n\n/**\n * How long to keep the supported-networks response in the instance-level\n * cache before refreshing it. One hour is sufficient — the list rarely\n * changes and a stale cache just means we may do an unnecessary token-list\n * fetch for a newly-unsupported chain (harmless).\n */\nconst SUPPORTED_NETWORKS_CACHE_TTL_MS = 60 * 60_000;\n\n/**\n * How long to keep suggested occurrence floors cached. Same TTL as supported\n * networks — floors change infrequently.\n */\nconst SUGGESTED_OCCURRENCE_FLOORS_CACHE_TTL_MS = 60 * 60_000;\n\n/**\n * TanStack-Query cache config for the cached `fetchTokenList` path.\n *\n * The Tokens API per-chain list barely changes between polls, so we keep\n * results fresh for a few minutes (`staleTime`) and retain them in cache for\n * an hour (`gcTime`) so re-detections across accounts/chains hit the cache.\n * These tunings only apply when a `queryClient` is provided to the client;\n * the uncached fallback path (used in standalone tests) is unaffected.\n */\nconst TOKEN_LIST_STALE_TIME_MS = 5 * 60_000;\nconst TOKEN_LIST_GC_TIME_MS = 60 * 60_000;\n\n/**\n * Shape of the `/v2/supportedNetworks` response.\n */\ntype SupportedNetworksResponse = {\n fullSupport?: string[];\n partialSupport?: string[];\n};\n\n/**\n * Shape of a single item returned by the Tokens API `/tokens/{chainId}`\n * endpoint. Mirrors `TokenListToken` in\n * `packages/assets-controllers/src/TokenListController.ts` and the response\n * parsed by `fetchTokenListByChainId` in `token-service.ts`.\n */\ntype ApiTokenListItem = {\n address: string;\n symbol?: string;\n name?: string;\n decimals?: number;\n occurrences?: number;\n aggregators?: string[];\n iconUrl?: string;\n};\n\n/**\n * Minimal structural type for the TanStack Query client method we use.\n * Avoids a direct dependency on `@tanstack/query-core` while still being\n * fully compatible with the shared `QueryClient` exposed by\n * `ApiPlatformClient.queryClient` (`@metamask/core-backend`).\n */\nexport type TokenListQueryClient = {\n fetchQuery<TData>(options: {\n queryKey: readonly unknown[];\n queryFn: () => Promise<TData>;\n staleTime?: number;\n gcTime?: number;\n }): Promise<TData>;\n};\n\nexport type TokensApiClientConfig = {\n /** Fetch function (defaults to globalThis.fetch). */\n fetch?: typeof globalThis.fetch;\n /**\n * Optional TanStack-Query client used to cache token-list responses across\n * detector polls / accounts / instances. When omitted, every call hits the\n * network directly (preserves prior behaviour for tests and standalone use).\n */\n queryClient?: TokenListQueryClient;\n};\n\n/**\n * Client for the MetaMask Tokens API.\n *\n * Fetches the per-chain ERC-20 token list from the same endpoint that\n * `TokenListController` uses (`token.api.cx.metamask.io/tokens/{chainId}`),\n * with the same query parameters. The `occurrenceFloor` query param comes from\n * Token API `/v1/suggestedOccurrenceFloors` (fallback {@link DEFAULT_OCCURRENCE_FLOOR}),\n * matching `TokenDataSource` spam filtering. Linea's post-response aggregator\n * filter is still applied client-side.\n *\n * Before fetching a chain's token list, the client checks\n * `/v2/supportedNetworks` and returns `[]` immediately for chains that are\n * not in the supported list, avoiding unnecessary API calls. The supported-\n * networks response is cached in-memory for one hour.\n *\n * When constructed with a `queryClient`, token-list results are cached and\n * deduped via TanStack Query (5 min staleTime, 1 h gcTime), so concurrent\n * detection cycles for the same chain coalesce into a single network request.\n */\nexport class TokensApiClient {\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #queryClient: TokenListQueryClient | undefined;\n\n /** CAIP chain IDs returned by `/v2/supportedNetworks` (both tiers). */\n #supportedChainIds: Set<string> | undefined;\n\n /** Timestamp of the last successful `/v2/supportedNetworks` fetch. */\n #supportedChainIdsCachedAt = 0;\n\n /**\n * In-flight `/v2/supportedNetworks` request shared across concurrent callers\n * so only one network request is made even when multiple `fetchTokenList`\n * calls arrive before the first one resolves.\n */\n #supportedChainIdsRefreshPromise: Promise<void> | undefined;\n\n /** Decimal chain ID → suggested occurrence floor from Token API. */\n #suggestedOccurrenceFloors: Record<string, number> | undefined;\n\n /** Timestamp of the last successful floors fetch. */\n #suggestedOccurrenceFloorsCachedAt = 0;\n\n /** In-flight floors request shared across concurrent callers. */\n #suggestedOccurrenceFloorsRefreshPromise: Promise<void> | undefined;\n\n constructor(config?: TokensApiClientConfig) {\n this.#fetch = config?.fetch ?? globalThis.fetch.bind(globalThis);\n this.#queryClient = config?.queryClient;\n }\n\n /**\n * Fetch the list of ERC-20 tokens for a chain from the Tokens API.\n *\n * Returns `[]` immediately (without hitting the token-list endpoint) when\n * the chain is not in the `/v2/supportedNetworks` response, or when the\n * supported-networks check fails for any reason.\n *\n * @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).\n * @returns Array of token list entries with address and metadata, or an empty\n * array if the chain is unsupported or the request fails.\n */\n async fetchTokenList(hexChainId: ChainId): Promise<TokenListEntry[]> {\n // Treat any error from the supported-networks check as \"not supported\":\n // the token-list endpoint is only contacted for known-supported chains.\n if (!(await this.#isSupportedChain(hexChainId).catch(() => false))) {\n return [];\n }\n\n const queryClient = this.#queryClient;\n if (queryClient === undefined) {\n return this.#fetchTokenListUncached(hexChainId);\n }\n\n return queryClient.fetchQuery({\n // Namespacing keeps this key from colliding with other clients that\n // share the same QueryClient (e.g. core-backend's ApiPlatformClient).\n queryKey: [\n 'assets-controller',\n 'rpc-detection',\n 'token-list',\n { chainId: hexChainId },\n ],\n queryFn: () => this.#fetchTokenListUncached(hexChainId),\n staleTime: TOKEN_LIST_STALE_TIME_MS,\n gcTime: TOKEN_LIST_GC_TIME_MS,\n });\n }\n\n /**\n * Check whether the given chain is present in the supported-networks list.\n *\n * Uses an instance-level cache (1 h TTL). Throws if the network request\n * fails — callers should handle this (e.g. via `.catch(() => false)`).\n *\n * @param hexChainId - Hex chain ID to check.\n * @returns `true` when the chain is in the supported-networks list.\n */\n async #isSupportedChain(hexChainId: ChainId): Promise<boolean> {\n const now = Date.now();\n if (\n this.#supportedChainIds === undefined ||\n now - this.#supportedChainIdsCachedAt >= SUPPORTED_NETWORKS_CACHE_TTL_MS\n ) {\n await this.#refreshSupportedChainIds(now);\n }\n\n const caipChainId = `eip155:${convertHexToDecimal(hexChainId)}`;\n return this.#supportedChainIds?.has(caipChainId) ?? false;\n }\n\n /**\n * Fetch `/v2/supportedNetworks` and update the instance cache.\n * Concurrent callers share the same in-flight Promise so only one network\n * request is made. If the response is not 2xx the cache is left unchanged\n * (the chain will be treated as unsupported this cycle).\n *\n * @param now - Current timestamp used to stamp the cache entry.\n * @returns A promise that resolves once the cache has been refreshed.\n */\n async #refreshSupportedChainIds(now: number): Promise<void> {\n if (this.#supportedChainIdsRefreshPromise !== undefined) {\n return this.#supportedChainIdsRefreshPromise;\n }\n\n this.#supportedChainIdsRefreshPromise = (async (): Promise<void> => {\n try {\n const response = await this.#fetch(SUPPORTED_NETWORKS_URL);\n if (response.ok) {\n const data = (await response.json()) as SupportedNetworksResponse;\n this.#supportedChainIds = new Set([\n ...(data.fullSupport ?? []),\n ...(data.partialSupport ?? []),\n ]);\n this.#supportedChainIdsCachedAt = now;\n }\n } finally {\n this.#supportedChainIdsRefreshPromise = undefined;\n }\n })();\n\n return this.#supportedChainIdsRefreshPromise;\n }\n\n /**\n * Resolve the `occurrenceFloor` query param for a chain from Token API\n * `/v1/suggestedOccurrenceFloors`. Falls back to\n * {@link DEFAULT_OCCURRENCE_FLOOR} when the chain is missing or the request\n * fails.\n *\n * @param hexChainId - Hex chain ID.\n * @returns Occurrence floor to send to `/tokens/{chainId}`.\n */\n async #getOccurrenceFloor(hexChainId: ChainId): Promise<number> {\n const now = Date.now();\n if (\n this.#suggestedOccurrenceFloors === undefined ||\n now - this.#suggestedOccurrenceFloorsCachedAt >=\n SUGGESTED_OCCURRENCE_FLOORS_CACHE_TTL_MS\n ) {\n await this.#refreshSuggestedOccurrenceFloors(now);\n }\n\n const decimalChainId = String(convertHexToDecimal(hexChainId));\n return (\n this.#suggestedOccurrenceFloors?.[decimalChainId] ??\n DEFAULT_OCCURRENCE_FLOOR\n );\n }\n\n /**\n * Fetch `/v1/suggestedOccurrenceFloors` and update the instance cache.\n * Concurrent callers share the in-flight Promise. Failures leave the cache\n * empty so {@link #getOccurrenceFloor} falls back to the default.\n *\n * @param now - Current timestamp used to stamp the cache entry.\n * @returns A promise that resolves once the cache has been refreshed.\n */\n async #refreshSuggestedOccurrenceFloors(now: number): Promise<void> {\n if (this.#suggestedOccurrenceFloorsRefreshPromise !== undefined) {\n return this.#suggestedOccurrenceFloorsRefreshPromise;\n }\n\n this.#suggestedOccurrenceFloorsRefreshPromise =\n (async (): Promise<void> => {\n try {\n const response = await this.#fetch(SUGGESTED_OCCURRENCE_FLOORS_URL);\n if (response.ok) {\n const data = (await response.json()) as Record<string, number>;\n this.#suggestedOccurrenceFloors =\n data && typeof data === 'object' && !Array.isArray(data)\n ? data\n : {};\n this.#suggestedOccurrenceFloorsCachedAt = now;\n } else {\n this.#suggestedOccurrenceFloors = {};\n this.#suggestedOccurrenceFloorsCachedAt = now;\n }\n } catch {\n this.#suggestedOccurrenceFloors = {};\n this.#suggestedOccurrenceFloorsCachedAt = now;\n } finally {\n this.#suggestedOccurrenceFloorsRefreshPromise = undefined;\n }\n })();\n\n return this.#suggestedOccurrenceFloorsRefreshPromise;\n }\n\n async #fetchTokenListUncached(\n hexChainId: ChainId,\n ): Promise<TokenListEntry[]> {\n const decimalChainId = convertHexToDecimal(hexChainId);\n const occurrenceFloor = await this.#getOccurrenceFloor(hexChainId);\n\n // Same query shape as `TokenListController.getTokensURL` (token-service.ts),\n // but `occurrenceFloor` comes from `/v1/suggestedOccurrenceFloors`.\n // No `first=...` cap — the API returns the full per-chain list bounded\n // server-side by `occurrenceFloor`.\n const url =\n `${TOKEN_END_POINT_API}/tokens/${decimalChainId}` +\n `?occurrenceFloor=${occurrenceFloor}` +\n `&includeNativeAssets=false` +\n `&includeTokenFees=false` +\n `&includeAssetType=false` +\n `&includeERC20Permit=false` +\n `&includeStorage=false` +\n `&includeRwaData=true`;\n\n let response: Response;\n try {\n response = await this.#fetch(url);\n } catch (error) {\n console.error(\n `Tokens API request failed for chain ${hexChainId}:`,\n error,\n );\n return [];\n }\n\n if (!response.ok) {\n console.error(\n `Tokens API responded with ${response.status} for chain ${hexChainId}`,\n );\n return [];\n }\n\n const raw = (await response.json()) as unknown;\n const items: ApiTokenListItem[] = Array.isArray(raw)\n ? (raw as ApiTokenListItem[])\n : [];\n\n const filtered = applyChainSpecificFilters(hexChainId, items);\n\n return filtered.map((item) => ({\n address: item.address,\n symbol: item.symbol ?? '',\n name: item.name ?? '',\n decimals: item.decimals ?? 18,\n iconUrl: item.iconUrl,\n aggregators: item.aggregators,\n occurrences: item.occurrences,\n }));\n }\n}\n\n/**\n * Apply chain-specific filters to a raw token list response, mirroring\n * `fetchTokenListByChainId` in `assets-controllers/src/token-service.ts`.\n *\n * For Linea mainnet, the API returns extras with low aggregator coverage, so\n * we keep only entries flagged by Linea's own team or seen by ≥3 aggregators.\n *\n * @param hexChainId - Hex chain ID.\n * @param items - Raw items from the API response.\n * @returns Items after chain-specific filtering.\n */\nfunction applyChainSpecificFilters(\n hexChainId: ChainId,\n items: ApiTokenListItem[],\n): ApiTokenListItem[] {\n if (hexChainId === ControllerChainId['linea-mainnet']) {\n return items.filter((item) => {\n const aggregators = item.aggregators ?? [];\n return aggregators.includes('lineaTeam') || aggregators.length >= 3;\n });\n }\n return items;\n}\n"]}
@@ -1,3 +1,4 @@
1
1
  export { decodeAggregate3Response, encodeAggregate3, MulticallClient, type MulticallClientConfig, } from './MulticallClient.js';
2
+ export { TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from './TokensApiClient.js';
2
3
  export type { GetProviderFunction, Provider } from '../types/index.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export { decodeAggregate3Response, encodeAggregate3, MulticallClient, } from './MulticallClient.js';
2
+ export { TokensApiClient, } from './TokensApiClient.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,GAEhB,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient.js';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types/index.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,GAEhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,GAGhB,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient.js';\n\nexport {\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './TokensApiClient.js';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types/index.js';\n"]}
@@ -1,5 +1,5 @@
1
1
  export type { Address, AssetFetchEntry, AssetsBalanceState, ChainId, GetProviderFunction, Provider, BalanceOfRequest, BalanceOfResponse, TokenListState, BalanceFetchResult, TokenDetectionResult, } from './types/index.js';
2
- export { MulticallClient, type MulticallClientConfig, } from './clients/index.js';
2
+ export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from './clients/index.js';
3
3
  export { BalanceFetcher, TokenDetector, StakedBalanceFetcher, getSupportedStakingChainIds, getStakingContractAddress, isStakingContractAssetId, type BalancePollingInput, type DetectionPollingInput, type StakedBalancePollingInput, type StakedBalanceFetchResult, type OnBalanceUpdateCallback, type OnDetectionUpdateCallback, type OnStakedBalanceUpdateCallback, } from './services/index.js';
4
4
  export { divideIntoBatches, reduceInBatchesSerially } from './utils/index.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,eAAe,EACf,kBAAkB,EAClB,OAAO,EACP,mBAAmB,EACnB,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,GACnC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,eAAe,EACf,kBAAkB,EAClB,OAAO,EACP,mBAAmB,EACnB,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,GACnC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,4 +1,4 @@
1
- export { MulticallClient, } from './clients/index.js';
1
+ export { MulticallClient, TokensApiClient, } from './clients/index.js';
2
2
  export { BalanceFetcher, TokenDetector, StakedBalanceFetcher, getSupportedStakingChainIds, getStakingContractAddress, isStakingContractAssetId, } from './services/index.js';
3
3
  export { divideIntoBatches, reduceInBatchesSerially } from './utils/index.js';
4
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,eAAe,GAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,GAQzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export type {\n Address,\n AssetFetchEntry,\n AssetsBalanceState,\n ChainId,\n GetProviderFunction,\n Provider,\n BalanceOfRequest,\n BalanceOfResponse,\n TokenListState,\n BalanceFetchResult,\n TokenDetectionResult,\n} from './types/index.js';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n} from './clients/index.js';\nexport {\n BalanceFetcher,\n TokenDetector,\n StakedBalanceFetcher,\n getSupportedStakingChainIds,\n getStakingContractAddress,\n isStakingContractAssetId,\n type BalancePollingInput,\n type DetectionPollingInput,\n type StakedBalancePollingInput,\n type StakedBalanceFetchResult,\n type OnBalanceUpdateCallback,\n type OnDetectionUpdateCallback,\n type OnStakedBalanceUpdateCallback,\n} from './services/index.js';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils/index.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,eAAe,EAEf,eAAe,GAGhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,GAQzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export type {\n Address,\n AssetFetchEntry,\n AssetsBalanceState,\n ChainId,\n GetProviderFunction,\n Provider,\n BalanceOfRequest,\n BalanceOfResponse,\n TokenListState,\n BalanceFetchResult,\n TokenDetectionResult,\n} from './types/index.js';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './clients/index.js';\nexport {\n BalanceFetcher,\n TokenDetector,\n StakedBalanceFetcher,\n getSupportedStakingChainIds,\n getStakingContractAddress,\n isStakingContractAssetId,\n type BalancePollingInput,\n type DetectionPollingInput,\n type StakedBalancePollingInput,\n type StakedBalanceFetchResult,\n type OnBalanceUpdateCallback,\n type OnDetectionUpdateCallback,\n type OnStakedBalanceUpdateCallback,\n} from './services/index.js';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils/index.js';\n"]}
@@ -1,11 +1,6 @@
1
- import type { TokenApiClient } from '@metamask/core-backend';
2
1
  import type { MulticallClient } from '../clients/index.js';
3
- import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult, TokenListEntry } from '../types/index.js';
4
- /**
5
- * Token-list calls TokenDetector makes. `TokenApiClient` satisfies this, and
6
- * tests can pass an object with only these methods.
7
- */
8
- type TokenListApi = Pick<TokenApiClient, 'fetchV2SupportedNetworks' | 'fetchV1SuggestedOccurrenceFloors' | 'fetchTokenList'>;
2
+ import type { TokensApiClient } from '../clients/TokensApiClient.js';
3
+ import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult } from '../types/index.js';
9
4
  export type TokenDetectorConfig = {
10
5
  /** Function returning whether token detection is enabled (avoids stale value) */
11
6
  tokenDetectionEnabled?: () => boolean;
@@ -55,7 +50,7 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
55
50
  */
56
51
  export declare class TokenDetector extends TokenDetector_base {
57
52
  #private;
58
- constructor(multicallClient: MulticallClient, tokenApi: TokenListApi, config?: TokenDetectorConfig);
53
+ constructor(multicallClient: MulticallClient, tokensApiClient: TokensApiClient, config?: TokenDetectorConfig);
59
54
  /**
60
55
  * Set the callback to receive detection updates during polling.
61
56
  *
@@ -77,13 +72,6 @@ export declare class TokenDetector extends TokenDetector_base {
77
72
  * @returns Array of token contract addresses.
78
73
  */
79
74
  getTokensToCheck(chainId: ChainId): Promise<Address[]>;
80
- /**
81
- * Fetch the mapped token list for a chain and keep it for metadata lookups.
82
- *
83
- * @param chainId - Chain ID in hex format.
84
- * @returns Token list entries, including fields the address list drops.
85
- */
86
- fetchAndCacheTokenList(chainId: ChainId): Promise<TokenListEntry[]>;
87
75
  detectTokens(chainId: ChainId, accountId: AccountId, accountAddress: Address, options?: TokenDetectionOptions): Promise<TokenDetectionResult>;
88
76
  }
89
77
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetector.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACf,MAAM,mBAAmB,CAAC;AAgC3B;;;GAGG;AACH,KAAK,YAAY,GAAG,IAAI,CACtB,cAAc,EACZ,0BAA0B,GAC1B,kCAAkC,GAClC,gBAAgB,CACnB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG;IAChC,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC;IACtC,yGAAyG;IACzG,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,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,yBAAyB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;AAE/E;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;IAiB7F,YACE,eAAe,EAAE,eAAe,EAChC,QAAQ,EAAE,YAAY,EACtB,MAAM,CAAC,EAAE,mBAAmB,EAgB7B;IAED;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAE9D;IAED;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9D;IAED;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAG3D;IAED;;;;;OAKG;IACG,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAcxE;IAEK,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAoF/B;CA8PF"}
1
+ {"version":3,"file":"TokenDetector.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,mBAAmB,CAAC;AAO3B,MAAM,MAAM,mBAAmB,GAAG;IAChC,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC;IACtC,yGAAyG;IACzG,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,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,yBAAyB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;AAE/E;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;IAW7F,YACE,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,MAAM,CAAC,EAAE,mBAAmB,EAgB7B;IAED;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAE9D;IAED;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9D;IAED;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAG3D;IAEK,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAoF/B;CAsJF"}