@metamask-previews/assets-controller 6.4.0-preview-8f8f607 → 6.4.0-preview-2b316ced3

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 (46) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/AssetsController.cjs +5 -0
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts.map +1 -1
  5. package/dist/AssetsController.d.mts.map +1 -1
  6. package/dist/AssetsController.mjs +5 -0
  7. package/dist/AssetsController.mjs.map +1 -1
  8. package/dist/data-sources/RpcDataSource.cjs +46 -25
  9. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  10. package/dist/data-sources/RpcDataSource.d.cts +12 -0
  11. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  12. package/dist/data-sources/RpcDataSource.d.mts +12 -0
  13. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  14. package/dist/data-sources/RpcDataSource.mjs +46 -25
  15. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  16. package/dist/data-sources/TokenDataSource.cjs +12 -5
  17. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  18. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  19. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  20. package/dist/data-sources/TokenDataSource.mjs +12 -5
  21. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  22. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.cjs +124 -32
  23. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.cjs.map +1 -1
  24. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.cts +31 -3
  25. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.cts.map +1 -1
  26. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.mts +31 -3
  27. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.mts.map +1 -1
  28. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.mjs +124 -32
  29. package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.mjs.map +1 -1
  30. package/dist/data-sources/evm-rpc-services/clients/index.cjs.map +1 -1
  31. package/dist/data-sources/evm-rpc-services/clients/index.d.cts +1 -1
  32. package/dist/data-sources/evm-rpc-services/clients/index.d.cts.map +1 -1
  33. package/dist/data-sources/evm-rpc-services/clients/index.d.mts +1 -1
  34. package/dist/data-sources/evm-rpc-services/clients/index.d.mts.map +1 -1
  35. package/dist/data-sources/evm-rpc-services/clients/index.mjs.map +1 -1
  36. package/dist/data-sources/evm-rpc-services/index.cjs.map +1 -1
  37. package/dist/data-sources/evm-rpc-services/index.d.cts +1 -1
  38. package/dist/data-sources/evm-rpc-services/index.d.cts.map +1 -1
  39. package/dist/data-sources/evm-rpc-services/index.d.mts +1 -1
  40. package/dist/data-sources/evm-rpc-services/index.d.mts.map +1 -1
  41. package/dist/data-sources/evm-rpc-services/index.mjs.map +1 -1
  42. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  43. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  44. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  45. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  46. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TokensApiClient.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,2BAAiB;AAgBxD,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,qBAAa,eAAe;;gBAGd,MAAM,CAAC,EAAE,qBAAqB;IAI1C;;;;;;;OAOG;IACG,cAAc,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAmCrE"}
1
+ {"version":3,"file":"TokensApiClient.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,2BAAiB;AAgExD;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE;QACzB,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;QAC7B,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,oBAAoB,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAe;;gBAKd,MAAM,CAAC,EAAE,qBAAqB;IAK1C;;;;;;OAMG;IACG,cAAc,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAgErE"}
@@ -1,18 +1,46 @@
1
1
  import type { ChainId, TokenListEntry } from "../types/index.mjs";
2
+ /**
3
+ * Minimal structural type for the TanStack Query client method we use.
4
+ * Avoids a direct dependency on `@tanstack/query-core` while still being
5
+ * fully compatible with the shared `QueryClient` exposed by
6
+ * `ApiPlatformClient.queryClient` (`@metamask/core-backend`).
7
+ */
8
+ export type TokenListQueryClient = {
9
+ fetchQuery<TData>(options: {
10
+ queryKey: readonly unknown[];
11
+ queryFn: () => Promise<TData>;
12
+ staleTime?: number;
13
+ gcTime?: number;
14
+ }): Promise<TData>;
15
+ };
2
16
  export type TokensApiClientConfig = {
3
17
  /** Fetch function (defaults to globalThis.fetch). */
4
18
  fetch?: typeof globalThis.fetch;
19
+ /**
20
+ * Optional TanStack-Query client used to cache token-list responses across
21
+ * detector polls / accounts / instances. When omitted, every call hits the
22
+ * network directly (preserves prior behaviour for tests and standalone use).
23
+ */
24
+ queryClient?: TokenListQueryClient;
5
25
  };
6
26
  /**
7
27
  * Client for the MetaMask Tokens API.
8
- * Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).
28
+ *
29
+ * Fetches the per-chain ERC-20 token list from the same endpoint that
30
+ * `TokenListController` uses (`token.api.cx.metamask.io/tokens/{chainId}`),
31
+ * with the same query parameters and the same per-chain occurrence floor /
32
+ * Linea aggregator filter. This keeps RPC token detection in lockstep with
33
+ * the wallet's primary token list.
34
+ *
35
+ * When constructed with a `queryClient`, results are cached and deduped via
36
+ * TanStack Query (5 min staleTime, 1 h gcTime), so concurrent detection cycles
37
+ * for the same chain coalesce into a single network request.
9
38
  */
