@metamask-previews/assets-controller 4.0.0-preview-e19d3725e → 4.0.0-preview-c5f25f9
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.
- package/CHANGELOG.md +11 -0
- package/dist/AssetsController.cjs +28 -14
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts +1 -2
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts +1 -2
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +28 -14
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/PriceDataSource.cjs +63 -38
- package/dist/data-sources/PriceDataSource.cjs.map +1 -1
- package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
- package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
- package/dist/data-sources/PriceDataSource.mjs +63 -38
- package/dist/data-sources/PriceDataSource.mjs.map +1 -1
- package/dist/data-sources/RpcDataSource.cjs +8 -63
- package/dist/data-sources/RpcDataSource.cjs.map +1 -1
- package/dist/data-sources/RpcDataSource.d.cts +1 -2
- package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
- package/dist/data-sources/RpcDataSource.d.mts +1 -2
- package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
- package/dist/data-sources/RpcDataSource.mjs +10 -65
- package/dist/data-sources/RpcDataSource.mjs.map +1 -1
- package/dist/data-sources/TokenDataSource.cjs +61 -30
- package/dist/data-sources/TokenDataSource.cjs.map +1 -1
- package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
- package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
- package/dist/data-sources/TokenDataSource.mjs +63 -32
- package/dist/data-sources/TokenDataSource.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.cjs +67 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.cts +23 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.mts +23 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.mjs +63 -0
- package/dist/data-sources/evm-rpc-services/clients/TokensApiClient.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.cjs +3 -1
- package/dist/data-sources/evm-rpc-services/clients/index.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/clients/index.d.cts +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/clients/index.d.mts +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/clients/index.mjs +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/index.cjs +2 -1
- package/dist/data-sources/evm-rpc-services/index.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/index.d.cts +1 -1
- package/dist/data-sources/evm-rpc-services/index.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/index.d.mts +1 -1
- package/dist/data-sources/evm-rpc-services/index.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/index.mjs +1 -1
- package/dist/data-sources/evm-rpc-services/index.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +27 -48
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +12 -9
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts +12 -9
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +27 -48
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.cts +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.mts +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokensApiClient.cjs","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,MAAa,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;AAlDD,0CAkDC","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"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ChainId, TokenListEntry } from "../types/index.cjs";
|
|
2
|
+
export type TokensApiClientConfig = {
|
|
3
|
+
/** Fetch function (defaults to globalThis.fetch). */
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Client for the MetaMask Tokens API.
|
|
8
|
+
* Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).
|
|
9
|
+
*/
|
|
10
|
+
export declare class TokensApiClient {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(config?: TokensApiClientConfig);
|
|
13
|
+
/**
|
|
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.
|
|
16
|
+
*
|
|
17
|
+
* @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
|
|
18
|
+
* @returns Array of token list entries with address and metadata.
|
|
19
|
+
* @throws If the API responds with a non-2xx status.
|
|
20
|
+
*/
|
|
21
|
+
fetchTokenList(hexChainId: ChainId): Promise<TokenListEntry[]>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=TokensApiClient.d.cts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ChainId, TokenListEntry } from "../types/index.mjs";
|
|
2
|
+
export type TokensApiClientConfig = {
|
|
3
|
+
/** Fetch function (defaults to globalThis.fetch). */
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Client for the MetaMask Tokens API.
|
|
8
|
+
* Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).
|
|
9
|
+
*/
|
|
10
|
+
export declare class TokensApiClient {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(config?: TokensApiClientConfig);
|
|
13
|
+
/**
|
|
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.
|
|
16
|
+
*
|
|
17
|
+
* @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
|
|
18
|
+
* @returns Array of token list entries with address and metadata.
|
|
19
|
+
* @throws If the API responds with a non-2xx status.
|
|
20
|
+
*/
|
|
21
|
+
fetchTokenList(hexChainId: ChainId): Promise<TokenListEntry[]>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=TokensApiClient.d.mts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
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
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
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;
|
|
16
|
+
/**
|
|
17
|
+
* Client for the MetaMask Tokens API.
|
|
18
|
+
* Fetches the top ERC-20 tokens for a given chain (occurrenceFloor=3, first=25).
|
|
19
|
+
*/
|
|
20
|
+
export class TokensApiClient {
|
|
21
|
+
constructor(config) {
|
|
22
|
+
_TokensApiClient_fetch.set(this, void 0);
|
|
23
|
+
__classPrivateFieldSet(this, _TokensApiClient_fetch, config?.fetch ?? globalThis.fetch.bind(globalThis), "f");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
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.
|
|
28
|
+
*
|
|
29
|
+
* @param hexChainId - Chain ID in hex format (e.g. `'0x1'` for Ethereum mainnet).
|
|
30
|
+
* @returns Array of token list entries with address and metadata.
|
|
31
|
+
* @throws If the API responds with a non-2xx status.
|
|
32
|
+
*/
|
|
33
|
+
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}`);
|
|
46
|
+
}
|
|
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
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
_TokensApiClient_fetch = new WeakMap();
|
|
63
|
+
//# sourceMappingURL=TokensApiClient.mjs.map
|
|
@@ -0,0 +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,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MulticallClient = exports.encodeAggregate3 = exports.decodeAggregate3Response = void 0;
|
|
3
|
+
exports.TokensApiClient = exports.MulticallClient = exports.encodeAggregate3 = exports.decodeAggregate3Response = void 0;
|
|
4
4
|
var MulticallClient_1 = require("./MulticallClient.cjs");
|
|
5
5
|
Object.defineProperty(exports, "decodeAggregate3Response", { enumerable: true, get: function () { return MulticallClient_1.decodeAggregate3Response; } });
|
|
6
6
|
Object.defineProperty(exports, "encodeAggregate3", { enumerable: true, get: function () { return MulticallClient_1.encodeAggregate3; } });
|
|
7
7
|
Object.defineProperty(exports, "MulticallClient", { enumerable: true, get: function () { return MulticallClient_1.MulticallClient; } });
|
|
8
|
+
var TokensApiClient_1 = require("./TokensApiClient.cjs");
|
|
9
|
+
Object.defineProperty(exports, "TokensApiClient", { enumerable: true, get: function () { return TokensApiClient_1.TokensApiClient; } });
|
|
8
10
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -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","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\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,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,3 +1,4 @@
|
|
|
1
1
|
export { decodeAggregate3Response, encodeAggregate3, MulticallClient, type MulticallClientConfig, } from "./MulticallClient.cjs";
|
|
2
|
+
export { TokensApiClient, type TokensApiClientConfig } from "./TokensApiClient.cjs";
|
|
2
3
|
export type { GetProviderFunction, Provider } from "../types/index.cjs";
|
|
3
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;
|
|
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,3 +1,4 @@
|
|
|
1
1
|
export { decodeAggregate3Response, encodeAggregate3, MulticallClient, type MulticallClientConfig, } from "./MulticallClient.mjs";
|
|
2
|
+
export { TokensApiClient, type TokensApiClientConfig } from "./TokensApiClient.mjs";
|
|
2
3
|
export type { GetProviderFunction, Provider } from "../types/index.mjs";
|
|
3
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;
|
|
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 +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","sourcesContent":["export {\n decodeAggregate3Response,\n encodeAggregate3,\n MulticallClient,\n type MulticallClientConfig,\n} from './MulticallClient';\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,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,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.isStakingContractAssetId = exports.getStakingContractAddress = exports.getSupportedStakingChainIds = exports.StakedBalanceFetcher = exports.TokenDetector = exports.BalanceFetcher = exports.MulticallClient = void 0;
|
|
3
|
+
exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.isStakingContractAssetId = exports.getStakingContractAddress = exports.getSupportedStakingChainIds = exports.StakedBalanceFetcher = exports.TokenDetector = exports.BalanceFetcher = exports.TokensApiClient = exports.MulticallClient = void 0;
|
|
4
4
|
var clients_1 = require("./clients/index.cjs");
|
|
5
5
|
Object.defineProperty(exports, "MulticallClient", { enumerable: true, get: function () { return clients_1.MulticallClient; } });
|
|
6
|
+
Object.defineProperty(exports, "TokensApiClient", { enumerable: true, get: function () { return clients_1.TokensApiClient; } });
|
|
6
7
|
var services_1 = require("./services/index.cjs");
|
|
7
8
|
Object.defineProperty(exports, "BalanceFetcher", { enumerable: true, get: function () { return services_1.BalanceFetcher; } });
|
|
8
9
|
Object.defineProperty(exports, "TokenDetector", { enumerable: true, get: function () { return services_1.TokenDetector; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":";;;AAaA,+
|
|
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,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 } from "./clients/index.cjs";
|
|
2
|
+
export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, } 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,
|
|
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,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 } from "./clients/index.mjs";
|
|
2
|
+
export { MulticallClient, type MulticallClientConfig, TokensApiClient, type TokensApiClientConfig, } 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,
|
|
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,4 +1,4 @@
|
|
|
1
|
-
export { MulticallClient } from "./clients/index.mjs";
|
|
1
|
+
export { MulticallClient, TokensApiClient } from "./clients/index.mjs";
|
|
2
2
|
export { BalanceFetcher, TokenDetector, StakedBalanceFetcher, getSupportedStakingChainIds, getStakingContractAddress, isStakingContractAssetId } from "./services/index.mjs";
|
|
3
3
|
export { divideIntoBatches, reduceInBatchesSerially } from "./utils/index.mjs";
|
|
4
4
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/data-sources/evm-rpc-services/index.ts"],"names":[],"mappings":"AAaA,OAAO,
|
|
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"]}
|
|
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _TokenDetector_instances, _TokenDetector_multicallClient,
|
|
13
|
+
var _TokenDetector_instances, _TokenDetector_multicallClient, _TokenDetector_tokensApiClient, _TokenDetector_config, _TokenDetector_tokenListCache, _TokenDetector_onDetectionUpdate, _TokenDetector_fetchAndCacheTokenList, _TokenDetector_processBalanceResponses, _TokenDetector_formatBalance, _TokenDetector_getTokenMetadata, _TokenDetector_createAsset;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.TokenDetector = void 0;
|
|
16
16
|
const polling_controller_1 = require("@metamask/polling-controller");
|
|
@@ -18,25 +18,26 @@ const utils_1 = require("../utils/index.cjs");
|
|
|
18
18
|
const DEFAULT_DETECTION_INTERVAL = 180000; // 3 minutes
|
|
19
19
|
/**
|
|
20
20
|
* TokenDetector - Detects tokens with non-zero balances via multicall.
|
|
21
|
+
* Fetches the token list from the Tokens API and uses multicall to check balances.
|
|
21
22
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
22
23
|
*/
|
|
23
24
|
class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingControllerOnly)() {
|
|
24
|
-
constructor(multicallClient,
|
|
25
|
+
constructor(multicallClient, tokensApiClient, config) {
|
|
25
26
|
super();
|
|
26
27
|
_TokenDetector_instances.add(this);
|
|
27
28
|
_TokenDetector_multicallClient.set(this, void 0);
|
|
28
|
-
|
|
29
|
+
_TokenDetector_tokensApiClient.set(this, void 0);
|
|
29
30
|
_TokenDetector_config.set(this, void 0);
|
|
31
|
+
_TokenDetector_tokenListCache.set(this, new Map());
|
|
30
32
|
_TokenDetector_onDetectionUpdate.set(this, void 0);
|
|
31
33
|
__classPrivateFieldSet(this, _TokenDetector_multicallClient, multicallClient, "f");
|
|
32
|
-
__classPrivateFieldSet(this,
|
|
34
|
+
__classPrivateFieldSet(this, _TokenDetector_tokensApiClient, tokensApiClient, "f");
|
|
33
35
|
__classPrivateFieldSet(this, _TokenDetector_config, {
|
|
34
36
|
tokenDetectionEnabled: config?.tokenDetectionEnabled ?? (() => true),
|
|
35
37
|
useExternalService: config?.useExternalService ?? (() => true),
|
|
36
38
|
defaultBatchSize: config?.defaultBatchSize ?? 300,
|
|
37
39
|
defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
|
|
38
40
|
}, "f");
|
|
39
|
-
// Set the polling interval
|
|
40
41
|
this.setIntervalLength(config?.pollingInterval ?? DEFAULT_DETECTION_INTERVAL);
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
@@ -54,35 +55,21 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
54
55
|
* @param input - The polling input.
|
|
55
56
|
*/
|
|
56
57
|
async _executePoll(input) {
|
|
57
|
-
// Check if token list is available for this chain
|
|
58
|
-
const tokensToCheck = this.getTokensToCheck(input.chainId);
|
|
59
|
-
if (tokensToCheck.length === 0) {
|
|
60
|
-
// No tokens in list for chain, will retry on next poll
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
58
|
const result = await this.detectTokens(input.chainId, input.accountId, input.accountAddress);
|
|
64
59
|
if (__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f") && result.detectedAssets.length > 0) {
|
|
65
60
|
__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f").call(this, result);
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const normalizedChainId = `0x${parseInt(chainId, 16).toString(16)}`;
|
|
79
|
-
chainCacheEntry = tokenListState.tokensChainsCache[normalizedChainId];
|
|
80
|
-
}
|
|
81
|
-
const chainTokenList = chainCacheEntry?.data;
|
|
82
|
-
if (!chainTokenList) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
return Object.keys(chainTokenList);
|
|
63
|
+
/**
|
|
64
|
+
* Fetch the list of token addresses to check for the given chain.
|
|
65
|
+
* Calls the Tokens API and caches the result for metadata lookups.
|
|
66
|
+
*
|
|
67
|
+
* @param chainId - Chain ID in hex format.
|
|
68
|
+
* @returns Array of token contract addresses.
|
|
69
|
+
*/
|
|
70
|
+
async getTokensToCheck(chainId) {
|
|
71
|
+
const tokenList = await __classPrivateFieldGet(this, _TokenDetector_instances, "m", _TokenDetector_fetchAndCacheTokenList).call(this, chainId);
|
|
72
|
+
return tokenList.map((entry) => entry.address);
|
|
86
73
|
}
|
|
87
74
|
async detectTokens(chainId, accountId, accountAddress, options) {
|
|
88
75
|
const tokenDetectionEnabled = options?.tokenDetectionEnabled ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").tokenDetectionEnabled();
|
|
@@ -101,7 +88,7 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
101
88
|
}
|
|
102
89
|
const batchSize = options?.batchSize ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").defaultBatchSize;
|
|
103
90
|
const timestamp = Date.now();
|
|
104
|
-
const tokensToCheck = this.getTokensToCheck(chainId);
|
|
91
|
+
const tokensToCheck = await this.getTokensToCheck(chainId);
|
|
105
92
|
if (tokensToCheck.length === 0) {
|
|
106
93
|
return {
|
|
107
94
|
chainId,
|
|
@@ -142,7 +129,11 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
142
129
|
}
|
|
143
130
|
}
|
|
144
131
|
exports.TokenDetector = TokenDetector;
|
|
145
|
-
_TokenDetector_multicallClient = new WeakMap(),
|
|
132
|
+
_TokenDetector_multicallClient = new WeakMap(), _TokenDetector_tokensApiClient = new WeakMap(), _TokenDetector_config = new WeakMap(), _TokenDetector_tokenListCache = new WeakMap(), _TokenDetector_onDetectionUpdate = new WeakMap(), _TokenDetector_instances = new WeakSet(), _TokenDetector_fetchAndCacheTokenList = async function _TokenDetector_fetchAndCacheTokenList(chainId) {
|
|
133
|
+
const list = await __classPrivateFieldGet(this, _TokenDetector_tokensApiClient, "f").fetchTokenList(chainId);
|
|
134
|
+
__classPrivateFieldGet(this, _TokenDetector_tokenListCache, "f").set(chainId, list);
|
|
135
|
+
return list;
|
|
136
|
+
}, _TokenDetector_processBalanceResponses = function _TokenDetector_processBalanceResponses(responses, accumulator, chainId, accountId, timestamp) {
|
|
146
137
|
const { detectedAssets, detectedBalances, zeroBalanceAddresses, failedAddresses, } = accumulator;
|
|
147
138
|
for (const response of responses) {
|
|
148
139
|
if (!response.success) {
|
|
@@ -195,25 +186,13 @@ _TokenDetector_multicallClient = new WeakMap(), _TokenDetector_messenger = new W
|
|
|
195
186
|
return rawBalance;
|
|
196
187
|
}
|
|
197
188
|
}, _TokenDetector_getTokenMetadata = function _TokenDetector_getTokenMetadata(chainId, tokenAddress) {
|
|
198
|
-
const
|
|
199
|
-
if (!tokenListState?.tokensChainsCache) {
|
|
200
|
-
return undefined;
|
|
201
|
-
}
|
|
202
|
-
const chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
203
|
-
const chainTokenList = chainCacheEntry?.data;
|
|
204
|
-
if (!chainTokenList) {
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
207
|
-
if (chainTokenList[tokenAddress]) {
|
|
208
|
-
return chainTokenList[tokenAddress];
|
|
209
|
-
}
|
|
189
|
+
const list = __classPrivateFieldGet(this, _TokenDetector_tokenListCache, "f").get(chainId) ?? [];
|
|
210
190
|
const lowerAddress = tokenAddress.toLowerCase();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
191
|
+
const exact = list.find((entry) => entry.address === tokenAddress);
|
|
192
|
+
if (exact) {
|
|
193
|
+
return exact;
|
|
215
194
|
}
|
|
216
|
-
return
|
|
195
|
+
return list.find((entry) => entry.address.toLowerCase() === lowerAddress);
|
|
217
196
|
}, _TokenDetector_createAsset = function _TokenDetector_createAsset(chainId, tokenAddress, metadata) {
|
|
218
197
|
const chainIdDecimal = parseInt(chainId, 16);
|
|
219
198
|
const assetId = `eip155:${chainIdDecimal}/erc20:${tokenAddress.toLowerCase()}`;
|
|
@@ -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 type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n Asset,\n AssetBalance,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenDetectionOptions,\n TokenDetectionResult,\n TokenListEntry,\n TokenListState,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_DETECTION_INTERVAL = 180_000; // 3 minutes\n\n/**\n * Minimal messenger interface for TokenDetector.\n */\nexport type TokenDetectorMessenger = {\n call: (action: 'TokenListController:getState') => TokenListState;\n};\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 * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class TokenDetector extends StaticIntervalPollingControllerOnly<DetectionPollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: TokenDetectorMessenger;\n\n readonly #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: TokenDetectorMessenger,\n config?: TokenDetectorConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\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 // Set the polling interval\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 // Check if token list is available for this chain\n const tokensToCheck = this.getTokensToCheck(input.chainId);\n\n if (tokensToCheck.length === 0) {\n // No tokens in list for chain, will retry on next poll\n return;\n }\n\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 }\n\n getTokensToCheck(chainId: ChainId): Address[] {\n const tokenListState = this.#messenger.call('TokenListController:getState');\n\n // Defensive check for tokensChainsCache\n if (!tokenListState?.tokensChainsCache) {\n return [];\n }\n\n // Try direct lookup first\n let chainCacheEntry = tokenListState.tokensChainsCache[chainId];\n\n // If not found, try normalizing the chain ID (e.g., 0x0a -> 0xa)\n if (!chainCacheEntry) {\n const normalizedChainId: ChainId = `0x${parseInt(chainId, 16).toString(\n 16,\n )}`;\n chainCacheEntry = tokenListState.tokensChainsCache[normalizedChainId];\n }\n\n const chainTokenList = chainCacheEntry?.data;\n\n if (!chainTokenList) {\n return [];\n }\n\n return Object.keys(chainTokenList) 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 = 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 #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 tokenListState = this.#messenger.call('TokenListController:getState');\n if (!tokenListState?.tokensChainsCache) {\n return undefined;\n }\n\n const chainCacheEntry = tokenListState.tokensChainsCache[chainId];\n const chainTokenList = chainCacheEntry?.data;\n if (!chainTokenList) {\n return undefined;\n }\n\n if (chainTokenList[tokenAddress]) {\n return chainTokenList[tokenAddress];\n }\n\n const lowerAddress = tokenAddress.toLowerCase();\n for (const [address, metadata] of Object.entries(chainTokenList)) {\n if (address.toLowerCase() === lowerAddress) {\n return metadata;\n }\n }\n\n return undefined;\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 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 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 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 }\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 const list = await this.#tokensApiClient.fetchTokenList(chainId);\n this.#tokenListCache.set(chainId, list);\n return list;\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,11 +1,6 @@
|
|
|
1
1
|
import type { MulticallClient } from "../clients/index.cjs";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
* Minimal messenger interface for TokenDetector.
|
|
5
|
-
*/
|
|
6
|
-
export type TokenDetectorMessenger = {
|
|
7
|
-
call: (action: 'TokenListController:getState') => TokenListState;
|
|
8
|
-
};
|
|
2
|
+
import type { TokensApiClient } from "../clients/TokensApiClient.cjs";
|
|
3
|
+
import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult } from "../types/index.cjs";
|
|
9
4
|
export type TokenDetectorConfig = {
|
|
10
5
|
/** Function returning whether token detection is enabled (avoids stale value) */
|
|
11
6
|
tokenDetectionEnabled?: () => boolean;
|
|
@@ -50,11 +45,12 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
50
45
|
};
|
|
51
46
|
/**
|
|
52
47
|
* TokenDetector - Detects tokens with non-zero balances via multicall.
|
|
48
|
+
* Fetches the token list from the Tokens API and uses multicall to check balances.
|
|
53
49
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
54
50
|
*/
|
|
55
51
|
export declare class TokenDetector extends TokenDetector_base {
|
|
56
52
|
#private;
|
|
57
|
-
constructor(multicallClient: MulticallClient,
|
|
53
|
+
constructor(multicallClient: MulticallClient, tokensApiClient: TokensApiClient, config?: TokenDetectorConfig);
|
|
58
54
|
/**
|
|
59
55
|
* Set the callback to receive detection updates during polling.
|
|
60
56
|
*
|
|
@@ -68,7 +64,14 @@ export declare class TokenDetector extends TokenDetector_base {
|
|
|
68
64
|
* @param input - The polling input.
|
|
69
65
|
*/
|
|
70
66
|
_executePoll(input: DetectionPollingInput): Promise<void>;
|
|
71
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Fetch the list of token addresses to check for the given chain.
|
|
69
|
+
* Calls the Tokens API and caches the result for metadata lookups.
|
|
70
|
+
*
|
|
71
|
+
* @param chainId - Chain ID in hex format.
|
|
72
|
+
* @returns Array of token contract addresses.
|
|
73
|
+
*/
|
|
74
|
+
getTokensToCheck(chainId: ChainId): Promise<Address[]>;
|
|
72
75
|
detectTokens(chainId: ChainId, accountId: AccountId, accountAddress: Address, options?: TokenDetectionOptions): Promise<TokenDetectionResult>;
|
|
73
76
|
}
|
|
74
77
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenDetector.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"TokenDetector.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAGA,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;AAKlB,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;IAY/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;CAiOjC"}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { MulticallClient } from "../clients/index.mjs";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
* Minimal messenger interface for TokenDetector.
|
|
5
|
-
*/
|
|
6
|
-
export type TokenDetectorMessenger = {
|
|
7
|
-
call: (action: 'TokenListController:getState') => TokenListState;
|
|
8
|
-
};
|
|
2
|
+
import type { TokensApiClient } from "../clients/TokensApiClient.mjs";
|
|
3
|
+
import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult } from "../types/index.mjs";
|
|
9
4
|
export type TokenDetectorConfig = {
|
|
10
5
|
/** Function returning whether token detection is enabled (avoids stale value) */
|
|
11
6
|
tokenDetectionEnabled?: () => boolean;
|
|
@@ -50,11 +45,12 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
50
45
|
};
|
|
51
46
|
/**
|
|
52
47
|
* TokenDetector - Detects tokens with non-zero balances via multicall.
|
|
48
|
+
* Fetches the token list from the Tokens API and uses multicall to check balances.
|
|
53
49
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
54
50
|
*/
|
|
55
51
|
export declare class TokenDetector extends TokenDetector_base {
|
|
56
52
|
#private;
|
|
57
|
-
constructor(multicallClient: MulticallClient,
|
|
53
|
+
constructor(multicallClient: MulticallClient, tokensApiClient: TokensApiClient, config?: TokenDetectorConfig);
|
|
58
54
|
/**
|
|
59
55
|
* Set the callback to receive detection updates during polling.
|
|
60
56
|
*
|
|
@@ -68,7 +64,14 @@ export declare class TokenDetector extends TokenDetector_base {
|
|
|
68
64
|
* @param input - The polling input.
|
|
69
65
|
*/
|
|
70
66
|
_executePoll(input: DetectionPollingInput): Promise<void>;
|
|
71
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Fetch the list of token addresses to check for the given chain.
|
|
69
|
+
* Calls the Tokens API and caches the result for metadata lookups.
|
|
70
|
+
*
|
|
71
|
+
* @param chainId - Chain ID in hex format.
|
|
72
|
+
* @returns Array of token contract addresses.
|
|
73
|
+
*/
|
|
74
|
+
getTokensToCheck(chainId: ChainId): Promise<Address[]>;
|
|
72
75
|
detectTokens(chainId: ChainId, accountId: AccountId, accountAddress: Address, options?: TokenDetectionOptions): Promise<TokenDetectionResult>;
|
|
73
76
|
}
|
|
74
77
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenDetector.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAKP,OAAO,EACP,qBAAqB,EACrB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"TokenDetector.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/TokenDetector.ts"],"names":[],"mappings":"AAGA,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;AAKlB,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;IAY/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;CAiOjC"}
|