10
39
  export declare class TokensApiClient {
11
40
  #private;
12
41
  constructor(config?: TokensApiClientConfig);
13
42
  /**
14
- * Fetch the list of top ERC-20 tokens for a chain from the Tokens API.
15
- * Only `erc20` assets are returned; native (`slip44`) entries are skipped.
43
+ * Fetch the list of ERC-20 tokens for a chain from the Tokens API.
16
44
  *
17
45
  * @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
18
46
  * @returns Array of token list entries with address and metadata.
@@ -1 +1 @@
1
- {"version":3,"file":"TokensApiClient.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,2BAAiB;AAgBxD,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,qBAAa,eAAe;;gBAGd,MAAM,CAAC,EAAE,qBAAqB;IAI1C;;;;;;;OAOG;IACG,cAAc,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAmCrE"}
1
+ {"version":3,"file":"TokensApiClient.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,2BAAiB;AAgExD;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE;QACzB,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;QAC7B,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,oBAAoB,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAe;;gBAKd,MAAM,CAAC,EAAE,qBAAqB;IAK1C;;;;;;OAMG;IACG,cAAc,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAgErE"}
@@ -9,55 +9,147 @@ 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 _TokensApiClient_fetch;
13
- const TOKENS_API_BASE_URL = 'https://tokens.api.cx.metamask.io/v3/chains';
14
- /** How many tokens to request from the API per chain. */
15
- const TOKENS_API_FIRST = 25;
12
+ var _TokensApiClient_instances, _TokensApiClient_fetch, _TokensApiClient_queryClient, _TokensApiClient_fetchTokenListUncached;
13
+ import { ChainId as ControllerChainId, convertHexToDecimal } from "@metamask/controller-utils";
14
+ /**
15
+ * Same host + path that `TokenListController` (assets-controllers) uses
16
+ * (`token-service.ts` → `getTokensURL`). Sharing this endpoint keeps the
17
+ * RPC token detector aligned with the rest of the wallet's token listing
18
+ * (occurrence floors, aggregator filters, icon URLs, etc.).
19
+ */
20
+ const TOKEN_END_POINT_API = 'https://token.api.cx.metamask.io';
21
+ /**
22
+ * Tempo Mainnet — not yet present in `@metamask/controller-utils`'s `ChainId`
23
+ * map at the time of writing, so it's spelled out as a literal here exactly as
24
+ * `TokenListController` does (see `token-service.ts:getTokensURL`).
25
+ */
26
+ const TEMPO_MAINNET_CHAIN_ID = '0x1079';
27
+ /**
28
+ * Per-chain occurrence floor, mirroring `TokenListController.getTokensURL`:
29
+ * Linea mainnet, MegaETH mainnet, and Tempo mainnet have thinner aggregator
30
+ * coverage so we lower the floor; everything else uses the default 3.
31
+ *
32
+ * @param hexChainId - Hex chain ID.
33
+ * @returns The occurrence floor to send to the Tokens API.
34
+ */
35
+ function getOccurrenceFloor(hexChainId) {
36
+ if (hexChainId === ControllerChainId['linea-mainnet'] ||
37
+ hexChainId === ControllerChainId['megaeth-mainnet'] ||
38
+ hexChainId === TEMPO_MAINNET_CHAIN_ID) {
39
+ return 1;
40
+ }
41
+ return 3;
42
+ }
43
+ /**
44
+ * TanStack-Query cache config for the cached `fetchTokenList` path.
45
+ *
46
+ * The Tokens API per-chain list barely changes between polls, so we keep
47
+ * results fresh for a few minutes (`staleTime`) and retain them in cache for
48
+ * an hour (`gcTime`) so re-detections across accounts/chains hit the cache.
49
+ * These tunings only apply when a `queryClient` is provided to the client;
50
+ * the uncached fallback path (used in standalone tests) is unaffected.
51
+ */
52
+ const TOKEN_LIST_STALE_TIME_MS = 5 * 60000;
53
+ const TOKEN_LIST_GC_TIME_MS = 60 * 60000;
16
54
  /**
17
55
  * Client for the MetaMask Tokens API.
18
- * Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).
56
+ *
57
+ * Fetches the per-chain ERC-20 token list from the same endpoint that
58
+ * `TokenListController` uses (`token.api.cx.metamask.io/tokens/{chainId}`),
59
+ * with the same query parameters and the same per-chain occurrence floor /
60
+ * Linea aggregator filter. This keeps RPC token detection in lockstep with
61
+ * the wallet's primary token list.
62
+ *
63
+ * When constructed with a `queryClient`, results are cached and deduped via
64
+ * TanStack Query (5 min staleTime, 1 h gcTime), so concurrent detection cycles
65
+ * for the same chain coalesce into a single network request.
19
66
  */
20
67
  export class TokensApiClient {
21
68
  constructor(config) {
69
+ _TokensApiClient_instances.add(this);
22
70
  _TokensApiClient_fetch.set(this, void 0);
71
+ _TokensApiClient_queryClient.set(this, void 0);
23
72
  __classPrivateFieldSet(this, _TokensApiClient_fetch, config?.fetch ?? globalThis.fetch.bind(globalThis), "f");
73
+ __classPrivateFieldSet(this, _TokensApiClient_queryClient, config?.queryClient, "f");
24
74
  }
25
75
  /**
26
- * Fetch the list of top ERC-20 tokens for a chain from the Tokens API.
27
- * Only `erc20` assets are returned; native (`slip44`) entries are skipped.
76
+ * Fetch the list of ERC-20 tokens for a chain from the Tokens API.
28
77
  *
29
78
  * @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
30
79
  * @returns Array of token list entries with address and metadata.
31
80
  * @throws If the API responds with a non-2xx status.
32
81
  */
33
82
  async fetchTokenList(hexChainId) {
34
- const chainIdDecimal = parseInt(hexChainId, 16);
35
- const caipChainId = `eip155:${chainIdDecimal}`;
36
- const url = `${TOKENS_API_BASE_URL}/${caipChainId}/assets` +
37
- `?first=${TOKENS_API_FIRST}` +
38
- `&includeOccurrences=true` +
39
- `&includeMetadata=true` +
40
- `&occurrenceFloor=3` +
41
- `&includeRwaData=true` +
42
- `&excludeDescription=true`;
43
- const response = await __classPrivateFieldGet(this, _TokensApiClient_fetch, "f").call(this, url);
44
- if (!response.ok) {
45
- throw new Error(`Tokens API responded with ${response.status} for ${caipChainId}`);
83
+ const queryClient = __classPrivateFieldGet(this, _TokensApiClient_queryClient, "f");
84
+ if (queryClient === undefined) {
85
+ return __classPrivateFieldGet(this, _TokensApiClient_instances, "m", _TokensApiClient_fetchTokenListUncached).call(this, hexChainId);
46
86
  }
47
- const { data } = (await response.json());
48
- return data
49
- .filter((item) => item.assetId.includes('/erc20:'))
50
- .map((item) => {
51
- const address = item.assetId.split('/erc20:')[1];
52
- return {
53
- address,
54
- symbol: item.symbol ?? '',
55
- name: item.name ?? '',
56
- decimals: item.decimals ?? 18,
57
- occurrences: item.occurrences,
58
- };
87
+ return queryClient.fetchQuery({
88
+ // Namespacing keeps this key from colliding with other clients that
89
+ // share the same QueryClient (e.g. core-backend's ApiPlatformClient).
90
+ queryKey: [
91
+ 'assets-controller',
92
+ 'rpc-detection',
93
+ 'token-list',
94
+ { chainId: hexChainId },
95
+ ],
96
+ queryFn: () => __classPrivateFieldGet(this, _TokensApiClient_instances, "m", _TokensApiClient_fetchTokenListUncached).call(this, hexChainId),
97
+ staleTime: TOKEN_LIST_STALE_TIME_MS,
98
+ gcTime: TOKEN_LIST_GC_TIME_MS,
99
+ });
100
+ }
101
+ }
102
+ _TokensApiClient_fetch = new WeakMap(), _TokensApiClient_queryClient = new WeakMap(), _TokensApiClient_instances = new WeakSet(), _TokensApiClient_fetchTokenListUncached = async function _TokensApiClient_fetchTokenListUncached(hexChainId) {
103
+ const decimalChainId = convertHexToDecimal(hexChainId);
104
+ const occurrenceFloor = getOccurrenceFloor(hexChainId);
105
+ // Mirrors `TokenListController.getTokensURL` exactly (token-service.ts).
106
+ // No `first=...` cap — the API returns the full per-chain list bounded
107
+ // server-side by `occurrenceFloor`.
108
+ const url = `${TOKEN_END_POINT_API}/tokens/${decimalChainId}` +
109
+ `?occurrenceFloor=${occurrenceFloor}` +
110
+ `&includeNativeAssets=false` +
111
+ `&includeTokenFees=false` +
112
+ `&includeAssetType=false` +
113
+ `&includeERC20Permit=false` +
114
+ `&includeStorage=false` +
115
+ `&includeRwaData=true`;
116
+ const response = await __classPrivateFieldGet(this, _TokensApiClient_fetch, "f").call(this, url);
117
+ if (!response.ok) {
118
+ throw new Error(`Tokens API responded with ${response.status} for chain ${hexChainId}`);
119
+ }
120
+ const raw = (await response.json());
121
+ const items = Array.isArray(raw)
122
+ ? raw
123
+ : [];
124
+ const filtered = applyChainSpecificFilters(hexChainId, items);
125
+ return filtered.map((item) => ({
126
+ address: item.address,
127
+ symbol: item.symbol ?? '',
128
+ name: item.name ?? '',
129
+ decimals: item.decimals ?? 18,
130
+ iconUrl: item.iconUrl,
131
+ aggregators: item.aggregators,
132
+ occurrences: item.occurrences,
133
+ }));
134
+ };
135
+ /**
136
+ * Apply chain-specific filters to a raw token list response, mirroring
137
+ * `fetchTokenListByChainId` in `assets-controllers/src/token-service.ts`.
138
+ *
139
+ * For Linea mainnet, the API returns extras with low aggregator coverage, so
140
+ * we keep only entries flagged by Linea's own team or seen by ≥3 aggregators.
141
+ *
142
+ * @param hexChainId - Hex chain ID.
143
+ * @param items - Raw items from the API response.
144
+ * @returns Items after chain-specific filtering.
145
+ */
146
+ function applyChainSpecificFilters(hexChainId, items) {
147
+ if (hexChainId === ControllerChainId['linea-mainnet']) {
148
+ return items.filter((item) => {
149
+ const aggregators = item.aggregators ?? [];
150
+ return aggregators.includes('lineaTeam') || aggregators.length >= 3;
59
151
  });
60
152
  }
153
+ return items;
61
154
  }
62
- _TokensApiClient_fetch = new WeakMap();
63
155
  //# sourceMappingURL=TokensApiClient.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TokensApiClient.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/TokensApiClient.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,MAAM,mBAAmB,GAAG,6CAA6C,CAAC;AAE1E,yDAAyD;AACzD,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAgB5B;;;GAGG;AACH,MAAM,OAAO,eAAe;IAG1B,YAAY,MAA8B;QAFjC,yCAAgC;QAGvC,uBAAA,IAAI,0BAAU,MAAM,EAAE,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAA,CAAC;IACnE,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,cAAc,CAAC,UAAmB;QACtC,MAAM,cAAc,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,UAAU,cAAc,EAAE,CAAC;QAE/C,MAAM,GAAG,GACP,GAAG,mBAAmB,IAAI,WAAW,SAAS;YAC9C,UAAU,gBAAgB,EAAE;YAC5B,0BAA0B;YAC1B,uBAAuB;YACvB,oBAAoB;YACpB,sBAAsB;YACtB,0BAA0B,CAAC;QAE7B,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,8BAAO,MAAX,IAAI,EAAQ,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,6BAA6B,QAAQ,CAAC,MAAM,QAAQ,WAAW,EAAE,CAClE,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA6B,CAAC;QAErE,OAAO,IAAI;aACR,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;aAClD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACZ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,OAAO;gBACL,OAAO;gBACP,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;gBACzB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;gBAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;aAC9B,CAAC;QACJ,CAAC,CAAC,CAAC;IACP,CAAC;CACF","sourcesContent":["import type { ChainId, TokenListEntry } from '../types';\n\nconst TOKENS_API_BASE_URL = 'https://tokens.api.cx.metamask.io/v3/chains';\n\n/** How many tokens to request from the API per chain. */\nconst TOKENS_API_FIRST = 25;\n\n/** Shape of a single item in the Tokens API response `data` array. */\ntype ApiTokenData = {\n assetId: string;\n symbol?: string;\n name?: string;\n decimals?: number;\n occurrences?: number;\n};\n\nexport type TokensApiClientConfig = {\n /** Fetch function (defaults to globalThis.fetch). */\n fetch?: typeof globalThis.fetch;\n};\n\n/**\n * Client for the MetaMask Tokens API.\n * Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).\n */\nexport class TokensApiClient {\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(config?: TokensApiClientConfig) {\n this.#fetch = config?.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Fetch the list of top ERC-20 tokens for a chain from the Tokens API.\n * Only `erc20` assets are returned; native (`slip44`) entries are skipped.\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.\n * @throws If the API responds with a non-2xx status.\n */\n async fetchTokenList(hexChainId: ChainId): Promise<TokenListEntry[]> {\n const chainIdDecimal = parseInt(hexChainId, 16);\n const caipChainId = `eip155:${chainIdDecimal}`;\n\n const url =\n `${TOKENS_API_BASE_URL}/${caipChainId}/assets` +\n `?first=${TOKENS_API_FIRST}` +\n `&includeOccurrences=true` +\n `&includeMetadata=true` +\n `&occurrenceFloor=3` +\n `&includeRwaData=true` +\n `&excludeDescription=true`;\n\n const response = await this.#fetch(url);\n if (!response.ok) {\n throw new Error(\n `Tokens API responded with ${response.status} for ${caipChainId}`,\n );\n }\n\n const { data } = (await response.json()) as { data: ApiTokenData[] };\n\n return data\n .filter((item) => item.assetId.includes('/erc20:'))\n .map((item) => {\n const address = item.assetId.split('/erc20:')[1];\n return {\n address,\n symbol: item.symbol ?? '',\n name: item.name ?? '',\n decimals: item.decimals ?? 18,\n occurrences: item.occurrences,\n };\n });\n }\n}\n"]}
1
+ {"version":3,"file":"TokensApiClient.mjs","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';\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 * Tempo Mainnet — not yet present in `@metamask/controller-utils`'s `ChainId`\n * map at the time of writing, so it's spelled out as a literal here exactly as\n * `TokenListController` does (see `token-service.ts:getTokensURL`).\n */\nconst TEMPO_MAINNET_CHAIN_ID = '0x1079' as const;\n\n/**\n * Per-chain occurrence floor, mirroring `TokenListController.getTokensURL`:\n * Linea mainnet, MegaETH mainnet, and Tempo mainnet have thinner aggregator\n * coverage so we lower the floor; everything else uses the default 3.\n *\n * @param hexChainId - Hex chain ID.\n * @returns The occurrence floor to send to the Tokens API.\n */\nfunction getOccurrenceFloor(hexChainId: ChainId): number {\n if (\n hexChainId === ControllerChainId['linea-mainnet'] ||\n hexChainId === ControllerChainId['megaeth-mainnet'] ||\n hexChainId === TEMPO_MAINNET_CHAIN_ID\n ) {\n return 1;\n }\n return 3;\n}\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 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 and the same per-chain occurrence floor /\n * Linea aggregator filter. This keeps RPC token detection in lockstep with\n * the wallet's primary token list.\n *\n * When constructed with a `queryClient`, results are cached and deduped via\n * TanStack Query (5 min staleTime, 1 h gcTime), so concurrent detection cycles\n * 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 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 * @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).\n * @returns Array of token list entries with address and metadata.\n * @throws If the API responds with a non-2xx status.\n */\n async fetchTokenList(hexChainId: ChainId): Promise<TokenListEntry[]> {\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 async #fetchTokenListUncached(\n hexChainId: ChainId,\n ): Promise<TokenListEntry[]> {\n const decimalChainId = convertHexToDecimal(hexChainId);\n const occurrenceFloor = getOccurrenceFloor(hexChainId);\n\n // Mirrors `TokenListController.getTokensURL` exactly (token-service.ts).\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 const response = await this.#fetch(url);\n if (!response.ok) {\n throw new Error(\n `Tokens API responded with ${response.status} for chain ${hexChainId}`,\n );\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 +1 @@
1
- {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":";;;AAAA,yDAK2B;AAJzB,2HAAA,wBAAwB,OAAA;AACxB,mHAAA,gBAAgB,OAAA;AAChB,kHAAA,eAAe,OAAA;AAIjB,yDAAgF;AAAvE,kHAAA,eAAe,OAAA","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\n\nexport { TokensApiClient, type TokensApiClientConfig } from './TokensApiClient';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types';\n"]}
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":";;;AAAA,yDAK2B;AAJzB,2HAAA,wBAAwB,OAAA;AACxB,mHAAA,gBAAgB,OAAA;AAChB,kHAAA,eAAe,OAAA;AAIjB,yDAI2B;AAHzB,kHAAA,eAAe,OAAA","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\n\nexport {\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './TokensApiClient';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types';\n"]}
@@ -1,4 +1,4 @@
1
1
  export { decodeAggregate3Response, encodeAggregate3, MulticallClient, type MulticallClientConfig, } from "./MulticallClient.cjs";
2
- export { TokensApiClient, type TokensApiClientConfig } from "./TokensApiClient.cjs";
2
+ export { TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from "./TokensApiClient.cjs";
3
3
  export type { GetProviderFunction, Provider } from "../types/index.cjs";
4
4
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","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,8BAA0B;AAE3B,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,8BAA0B;AAGhF,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,2BAAiB"}
1
+ {"version":3,"file":"index.d.cts","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,8BAA0B;AAE3B,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,8BAA0B;AAG3B,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,2BAAiB"}
@@ -1,4 +1,4 @@
1
1
  export { decodeAggregate3Response, encodeAggregate3, MulticallClient, type MulticallClientConfig, } from "./MulticallClient.mjs";
2
- export { TokensApiClient, type TokensApiClientConfig } from "./TokensApiClient.mjs";
2
+ export { TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from "./TokensApiClient.mjs";
3
3
  export type { GetProviderFunction, Provider } from "../types/index.mjs";
4
4
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","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,8BAA0B;AAE3B,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,8BAA0B;AAGhF,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,2BAAiB"}
1
+ {"version":3,"file":"index.d.mts","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,8BAA0B;AAE3B,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,8BAA0B;AAG3B,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,2BAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAEhB,8BAA0B;AAE3B,OAAO,EAAE,eAAe,EAA8B,8BAA0B","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\n\nexport { TokensApiClient, type TokensApiClientConfig } from './TokensApiClient';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types';\n"]}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAEhB,8BAA0B;AAE3B,OAAO,EACL,eAAe,EAGhB,8BAA0B","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\n\nexport {\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './TokensApiClient';\n\n// Re-export provider types from types module\nexport type { GetProviderFunction, Provider } from '../types';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":";;;AAaA,+CAKmB;AAJjB,0GAAA,eAAe,OAAA;AAEf,0GAAA,eAAe,OAAA;AAGjB,iDAcoB;AAblB,0GAAA,cAAc,OAAA;AACd,yGAAA,aAAa,OAAA;AACb,gHAAA,oBAAoB,OAAA;AACpB,uHAAA,2BAA2B,OAAA;AAC3B,qHAAA,yBAAyB,OAAA;AACzB,oHAAA,wBAAwB,OAAA;AAS1B,2CAAqE;AAA5D,0GAAA,iBAAiB,OAAA;AAAE,gHAAA,uBAAuB,OAAA","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';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n TokensApiClient,\n type TokensApiClientConfig,\n} from './clients';\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';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils';\n"]}
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":";;;AAaA,+CAMmB;AALjB,0GAAA,eAAe,OAAA;AAEf,0GAAA,eAAe,OAAA;AAIjB,iDAcoB;AAblB,0GAAA,cAAc,OAAA;AACd,yGAAA,aAAa,OAAA;AACb,gHAAA,oBAAoB,OAAA;AACpB,uHAAA,2BAA2B,OAAA;AAC3B,qHAAA,yBAAyB,OAAA;AACzB,oHAAA,wBAAwB,OAAA;AAS1B,2CAAqE;AAA5D,0GAAA,iBAAiB,OAAA;AAAE,gHAAA,uBAAuB,OAAA","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';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './clients';\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';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils';\n"]}
@@ -1,5 +1,5 @@
1
1
  export type { Address, AssetFetchEntry, AssetsBalanceState, ChainId, GetProviderFunction, Provider, BalanceOfRequest, BalanceOfResponse, TokenListState, BalanceFetchResult, TokenDetectionResult, } from "./types/index.cjs";
2
- export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, } from "./clients/index.cjs";
2
+ export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from "./clients/index.cjs";
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.cjs";
4
4
  export { divideIntoBatches, reduceInBatchesSerially } from "./utils/index.cjs";
5
5
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","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,0BAAgB;AACjB,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,qBAAqB,GAC3B,4BAAkB;AACnB,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,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB"}
1
+ {"version":3,"file":"index.d.cts","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,0BAAgB;AACjB,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,4BAAkB;AACnB,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,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB"}
@@ -1,5 +1,5 @@
1
1
  export type { Address, AssetFetchEntry, AssetsBalanceState, ChainId, GetProviderFunction, Provider, BalanceOfRequest, BalanceOfResponse, TokenListState, BalanceFetchResult, TokenDetectionResult, } from "./types/index.mjs";
2
- export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, } from "./clients/index.mjs";
2
+ export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, type TokenListQueryClient, } from "./clients/index.mjs";
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.mjs";
4
4
  export { divideIntoBatches, reduceInBatchesSerially } from "./utils/index.mjs";
5
5
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","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,0BAAgB;AACjB,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,qBAAqB,GAC3B,4BAAkB;AACnB,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,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB"}
1
+ {"version":3,"file":"index.d.mts","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,0BAAgB;AACjB,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,4BAAkB;AACnB,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,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,eAAe,EAEf,eAAe,EAEhB,4BAAkB;AACnB,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,EAQzB,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB","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';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n TokensApiClient,\n type TokensApiClientConfig,\n} from './clients';\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';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils';\n"]}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,eAAe,EAEf,eAAe,EAGhB,4BAAkB;AACnB,OAAO,EACL,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,wBAAwB,EAQzB,6BAAmB;AACpB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,0BAAgB","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';\nexport {\n MulticallClient,\n type MulticallClientConfig,\n TokensApiClient,\n type TokensApiClientConfig,\n type TokenListQueryClient,\n} from './clients';\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';\nexport { divideIntoBatches, reduceInBatchesSerially } from './utils';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetector.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../../../logger';\nimport type { MulticallClient } from '../clients';\nimport type { TokensApiClient } from '../clients/TokensApiClient';\nimport type {\n AccountId,\n Address,\n Asset,\n AssetBalance,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenDetectionOptions,\n TokenDetectionResult,\n TokenListEntry,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst log = createModuleLogger(projectLogger, 'TokenDetector');\n\nconst DEFAULT_DETECTION_INTERVAL = 180_000; // 3 minutes\n\nexport type TokenDetectorConfig = {\n /** Function returning whether token detection is enabled (avoids stale value) */\n tokenDetectionEnabled?: () => boolean;\n /** Function returning whether external services are allowed (avoids stale value; default: () => true) */\n useExternalService?: () => boolean;\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 3 minutes) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for TokenDetector - identifies what to poll for.\n */\nexport type DetectionPollingInput = {\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 token detection updates.\n */\nexport type OnDetectionUpdateCallback = (result: TokenDetectionResult) => void;\n\n/**\n * TokenDetector - Detects tokens with non-zero balances via multicall.\n * Fetches the token list from the Tokens API and uses multicall to check balances.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class TokenDetector extends StaticIntervalPollingControllerOnly<DetectionPollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #tokensApiClient: TokensApiClient;\n\n readonly #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n readonly #tokenListCache: Map<ChainId, TokenListEntry[]> = new Map();\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n tokensApiClient: TokensApiClient,\n config?: TokenDetectorConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#tokensApiClient = tokensApiClient;\n this.#config = {\n tokenDetectionEnabled:\n config?.tokenDetectionEnabled ?? ((): boolean => true),\n useExternalService: config?.useExternalService ?? ((): boolean => true),\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_DETECTION_INTERVAL,\n );\n }\n\n /**\n * Set the callback to receive detection updates during polling.\n *\n * @param callback - Function to call with detection results.\n */\n setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void {\n this.#onDetectionUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Detects tokens and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: DetectionPollingInput): Promise<void> {\n try {\n const result = await this.detectTokens(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onDetectionUpdate && result.detectedAssets.length > 0) {\n this.#onDetectionUpdate(result);\n }\n } catch (error) {\n log('Token detection poll failed', { chainId: input.chainId, error });\n }\n }\n\n /**\n * Fetch the list of token addresses to check for the given chain.\n * Calls the Tokens API and caches the result for metadata lookups.\n *\n * @param chainId - Chain ID in hex format.\n * @returns Array of token contract addresses.\n */\n async getTokensToCheck(chainId: ChainId): Promise<Address[]> {\n const tokenList = await this.#fetchAndCacheTokenList(chainId);\n return tokenList.map((entry) => entry.address as Address);\n }\n\n async detectTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: TokenDetectionOptions,\n ): Promise<TokenDetectionResult> {\n const tokenDetectionEnabled =\n options?.tokenDetectionEnabled ?? this.#config.tokenDetectionEnabled();\n const useExternalService =\n options?.useExternalService ?? this.#config.useExternalService();\n if (!tokenDetectionEnabled || !useExternalService) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp: Date.now(),\n };\n }\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const timestamp = Date.now();\n\n const tokensToCheck = await this.getTokensToCheck(chainId);\n\n if (tokensToCheck.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n const balanceRequests: BalanceOfRequest[] = tokensToCheck.map(\n (tokenAddress) => ({\n tokenAddress,\n accountAddress,\n }),\n );\n\n type DetectionAccumulator = {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n DetectionAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\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 DetectionAccumulator,\n chainId,\n accountId,\n timestamp,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n async #fetchAndCacheTokenList(chainId: ChainId): Promise<TokenListEntry[]> {\n try {\n const list = await this.#tokensApiClient.fetchTokenList(chainId);\n this.#tokenListCache.set(chainId, list);\n return list;\n } catch (error) {\n const cached = this.#tokenListCache.get(chainId);\n log('Failed to fetch token list; using stale cache', {\n chainId,\n cachedCount: cached?.length ?? 0,\n error,\n });\n return cached ?? [];\n }\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n ): {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n } {\n const {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n } = 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\n if (balance === '0' || balance === '') {\n zeroBalanceAddresses.push(response.tokenAddress);\n continue;\n }\n\n const tokenMetadata = this.#getTokenMetadata(\n chainId,\n response.tokenAddress,\n );\n\n const asset = this.#createAsset(\n chainId,\n response.tokenAddress,\n tokenMetadata,\n );\n detectedAssets.push(asset);\n\n if (tokenMetadata?.decimals === undefined) {\n continue;\n }\n\n const { decimals } = tokenMetadata;\n const formattedBalance = this.#formatBalance(balance, decimals);\n\n detectedBalances.push({\n assetId: asset.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\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 #getTokenMetadata(\n chainId: ChainId,\n tokenAddress: Address,\n ): TokenListEntry | undefined {\n const list = this.#tokenListCache.get(chainId) ?? [];\n const lowerAddress = tokenAddress.toLowerCase();\n\n const exact = list.find((entry) => entry.address === tokenAddress);\n if (exact) {\n return exact;\n }\n\n return list.find((entry) => entry.address.toLowerCase() === lowerAddress);\n }\n\n #createAsset(\n chainId: ChainId,\n tokenAddress: Address,\n metadata: TokenListEntry | undefined,\n ): Asset {\n const chainIdDecimal = parseInt(chainId, 16);\n\n const assetId =\n `eip155:${chainIdDecimal}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType;\n\n return {\n assetId,\n chainId,\n address: tokenAddress,\n type: 'erc20',\n symbol: metadata?.symbol,\n name: metadata?.name,\n decimals: metadata?.decimals,\n image: metadata?.iconUrl,\n isNative: false,\n aggregators: metadata?.aggregators,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"TokenDetector.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../../../logger';\nimport type { MulticallClient } from '../clients';\nimport type { TokensApiClient } from '../clients/TokensApiClient';\nimport type {\n AccountId,\n Address,\n Asset,\n AssetBalance,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenDetectionOptions,\n TokenDetectionResult,\n TokenListEntry,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst log = createModuleLogger(projectLogger, 'TokenDetector');\n\nconst DEFAULT_DETECTION_INTERVAL = 180_000; // 3 minutes\n\nexport type TokenDetectorConfig = {\n /** Function returning whether token detection is enabled (avoids stale value) */\n tokenDetectionEnabled?: () => boolean;\n /** Function returning whether external services are allowed (avoids stale value; default: () => true) */\n useExternalService?: () => boolean;\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 3 minutes) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for TokenDetector - identifies what to poll for.\n */\nexport type DetectionPollingInput = {\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 token detection updates.\n */\nexport type OnDetectionUpdateCallback = (result: TokenDetectionResult) => void;\n\n/**\n * TokenDetector - Detects tokens with non-zero balances via multicall.\n * Fetches the token list from the Tokens API and uses multicall to check balances.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class TokenDetector extends StaticIntervalPollingControllerOnly<DetectionPollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #tokensApiClient: TokensApiClient;\n\n readonly #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n readonly #tokenListCache: Map<ChainId, TokenListEntry[]> = new Map();\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n tokensApiClient: TokensApiClient,\n config?: TokenDetectorConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#tokensApiClient = tokensApiClient;\n this.#config = {\n tokenDetectionEnabled:\n config?.tokenDetectionEnabled ?? ((): boolean => true),\n useExternalService: config?.useExternalService ?? ((): boolean => true),\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_DETECTION_INTERVAL,\n );\n }\n\n /**\n * Set the callback to receive detection updates during polling.\n *\n * @param callback - Function to call with detection results.\n */\n setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void {\n this.#onDetectionUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Detects tokens and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: DetectionPollingInput): Promise<void> {\n try {\n const result = await this.detectTokens(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onDetectionUpdate && result.detectedAssets.length > 0) {\n this.#onDetectionUpdate(result);\n }\n } catch (error) {\n log('Token detection poll failed', { chainId: input.chainId, error });\n }\n }\n\n /**\n * Fetch the list of token addresses to check for the given chain.\n * Calls the Tokens API and caches the result for metadata lookups.\n *\n * @param chainId - Chain ID in hex format.\n * @returns Array of token contract addresses.\n */\n async getTokensToCheck(chainId: ChainId): Promise<Address[]> {\n const tokenList = await this.#fetchAndCacheTokenList(chainId);\n return tokenList.map((entry) => entry.address as Address);\n }\n\n async detectTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: TokenDetectionOptions,\n ): Promise<TokenDetectionResult> {\n const tokenDetectionEnabled =\n options?.tokenDetectionEnabled ?? this.#config.tokenDetectionEnabled();\n const useExternalService =\n options?.useExternalService ?? this.#config.useExternalService();\n if (!tokenDetectionEnabled || !useExternalService) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp: Date.now(),\n };\n }\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const timestamp = Date.now();\n\n const tokensToCheck = await this.getTokensToCheck(chainId);\n\n if (tokensToCheck.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n const balanceRequests: BalanceOfRequest[] = tokensToCheck.map(\n (tokenAddress) => ({\n tokenAddress,\n accountAddress,\n }),\n );\n\n type DetectionAccumulator = {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n DetectionAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\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 DetectionAccumulator,\n chainId,\n accountId,\n timestamp,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n async #fetchAndCacheTokenList(chainId: ChainId): Promise<TokenListEntry[]> {\n try {\n const list = await this.#tokensApiClient.fetchTokenList(chainId);\n this.#tokenListCache.set(chainId, list);\n return list;\n } catch (error) {\n const cached = this.#tokenListCache.get(chainId);\n log('Failed to fetch token list; using stale cache', {\n chainId,\n cachedCount: cached?.length ?? 0,\n error,\n });\n return cached ?? [];\n }\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n ): {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n } {\n const {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n } = 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\n if (balance === '0' || balance === '') {\n zeroBalanceAddresses.push(response.tokenAddress);\n continue;\n }\n\n const tokenMetadata = this.#getTokenMetadata(\n chainId,\n response.tokenAddress,\n );\n\n const asset = this.#createAsset(\n chainId,\n response.tokenAddress,\n tokenMetadata,\n );\n detectedAssets.push(asset);\n\n if (tokenMetadata?.decimals === undefined) {\n continue;\n }\n\n const { decimals } = tokenMetadata;\n const formattedBalance = this.#formatBalance(balance, decimals);\n detectedBalances.push({\n assetId: asset.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\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 #getTokenMetadata(\n chainId: ChainId,\n tokenAddress: Address,\n ): TokenListEntry | undefined {\n const list = this.#tokenListCache.get(chainId) ?? [];\n const lowerAddress = tokenAddress.toLowerCase();\n\n const exact = list.find((entry) => entry.address === tokenAddress);\n if (exact) {\n return exact;\n }\n\n return list.find((entry) => entry.address.toLowerCase() === lowerAddress);\n }\n\n #createAsset(\n chainId: ChainId,\n tokenAddress: Address,\n metadata: TokenListEntry | undefined,\n ): Asset {\n const chainIdDecimal = parseInt(chainId, 16);\n\n const assetId =\n `eip155:${chainIdDecimal}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType;\n\n return {\n assetId,\n chainId,\n address: tokenAddress,\n type: 'erc20',\n symbol: metadata?.symbol,\n name: metadata?.name,\n decimals: metadata?.decimals,\n image: metadata?.iconUrl,\n isNative: false,\n aggregators: metadata?.aggregators,\n };\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetector.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAmC;AAClE,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EAErB,2BAAiB;AAOlB,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;;gBAY3F,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,MAAM,CAAC,EAAE,mBAAmB;IAkB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/D;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAKtD,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CA2OjC"}
1
+ {"version":3,"file":"TokenDetector.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAmC;AAClE,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EAErB,2BAAiB;AAOlB,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;;gBAY3F,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,MAAM,CAAC,EAAE,mBAAmB;IAkB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/D;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAKtD,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CA0OjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetector.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAmC;AAClE,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EAErB,2BAAiB;AAOlB,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;;gBAY3F,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,MAAM,CAAC,EAAE,mBAAmB;IAkB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/D;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAKtD,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CA2OjC"}
1
+ {"version":3,"file":"TokenDetector.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAmC;AAClE,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,EAErB,2BAAiB;AAOlB,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;;gBAY3F,eAAe,EAAE,eAAe,EAChC,eAAe,EAAE,eAAe,EAChC,MAAM,CAAC,EAAE,mBAAmB;IAkB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/D;;;;;;OAMG;IACG,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAKtD,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CA0OjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetector.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../../../logger';\nimport type { MulticallClient } from '../clients';\nimport type { TokensApiClient } from '../clients/TokensApiClient';\nimport type {\n AccountId,\n Address,\n Asset,\n AssetBalance,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenDetectionOptions,\n TokenDetectionResult,\n TokenListEntry,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst log = createModuleLogger(projectLogger, 'TokenDetector');\n\nconst DEFAULT_DETECTION_INTERVAL = 180_000; // 3 minutes\n\nexport type TokenDetectorConfig = {\n /** Function returning whether token detection is enabled (avoids stale value) */\n tokenDetectionEnabled?: () => boolean;\n /** Function returning whether external services are allowed (avoids stale value; default: () => true) */\n useExternalService?: () => boolean;\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 3 minutes) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for TokenDetector - identifies what to poll for.\n */\nexport type DetectionPollingInput = {\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 token detection updates.\n */\nexport type OnDetectionUpdateCallback = (result: TokenDetectionResult) => void;\n\n/**\n * TokenDetector - Detects tokens with non-zero balances via multicall.\n * Fetches the token list from the Tokens API and uses multicall to check balances.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class TokenDetector extends StaticIntervalPollingControllerOnly<DetectionPollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #tokensApiClient: TokensApiClient;\n\n readonly #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n readonly #tokenListCache: Map<ChainId, TokenListEntry[]> = new Map();\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n tokensApiClient: TokensApiClient,\n config?: TokenDetectorConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#tokensApiClient = tokensApiClient;\n this.#config = {\n tokenDetectionEnabled:\n config?.tokenDetectionEnabled ?? ((): boolean => true),\n useExternalService: config?.useExternalService ?? ((): boolean => true),\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_DETECTION_INTERVAL,\n );\n }\n\n /**\n * Set the callback to receive detection updates during polling.\n *\n * @param callback - Function to call with detection results.\n */\n setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void {\n this.#onDetectionUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Detects tokens and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: DetectionPollingInput): Promise<void> {\n try {\n const result = await this.detectTokens(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onDetectionUpdate && result.detectedAssets.length > 0) {\n this.#onDetectionUpdate(result);\n }\n } catch (error) {\n log('Token detection poll failed', { chainId: input.chainId, error });\n }\n }\n\n /**\n * Fetch the list of token addresses to check for the given chain.\n * Calls the Tokens API and caches the result for metadata lookups.\n *\n * @param chainId - Chain ID in hex format.\n * @returns Array of token contract addresses.\n */\n async getTokensToCheck(chainId: ChainId): Promise<Address[]> {\n const tokenList = await this.#fetchAndCacheTokenList(chainId);\n return tokenList.map((entry) => entry.address as Address);\n }\n\n async detectTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: TokenDetectionOptions,\n ): Promise<TokenDetectionResult> {\n const tokenDetectionEnabled =\n options?.tokenDetectionEnabled ?? this.#config.tokenDetectionEnabled();\n const useExternalService =\n options?.useExternalService ?? this.#config.useExternalService();\n if (!tokenDetectionEnabled || !useExternalService) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp: Date.now(),\n };\n }\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const timestamp = Date.now();\n\n const tokensToCheck = await this.getTokensToCheck(chainId);\n\n if (tokensToCheck.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n const balanceRequests: BalanceOfRequest[] = tokensToCheck.map(\n (tokenAddress) => ({\n tokenAddress,\n accountAddress,\n }),\n );\n\n type DetectionAccumulator = {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n DetectionAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\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 DetectionAccumulator,\n chainId,\n accountId,\n timestamp,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n async #fetchAndCacheTokenList(chainId: ChainId): Promise<TokenListEntry[]> {\n try {\n const list = await this.#tokensApiClient.fetchTokenList(chainId);\n this.#tokenListCache.set(chainId, list);\n return list;\n } catch (error) {\n const cached = this.#tokenListCache.get(chainId);\n log('Failed to fetch token list; using stale cache', {\n chainId,\n cachedCount: cached?.length ?? 0,\n error,\n });\n return cached ?? [];\n }\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n ): {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n } {\n const {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n } = 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\n if (balance === '0' || balance === '') {\n zeroBalanceAddresses.push(response.tokenAddress);\n continue;\n }\n\n const tokenMetadata = this.#getTokenMetadata(\n chainId,\n response.tokenAddress,\n );\n\n const asset = this.#createAsset(\n chainId,\n response.tokenAddress,\n tokenMetadata,\n );\n detectedAssets.push(asset);\n\n if (tokenMetadata?.decimals === undefined) {\n continue;\n }\n\n const { decimals } = tokenMetadata;\n const formattedBalance = this.#formatBalance(balance, decimals);\n\n detectedBalances.push({\n assetId: asset.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\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 #getTokenMetadata(\n chainId: ChainId,\n tokenAddress: Address,\n ): TokenListEntry | undefined {\n const list = this.#tokenListCache.get(chainId) ?? [];\n const lowerAddress = tokenAddress.toLowerCase();\n\n const exact = list.find((entry) => entry.address === tokenAddress);\n if (exact) {\n return exact;\n }\n\n return list.find((entry) => entry.address.toLowerCase() === lowerAddress);\n }\n\n #createAsset(\n chainId: ChainId,\n tokenAddress: Address,\n metadata: TokenListEntry | undefined,\n ): Asset {\n const chainIdDecimal = parseInt(chainId, 16);\n\n const assetId =\n `eip155:${chainIdDecimal}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType;\n\n return {\n assetId,\n chainId,\n address: tokenAddress,\n type: 'erc20',\n symbol: metadata?.symbol,\n name: metadata?.name,\n decimals: metadata?.decimals,\n image: metadata?.iconUrl,\n isNative: false,\n aggregators: metadata?.aggregators,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"TokenDetector.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../../../logger';\nimport type { MulticallClient } from '../clients';\nimport type { TokensApiClient } from '../clients/TokensApiClient';\nimport type {\n AccountId,\n Address,\n Asset,\n AssetBalance,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenDetectionOptions,\n TokenDetectionResult,\n TokenListEntry,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst log = createModuleLogger(projectLogger, 'TokenDetector');\n\nconst DEFAULT_DETECTION_INTERVAL = 180_000; // 3 minutes\n\nexport type TokenDetectorConfig = {\n /** Function returning whether token detection is enabled (avoids stale value) */\n tokenDetectionEnabled?: () => boolean;\n /** Function returning whether external services are allowed (avoids stale value; default: () => true) */\n useExternalService?: () => boolean;\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 3 minutes) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for TokenDetector - identifies what to poll for.\n */\nexport type DetectionPollingInput = {\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 token detection updates.\n */\nexport type OnDetectionUpdateCallback = (result: TokenDetectionResult) => void;\n\n/**\n * TokenDetector - Detects tokens with non-zero balances via multicall.\n * Fetches the token list from the Tokens API and uses multicall to check balances.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class TokenDetector extends StaticIntervalPollingControllerOnly<DetectionPollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #tokensApiClient: TokensApiClient;\n\n readonly #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n readonly #tokenListCache: Map<ChainId, TokenListEntry[]> = new Map();\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n tokensApiClient: TokensApiClient,\n config?: TokenDetectorConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#tokensApiClient = tokensApiClient;\n this.#config = {\n tokenDetectionEnabled:\n config?.tokenDetectionEnabled ?? ((): boolean => true),\n useExternalService: config?.useExternalService ?? ((): boolean => true),\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_DETECTION_INTERVAL,\n );\n }\n\n /**\n * Set the callback to receive detection updates during polling.\n *\n * @param callback - Function to call with detection results.\n */\n setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void {\n this.#onDetectionUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Detects tokens and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: DetectionPollingInput): Promise<void> {\n try {\n const result = await this.detectTokens(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onDetectionUpdate && result.detectedAssets.length > 0) {\n this.#onDetectionUpdate(result);\n }\n } catch (error) {\n log('Token detection poll failed', { chainId: input.chainId, error });\n }\n }\n\n /**\n * Fetch the list of token addresses to check for the given chain.\n * Calls the Tokens API and caches the result for metadata lookups.\n *\n * @param chainId - Chain ID in hex format.\n * @returns Array of token contract addresses.\n */\n async getTokensToCheck(chainId: ChainId): Promise<Address[]> {\n const tokenList = await this.#fetchAndCacheTokenList(chainId);\n return tokenList.map((entry) => entry.address as Address);\n }\n\n async detectTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: TokenDetectionOptions,\n ): Promise<TokenDetectionResult> {\n const tokenDetectionEnabled =\n options?.tokenDetectionEnabled ?? this.#config.tokenDetectionEnabled();\n const useExternalService =\n options?.useExternalService ?? this.#config.useExternalService();\n if (!tokenDetectionEnabled || !useExternalService) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp: Date.now(),\n };\n }\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const timestamp = Date.now();\n\n const tokensToCheck = await this.getTokensToCheck(chainId);\n\n if (tokensToCheck.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n const balanceRequests: BalanceOfRequest[] = tokensToCheck.map(\n (tokenAddress) => ({\n tokenAddress,\n accountAddress,\n }),\n );\n\n type DetectionAccumulator = {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n DetectionAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n detectedAssets: [],\n detectedBalances: [],\n zeroBalanceAddresses: [],\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 DetectionAccumulator,\n chainId,\n accountId,\n timestamp,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n async #fetchAndCacheTokenList(chainId: ChainId): Promise<TokenListEntry[]> {\n try {\n const list = await this.#tokensApiClient.fetchTokenList(chainId);\n this.#tokenListCache.set(chainId, list);\n return list;\n } catch (error) {\n const cached = this.#tokenListCache.get(chainId);\n log('Failed to fetch token list; using stale cache', {\n chainId,\n cachedCount: cached?.length ?? 0,\n error,\n });\n return cached ?? [];\n }\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n ): {\n detectedAssets: Asset[];\n detectedBalances: AssetBalance[];\n zeroBalanceAddresses: Address[];\n failedAddresses: Address[];\n } {\n const {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n } = 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\n if (balance === '0' || balance === '') {\n zeroBalanceAddresses.push(response.tokenAddress);\n continue;\n }\n\n const tokenMetadata = this.#getTokenMetadata(\n chainId,\n response.tokenAddress,\n );\n\n const asset = this.#createAsset(\n chainId,\n response.tokenAddress,\n tokenMetadata,\n );\n detectedAssets.push(asset);\n\n if (tokenMetadata?.decimals === undefined) {\n continue;\n }\n\n const { decimals } = tokenMetadata;\n const formattedBalance = this.#formatBalance(balance, decimals);\n detectedBalances.push({\n assetId: asset.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return {\n detectedAssets,\n detectedBalances,\n zeroBalanceAddresses,\n failedAddresses,\n };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\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 #getTokenMetadata(\n chainId: ChainId,\n tokenAddress: Address,\n ): TokenListEntry | undefined {\n const list = this.#tokenListCache.get(chainId) ?? [];\n const lowerAddress = tokenAddress.toLowerCase();\n\n const exact = list.find((entry) => entry.address === tokenAddress);\n if (exact) {\n return exact;\n }\n\n return list.find((entry) => entry.address.toLowerCase() === lowerAddress);\n }\n\n #createAsset(\n chainId: ChainId,\n tokenAddress: Address,\n metadata: TokenListEntry | undefined,\n ): Asset {\n const chainIdDecimal = parseInt(chainId, 16);\n\n const assetId =\n `eip155:${chainIdDecimal}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType;\n\n return {\n assetId,\n chainId,\n address: tokenAddress,\n type: 'erc20',\n symbol: metadata?.symbol,\n name: metadata?.name,\n decimals: metadata?.decimals,\n image: metadata?.iconUrl,\n isNative: false,\n aggregators: metadata?.aggregators,\n };\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/assets-controller",
3
- "version": "6.4.0-preview-8f8f607",
3
+ "version": "6.4.0-preview-2b316ced3",
4
4
  "description": "Tracks assets balances/prices and handles token detection across all digital assets",
5
5
  "keywords": [
6
6
  "Ethereum",