@metamask-previews/assets-controller 0.0.0-preview-52f4a2ca → 0.0.0-preview-2d06693a
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 +0 -3
- package/dist/AssetsController.cjs +19 -7
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +19 -7
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/RpcDataSource.cjs +42 -232
- package/dist/data-sources/RpcDataSource.cjs.map +1 -1
- package/dist/data-sources/RpcDataSource.d.cts +1 -5
- package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
- package/dist/data-sources/RpcDataSource.d.mts +1 -5
- package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
- package/dist/data-sources/RpcDataSource.mjs +42 -229
- package/dist/data-sources/RpcDataSource.mjs.map +1 -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.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +27 -32
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts +5 -12
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts +5 -12
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +27 -32
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +12 -23
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +3 -8
- 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 +3 -8
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +12 -23
- 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 +2 -2
- 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 +2 -2
- 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/dist/data-sources/evm-rpc-services/types/index.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/index.d.cts +1 -1
- package/dist/data-sources/evm-rpc-services/types/index.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/index.d.mts +1 -1
- package/dist/data-sources/evm-rpc-services/types/index.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/index.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.d.cts +24 -9
- package/dist/data-sources/evm-rpc-services/types/state.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.d.mts +24 -9
- package/dist/data-sources/evm-rpc-services/types/state.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { MulticallClient } from "../clients/index.mjs";
|
|
2
|
-
import type { AccountId, Address,
|
|
3
|
-
/**
|
|
4
|
-
* Minimal messenger interface for BalanceFetcher.
|
|
5
|
-
*/
|
|
6
|
-
export type BalanceFetcherMessenger = {
|
|
7
|
-
call: (action: 'AssetsController:getState') => AssetsBalanceState;
|
|
8
|
-
};
|
|
2
|
+
import type { AccountId, Address, BalanceFetchOptions, BalanceFetchResult, ChainId, TokenFetchInfo, UserTokensState } from "../types/index.mjs";
|
|
9
3
|
export type BalanceFetcherConfig = {
|
|
10
4
|
defaultBatchSize?: number;
|
|
11
5
|
defaultTimeoutMs?: number;
|
|
@@ -40,9 +34,7 @@ declare const BalanceFetcher_base: (abstract new (...args: any[]) => {
|
|
|
40
34
|
_executePoll(input: BalancePollingInput): Promise<void>;
|
|
41
35
|
startPolling(input: BalancePollingInput): string;
|
|
42
36
|
stopAllPolling(): void;
|
|
43
|
-
stopPollingByPollingToken(pollingToken: string): void; /**
|
|
44
|
-
* Polling input for BalanceFetcher - identifies what to poll for.
|
|
45
|
-
*/
|
|
37
|
+
stopPollingByPollingToken(pollingToken: string): void; /** Account ID */
|
|
46
38
|
onPollingComplete(input: BalancePollingInput, callback: (input: BalancePollingInput) => void): void;
|
|
47
39
|
}) & {
|
|
48
40
|
new (): {};
|
|
@@ -53,13 +45,14 @@ declare const BalanceFetcher_base: (abstract new (...args: any[]) => {
|
|
|
53
45
|
*/
|
|
54
46
|
export declare class BalanceFetcher extends BalanceFetcher_base {
|
|
55
47
|
#private;
|
|
56
|
-
constructor(multicallClient: MulticallClient,
|
|
48
|
+
constructor(multicallClient: MulticallClient, config?: BalanceFetcherConfig);
|
|
57
49
|
/**
|
|
58
50
|
* Set the callback to receive balance updates during polling.
|
|
59
51
|
*
|
|
60
52
|
* @param callback - Function to call with balance results.
|
|
61
53
|
*/
|
|
62
54
|
setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void;
|
|
55
|
+
setUserTokensStateGetter(getUserTokensState: () => UserTokensState): void;
|
|
63
56
|
/**
|
|
64
57
|
* Execute a poll cycle (required by base class).
|
|
65
58
|
* Fetches balances and calls the update callback.
|
|
@@ -67,7 +60,7 @@ export declare class BalanceFetcher extends BalanceFetcher_base {
|
|
|
67
60
|
* @param input - The polling input.
|
|
68
61
|
*/
|
|
69
62
|
_executePoll(input: BalancePollingInput): Promise<void>;
|
|
70
|
-
getTokensToFetch(chainId: ChainId,
|
|
63
|
+
getTokensToFetch(chainId: ChainId, accountAddress: Address): TokenFetchInfo[];
|
|
71
64
|
fetchBalances(chainId: ChainId, accountId: AccountId, accountAddress: Address, options?: BalanceFetchOptions): Promise<BalanceFetchResult>;
|
|
72
65
|
fetchBalancesForTokens(chainId: ChainId, accountId: AccountId, accountAddress: Address, tokenAddresses: Address[], options?: BalanceFetchOptions, tokenInfos?: TokenFetchInfo[]): Promise<BalanceFetchResult>;
|
|
73
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,mBAAmB,EACnB,kBAAkB,EAGlB,OAAO,EACP,cAAc,EAEd,eAAe,EAChB,2BAAiB;AAQlB,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;;;;;;;;;;;;;2DATzE,iBAAiB;;;;;AAWnB;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAShF,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,oBAAoB;IAa3E;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D,wBAAwB,CAAC,kBAAkB,EAAE,MAAM,eAAe,GAAG,IAAI;IAIzE;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D,gBAAgB,CACd,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,OAAO,GACtB,cAAc,EAAE;IAmCb,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAcxB,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,cAAc,EAAE,GAC5B,OAAO,CAAC,kBAAkB,CAAC;CAwJ/B"}
|
|
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _BalanceFetcher_instances, _BalanceFetcher_multicallClient,
|
|
12
|
+
var _BalanceFetcher_instances, _BalanceFetcher_multicallClient, _BalanceFetcher_config, _BalanceFetcher_getUserTokensState, _BalanceFetcher_onBalanceUpdate, _BalanceFetcher_processBalanceResponses, _BalanceFetcher_formatBalance;
|
|
13
13
|
import { StaticIntervalPollingControllerOnly } from "@metamask/polling-controller";
|
|
14
14
|
import { reduceInBatchesSerially } from "../utils/index.mjs";
|
|
15
15
|
const DEFAULT_BALANCE_INTERVAL = 30000; // 30 seconds
|
|
@@ -19,15 +19,14 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
|
19
19
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
20
20
|
*/
|
|
21
21
|
export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
22
|
-
constructor(multicallClient,
|
|
22
|
+
constructor(multicallClient, config) {
|
|
23
23
|
super();
|
|
24
24
|
_BalanceFetcher_instances.add(this);
|
|
25
25
|
_BalanceFetcher_multicallClient.set(this, void 0);
|
|
26
|
-
_BalanceFetcher_messenger.set(this, void 0);
|
|
27
26
|
_BalanceFetcher_config.set(this, void 0);
|
|
27
|
+
_BalanceFetcher_getUserTokensState.set(this, void 0);
|
|
28
28
|
_BalanceFetcher_onBalanceUpdate.set(this, void 0);
|
|
29
29
|
__classPrivateFieldSet(this, _BalanceFetcher_multicallClient, multicallClient, "f");
|
|
30
|
-
__classPrivateFieldSet(this, _BalanceFetcher_messenger, messenger, "f");
|
|
31
30
|
__classPrivateFieldSet(this, _BalanceFetcher_config, {
|
|
32
31
|
defaultBatchSize: config?.defaultBatchSize ?? 300,
|
|
33
32
|
defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
|
|
@@ -44,6 +43,9 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
44
43
|
setOnBalanceUpdate(callback) {
|
|
45
44
|
__classPrivateFieldSet(this, _BalanceFetcher_onBalanceUpdate, callback, "f");
|
|
46
45
|
}
|
|
46
|
+
setUserTokensStateGetter(getUserTokensState) {
|
|
47
|
+
__classPrivateFieldSet(this, _BalanceFetcher_getUserTokensState, getUserTokensState, "f");
|
|
48
|
+
}
|
|
47
49
|
/**
|
|
48
50
|
* Execute a poll cycle (required by base class).
|
|
49
51
|
* Fetches balances and calls the update callback.
|
|
@@ -56,41 +58,34 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
56
58
|
__classPrivateFieldGet(this, _BalanceFetcher_onBalanceUpdate, "f").call(this, result);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
|
-
getTokensToFetch(chainId,
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
const accountBalances = state.assetsBalance[accountId];
|
|
65
|
-
if (!accountBalances) {
|
|
61
|
+
getTokensToFetch(chainId, accountAddress) {
|
|
62
|
+
const userTokensState = __classPrivateFieldGet(this, _BalanceFetcher_getUserTokensState, "f")?.call(this);
|
|
63
|
+
if (!userTokensState) {
|
|
66
64
|
return [];
|
|
67
65
|
}
|
|
68
|
-
// Convert hex chainId to decimal for CAIP-2 matching
|
|
69
|
-
const chainIdDecimal = parseInt(chainId, 16);
|
|
70
|
-
const caipChainPrefix = `eip155:${chainIdDecimal}/`;
|
|
71
66
|
const tokenMap = new Map();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
tokenMap.set(lowerAddress, {
|
|
81
|
-
address: tokenAddress,
|
|
82
|
-
// Decimals will be fetched from metadata or defaulted
|
|
83
|
-
decimals: 18,
|
|
84
|
-
symbol: '',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
67
|
+
const addToken = (token) => {
|
|
68
|
+
const lowerAddress = token.address.toLowerCase();
|
|
69
|
+
if (!tokenMap.has(lowerAddress)) {
|
|
70
|
+
tokenMap.set(lowerAddress, {
|
|
71
|
+
address: token.address,
|
|
72
|
+
decimals: token.decimals,
|
|
73
|
+
symbol: token.symbol,
|
|
74
|
+
});
|
|
88
75
|
}
|
|
76
|
+
};
|
|
77
|
+
const importedTokens = userTokensState.allTokens[chainId]?.[accountAddress] ?? [];
|
|
78
|
+
for (const token of importedTokens) {
|
|
79
|
+
addToken(token);
|
|
80
|
+
}
|
|
81
|
+
const detectedTokens = userTokensState.allDetectedTokens[chainId]?.[accountAddress] ?? [];
|
|
82
|
+
for (const token of detectedTokens) {
|
|
83
|
+
addToken(token);
|
|
89
84
|
}
|
|
90
85
|
return Array.from(tokenMap.values());
|
|
91
86
|
}
|
|
92
87
|
async fetchBalances(chainId, accountId, accountAddress, options) {
|
|
93
|
-
const tokens = this.getTokensToFetch(chainId,
|
|
88
|
+
const tokens = this.getTokensToFetch(chainId, accountAddress);
|
|
94
89
|
const tokenAddresses = tokens.map((token) => token.address);
|
|
95
90
|
return this.fetchBalancesForTokens(chainId, accountId, accountAddress, tokenAddresses, options, tokens);
|
|
96
91
|
}
|
|
@@ -148,7 +143,7 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
148
143
|
};
|
|
149
144
|
}
|
|
150
145
|
}
|
|
151
|
-
_BalanceFetcher_multicallClient = new WeakMap(),
|
|
146
|
+
_BalanceFetcher_multicallClient = new WeakMap(), _BalanceFetcher_config = new WeakMap(), _BalanceFetcher_getUserTokensState = new WeakMap(), _BalanceFetcher_onBalanceUpdate = new WeakMap(), _BalanceFetcher_instances = new WeakSet(), _BalanceFetcher_processBalanceResponses = function _BalanceFetcher_processBalanceResponses(responses, accumulator, chainId, accountId, timestamp, tokenInfoMap) {
|
|
152
147
|
const { balances, failedAddresses } = accumulator;
|
|
153
148
|
for (const response of responses) {
|
|
154
149
|
if (!response.success) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetsBalanceState,\n BalanceFetchOptions,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenFetchInfo,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\nconst ZERO_ADDRESS: Address =\n '0x0000000000000000000000000000000000000000' as Address;\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n includeNativeByDefault?: boolean;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (result: BalanceFetchResult) => void;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n includeNativeByDefault: config?.includeNativeByDefault ?? true,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n this.#onBalanceUpdate(result);\n }\n }\n\n getTokensToFetch(chainId: ChainId, accountId: AccountId): TokenFetchInfo[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n if (!state?.assetsBalance) {\n return [];\n }\n\n const accountBalances = state.assetsBalance[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n const chainIdDecimal = parseInt(chainId, 16);\n const caipChainPrefix = `eip155:${chainIdDecimal}/`;\n\n const tokenMap = new Map<string, TokenFetchInfo>();\n\n for (const assetId of Object.keys(accountBalances)) {\n // Only process ERC20 tokens on the current chain\n if (assetId.startsWith(caipChainPrefix) && assetId.includes('/erc20:')) {\n // Parse token address from CAIP-19: eip155:1/erc20:0x...\n const tokenAddress = assetId.split('/erc20:')[1] as Address;\n if (tokenAddress) {\n const lowerAddress = tokenAddress.toLowerCase();\n if (!tokenMap.has(lowerAddress)) {\n tokenMap.set(lowerAddress, {\n address: tokenAddress,\n // Decimals will be fetched from metadata or defaulted\n decimals: 18,\n symbol: '',\n });\n }\n }\n }\n }\n\n return Array.from(tokenMap.values());\n }\n\n async fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: BalanceFetchOptions,\n ): Promise<BalanceFetchResult> {\n const tokens = this.getTokensToFetch(chainId, accountId);\n const tokenAddresses = tokens.map((token) => token.address);\n\n return this.fetchBalancesForTokens(\n chainId,\n accountId,\n accountAddress,\n tokenAddresses,\n options,\n tokens,\n );\n }\n\n async fetchBalancesForTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n tokenAddresses: Address[],\n options?: BalanceFetchOptions,\n tokenInfos?: TokenFetchInfo[],\n ): Promise<BalanceFetchResult> {\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const includeNative =\n options?.includeNative ?? this.#config.includeNativeByDefault;\n const timestamp = Date.now();\n\n const tokenInfoMap = new Map<string, TokenFetchInfo>();\n if (tokenInfos) {\n for (const info of tokenInfos) {\n tokenInfoMap.set(info.address.toLowerCase(), info);\n }\n }\n\n const balanceRequests: BalanceOfRequest[] = [];\n\n if (includeNative) {\n balanceRequests.push({\n tokenAddress: ZERO_ADDRESS,\n accountAddress,\n });\n }\n\n for (const tokenAddress of tokenAddresses) {\n balanceRequests.push({\n tokenAddress,\n accountAddress,\n });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n tokenInfoMap,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n tokenInfoMap: Map<string, TokenFetchInfo>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const balance = response.balance ?? '0';\n const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());\n const decimals = tokenInfo?.decimals ?? 18;\n const formattedBalance = this.#formatBalance(balance, decimals);\n\n const chainIdDecimal = parseInt(chainId, 16);\n const isNative =\n response.tokenAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase();\n const assetId: CaipAssetType = isNative\n ? (`eip155:${chainIdDecimal}/slip44:60` as CaipAssetType)\n : (`eip155:${chainIdDecimal}/erc20:${response.tokenAddress.toLowerCase()}` as CaipAssetType);\n\n balances.push({\n assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n BalanceFetchOptions,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n ChainId,\n TokenFetchInfo,\n UserToken,\n UserTokensState,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\nconst ZERO_ADDRESS: Address =\n '0x0000000000000000000000000000000000000000' as Address;\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n includeNativeByDefault?: boolean;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (result: BalanceFetchResult) => void;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #getUserTokensState: (() => UserTokensState) | undefined;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(multicallClient: MulticallClient, config?: BalanceFetcherConfig) {\n super();\n this.#multicallClient = multicallClient;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n includeNativeByDefault: config?.includeNativeByDefault ?? true,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n setUserTokensStateGetter(getUserTokensState: () => UserTokensState): void {\n this.#getUserTokensState = getUserTokensState;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n this.#onBalanceUpdate(result);\n }\n }\n\n getTokensToFetch(\n chainId: ChainId,\n accountAddress: Address,\n ): TokenFetchInfo[] {\n const userTokensState = this.#getUserTokensState?.();\n\n if (!userTokensState) {\n return [];\n }\n\n const tokenMap = new Map<string, TokenFetchInfo>();\n\n const addToken = (token: UserToken): void => {\n const lowerAddress = token.address.toLowerCase();\n if (!tokenMap.has(lowerAddress)) {\n tokenMap.set(lowerAddress, {\n address: token.address,\n decimals: token.decimals,\n symbol: token.symbol,\n });\n }\n };\n\n const importedTokens =\n userTokensState.allTokens[chainId]?.[accountAddress] ?? [];\n for (const token of importedTokens) {\n addToken(token);\n }\n\n const detectedTokens =\n userTokensState.allDetectedTokens[chainId]?.[accountAddress] ?? [];\n for (const token of detectedTokens) {\n addToken(token);\n }\n\n return Array.from(tokenMap.values());\n }\n\n async fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n options?: BalanceFetchOptions,\n ): Promise<BalanceFetchResult> {\n const tokens = this.getTokensToFetch(chainId, accountAddress);\n const tokenAddresses = tokens.map((token) => token.address);\n\n return this.fetchBalancesForTokens(\n chainId,\n accountId,\n accountAddress,\n tokenAddresses,\n options,\n tokens,\n );\n }\n\n async fetchBalancesForTokens(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n tokenAddresses: Address[],\n options?: BalanceFetchOptions,\n tokenInfos?: TokenFetchInfo[],\n ): Promise<BalanceFetchResult> {\n const batchSize = options?.batchSize ?? this.#config.defaultBatchSize;\n const includeNative =\n options?.includeNative ?? this.#config.includeNativeByDefault;\n const timestamp = Date.now();\n\n const tokenInfoMap = new Map<string, TokenFetchInfo>();\n if (tokenInfos) {\n for (const info of tokenInfos) {\n tokenInfoMap.set(info.address.toLowerCase(), info);\n }\n }\n\n const balanceRequests: BalanceOfRequest[] = [];\n\n if (includeNative) {\n balanceRequests.push({\n tokenAddress: ZERO_ADDRESS,\n accountAddress,\n });\n }\n\n for (const tokenAddress of tokenAddresses) {\n balanceRequests.push({\n tokenAddress,\n accountAddress,\n });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n tokenInfoMap,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n tokenInfoMap: Map<string, TokenFetchInfo>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const balance = response.balance ?? '0';\n const tokenInfo = tokenInfoMap.get(response.tokenAddress.toLowerCase());\n const decimals = tokenInfo?.decimals ?? 18;\n const formattedBalance = this.#formatBalance(balance, decimals);\n\n const chainIdDecimal = parseInt(chainId, 16);\n const isNative =\n response.tokenAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase();\n const assetId: CaipAssetType = isNative\n ? (`eip155:${chainIdDecimal}/slip44:60` as CaipAssetType)\n : (`eip155:${chainIdDecimal}/erc20:${response.tokenAddress.toLowerCase()}` as CaipAssetType);\n\n balances.push({\n assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n decimals,\n timestamp,\n });\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
@@ -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_config, _TokenDetector_getTokenListState, _TokenDetector_onDetectionUpdate, _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");
|
|
@@ -21,15 +21,14 @@ const DEFAULT_DETECTION_INTERVAL = 180000; // 3 minutes
|
|
|
21
21
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
22
22
|
*/
|
|
23
23
|
class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingControllerOnly)() {
|
|
24
|
-
constructor(multicallClient,
|
|
24
|
+
constructor(multicallClient, config) {
|
|
25
25
|
super();
|
|
26
26
|
_TokenDetector_instances.add(this);
|
|
27
27
|
_TokenDetector_multicallClient.set(this, void 0);
|
|
28
|
-
_TokenDetector_messenger.set(this, void 0);
|
|
29
28
|
_TokenDetector_config.set(this, void 0);
|
|
29
|
+
_TokenDetector_getTokenListState.set(this, void 0);
|
|
30
30
|
_TokenDetector_onDetectionUpdate.set(this, void 0);
|
|
31
31
|
__classPrivateFieldSet(this, _TokenDetector_multicallClient, multicallClient, "f");
|
|
32
|
-
__classPrivateFieldSet(this, _TokenDetector_messenger, messenger, "f");
|
|
33
32
|
__classPrivateFieldSet(this, _TokenDetector_config, {
|
|
34
33
|
defaultBatchSize: config?.defaultBatchSize ?? 300,
|
|
35
34
|
defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
|
|
@@ -45,6 +44,9 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
45
44
|
setOnDetectionUpdate(callback) {
|
|
46
45
|
__classPrivateFieldSet(this, _TokenDetector_onDetectionUpdate, callback, "f");
|
|
47
46
|
}
|
|
47
|
+
setTokenListStateGetter(getTokenListState) {
|
|
48
|
+
__classPrivateFieldSet(this, _TokenDetector_getTokenListState, getTokenListState, "f");
|
|
49
|
+
}
|
|
48
50
|
/**
|
|
49
51
|
* Execute a poll cycle (required by base class).
|
|
50
52
|
* Detects tokens and calls the update callback.
|
|
@@ -52,30 +54,17 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
52
54
|
* @param input - The polling input.
|
|
53
55
|
*/
|
|
54
56
|
async _executePoll(input) {
|
|
55
|
-
// Check if token list is available for this chain
|
|
56
|
-
const tokensToCheck = this.getTokensToCheck(input.chainId);
|
|
57
|
-
if (tokensToCheck.length === 0) {
|
|
58
|
-
// No tokens in list for chain, will retry on next poll
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
57
|
const result = await this.detectTokens(input.chainId, input.accountId, input.accountAddress);
|
|
62
58
|
if (__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f") && result.detectedAssets.length > 0) {
|
|
63
59
|
__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f").call(this, result);
|
|
64
60
|
}
|
|
65
61
|
}
|
|
66
62
|
getTokensToCheck(chainId) {
|
|
67
|
-
const tokenListState = __classPrivateFieldGet(this,
|
|
68
|
-
|
|
69
|
-
if (!tokenListState?.tokensChainsCache) {
|
|
63
|
+
const tokenListState = __classPrivateFieldGet(this, _TokenDetector_getTokenListState, "f")?.call(this);
|
|
64
|
+
if (!tokenListState) {
|
|
70
65
|
return [];
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
let chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
74
|
-
// If not found, try normalizing the chain ID (e.g., 0x0a -> 0xa)
|
|
75
|
-
if (!chainCacheEntry) {
|
|
76
|
-
const normalizedChainId = `0x${parseInt(chainId, 16).toString(16)}`;
|
|
77
|
-
chainCacheEntry = tokenListState.tokensChainsCache[normalizedChainId];
|
|
78
|
-
}
|
|
67
|
+
const chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
79
68
|
const chainTokenList = chainCacheEntry?.data;
|
|
80
69
|
if (!chainTokenList) {
|
|
81
70
|
return [];
|
|
@@ -126,7 +115,7 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
|
|
|
126
115
|
}
|
|
127
116
|
}
|
|
128
117
|
exports.TokenDetector = TokenDetector;
|
|
129
|
-
_TokenDetector_multicallClient = new WeakMap(),
|
|
118
|
+
_TokenDetector_multicallClient = new WeakMap(), _TokenDetector_config = new WeakMap(), _TokenDetector_getTokenListState = new WeakMap(), _TokenDetector_onDetectionUpdate = new WeakMap(), _TokenDetector_instances = new WeakSet(), _TokenDetector_processBalanceResponses = function _TokenDetector_processBalanceResponses(responses, accumulator, chainId, accountId, timestamp) {
|
|
130
119
|
const { detectedAssets, detectedBalances, zeroBalanceAddresses, failedAddresses, } = accumulator;
|
|
131
120
|
for (const response of responses) {
|
|
132
121
|
if (!response.success) {
|
|
@@ -176,8 +165,8 @@ _TokenDetector_multicallClient = new WeakMap(), _TokenDetector_messenger = new W
|
|
|
176
165
|
return rawBalance;
|
|
177
166
|
}
|
|
178
167
|
}, _TokenDetector_getTokenMetadata = function _TokenDetector_getTokenMetadata(chainId, tokenAddress) {
|
|
179
|
-
const tokenListState = __classPrivateFieldGet(this,
|
|
180
|
-
if (!tokenListState
|
|
168
|
+
const tokenListState = __classPrivateFieldGet(this, _TokenDetector_getTokenListState, "f")?.call(this);
|
|
169
|
+
if (!tokenListState) {
|
|
181
170
|
return undefined;
|
|
182
171
|
}
|
|
183
172
|
const chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
@@ -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 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 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 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 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 const decimals = tokenMetadata?.decimals ?? 18;\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 {\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\nexport type TokenDetectorConfig = {\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 #config: Required<Omit<TokenDetectorConfig, 'pollingInterval'>>;\n\n #getTokenListState: (() => TokenListState) | undefined;\n\n #onDetectionUpdate: OnDetectionUpdateCallback | undefined;\n\n constructor(multicallClient: MulticallClient, config?: TokenDetectorConfig) {\n super();\n this.#multicallClient = multicallClient;\n this.#config = {\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 setTokenListStateGetter(getTokenListState: () => TokenListState): void {\n this.#getTokenListState = getTokenListState;\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 getTokensToCheck(chainId: ChainId): Address[] {\n const tokenListState = this.#getTokenListState?.();\n\n if (!tokenListState) {\n return [];\n }\n\n const chainCacheEntry = tokenListState.tokensChainsCache[chainId];\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 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 const decimals = tokenMetadata?.decimals ?? 18;\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.#getTokenListState?.();\n if (!tokenListState) {\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,11 +1,5 @@
|
|
|
1
1
|
import type { MulticallClient } from "../clients/index.cjs";
|
|
2
2
|
import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult, TokenListState } from "../types/index.cjs";
|
|
3
|
-
/**
|
|
4
|
-
* Minimal messenger interface for TokenDetector.
|
|
5
|
-
*/
|
|
6
|
-
export type TokenDetectorMessenger = {
|
|
7
|
-
call: (action: 'TokenListController:getState') => TokenListState;
|
|
8
|
-
};
|
|
9
3
|
export type TokenDetectorConfig = {
|
|
10
4
|
defaultBatchSize?: number;
|
|
11
5
|
defaultTimeoutMs?: number;
|
|
@@ -39,7 +33,7 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
39
33
|
_executePoll(input: DetectionPollingInput): Promise<void>;
|
|
40
34
|
startPolling(input: DetectionPollingInput): string;
|
|
41
35
|
stopAllPolling(): void;
|
|
42
|
-
stopPollingByPollingToken(pollingToken: string): void;
|
|
36
|
+
stopPollingByPollingToken(pollingToken: string): void;
|
|
43
37
|
onPollingComplete(input: DetectionPollingInput, callback: (input: DetectionPollingInput) => void): void;
|
|
44
38
|
}) & {
|
|
45
39
|
new (): {};
|
|
@@ -50,13 +44,14 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
50
44
|
*/
|
|
51
45
|
export declare class TokenDetector extends TokenDetector_base {
|
|
52
46
|
#private;
|
|
53
|
-
constructor(multicallClient: MulticallClient,
|
|
47
|
+
constructor(multicallClient: MulticallClient, config?: TokenDetectorConfig);
|
|
54
48
|
/**
|
|
55
49
|
* Set the callback to receive detection updates during polling.
|
|
56
50
|
*
|
|
57
51
|
* @param callback - Function to call with detection results.
|
|
58
52
|
*/
|
|
59
53
|
setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void;
|
|
54
|
+
setTokenListStateGetter(getTokenListState: () => TokenListState): void;
|
|
60
55
|
/**
|
|
61
56
|
* Execute a poll cycle (required by base class).
|
|
62
57
|
* Detects tokens and calls the update callback.
|
|
@@ -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,EAEpB,cAAc,EACf,2BAAiB;AAKlB
|
|
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,EAEpB,cAAc,EACf,2BAAiB;AAKlB,MAAM,MAAM,mBAAmB,GAAG;IAChC,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;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBASjF,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,mBAAmB;IAc1E;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,cAAc,GAAG,IAAI;IAItE;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IAiBvC,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CAqNjC"}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { MulticallClient } from "../clients/index.mjs";
|
|
2
2
|
import type { AccountId, Address, ChainId, TokenDetectionOptions, TokenDetectionResult, TokenListState } from "../types/index.mjs";
|
|
3
|
-
/**
|
|
4
|
-
* Minimal messenger interface for TokenDetector.
|
|
5
|
-
*/
|
|
6
|
-
export type TokenDetectorMessenger = {
|
|
7
|
-
call: (action: 'TokenListController:getState') => TokenListState;
|
|
8
|
-
};
|
|
9
3
|
export type TokenDetectorConfig = {
|
|
10
4
|
defaultBatchSize?: number;
|
|
11
5
|
defaultTimeoutMs?: number;
|
|
@@ -39,7 +33,7 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
39
33
|
_executePoll(input: DetectionPollingInput): Promise<void>;
|
|
40
34
|
startPolling(input: DetectionPollingInput): string;
|
|
41
35
|
stopAllPolling(): void;
|
|
42
|
-
stopPollingByPollingToken(pollingToken: string): void;
|
|
36
|
+
stopPollingByPollingToken(pollingToken: string): void;
|
|
43
37
|
onPollingComplete(input: DetectionPollingInput, callback: (input: DetectionPollingInput) => void): void;
|
|
44
38
|
}) & {
|
|
45
39
|
new (): {};
|
|
@@ -50,13 +44,14 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
|
|
|
50
44
|
*/
|
|
51
45
|
export declare class TokenDetector extends TokenDetector_base {
|
|
52
46
|
#private;
|
|
53
|
-
constructor(multicallClient: MulticallClient,
|
|
47
|
+
constructor(multicallClient: MulticallClient, config?: TokenDetectorConfig);
|
|
54
48
|
/**
|
|
55
49
|
* Set the callback to receive detection updates during polling.
|
|
56
50
|
*
|
|
57
51
|
* @param callback - Function to call with detection results.
|
|
58
52
|
*/
|
|
59
53
|
setOnDetectionUpdate(callback: OnDetectionUpdateCallback): void;
|
|
54
|
+
setTokenListStateGetter(getTokenListState: () => TokenListState): void;
|
|
60
55
|
/**
|
|
61
56
|
* Execute a poll cycle (required by base class).
|
|
62
57
|
* Detects tokens and calls the update callback.
|
|
@@ -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,EAEpB,cAAc,EACf,2BAAiB;AAKlB
|
|
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,EAEpB,cAAc,EACf,2BAAiB;AAKlB,MAAM,MAAM,mBAAmB,GAAG;IAChC,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;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBASjF,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,mBAAmB;IAc1E;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,cAAc,GAAG,IAAI;IAItE;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IAiBvC,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CAqNjC"}
|
|
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _TokenDetector_instances, _TokenDetector_multicallClient,
|
|
12
|
+
var _TokenDetector_instances, _TokenDetector_multicallClient, _TokenDetector_config, _TokenDetector_getTokenListState, _TokenDetector_onDetectionUpdate, _TokenDetector_processBalanceResponses, _TokenDetector_formatBalance, _TokenDetector_getTokenMetadata, _TokenDetector_createAsset;
|
|
13
13
|
import { StaticIntervalPollingControllerOnly } from "@metamask/polling-controller";
|
|
14
14
|
import { reduceInBatchesSerially } from "../utils/index.mjs";
|
|
15
15
|
const DEFAULT_DETECTION_INTERVAL = 180000; // 3 minutes
|
|
@@ -18,15 +18,14 @@ const DEFAULT_DETECTION_INTERVAL = 180000; // 3 minutes
|
|
|
18
18
|
* Extends StaticIntervalPollingControllerOnly for built-in polling support.
|
|
19
19
|
*/
|
|
20
20
|
export class TokenDetector extends StaticIntervalPollingControllerOnly() {
|
|
21
|
-
constructor(multicallClient,
|
|
21
|
+
constructor(multicallClient, config) {
|
|
22
22
|
super();
|
|
23
23
|
_TokenDetector_instances.add(this);
|
|
24
24
|
_TokenDetector_multicallClient.set(this, void 0);
|
|
25
|
-
_TokenDetector_messenger.set(this, void 0);
|
|
26
25
|
_TokenDetector_config.set(this, void 0);
|
|
26
|
+
_TokenDetector_getTokenListState.set(this, void 0);
|
|
27
27
|
_TokenDetector_onDetectionUpdate.set(this, void 0);
|
|
28
28
|
__classPrivateFieldSet(this, _TokenDetector_multicallClient, multicallClient, "f");
|
|
29
|
-
__classPrivateFieldSet(this, _TokenDetector_messenger, messenger, "f");
|
|
30
29
|
__classPrivateFieldSet(this, _TokenDetector_config, {
|
|
31
30
|
defaultBatchSize: config?.defaultBatchSize ?? 300,
|
|
32
31
|
defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
|
|
@@ -42,6 +41,9 @@ export class TokenDetector extends StaticIntervalPollingControllerOnly() {
|
|
|
42
41
|
setOnDetectionUpdate(callback) {
|
|
43
42
|
__classPrivateFieldSet(this, _TokenDetector_onDetectionUpdate, callback, "f");
|
|
44
43
|
}
|
|
44
|
+
setTokenListStateGetter(getTokenListState) {
|
|
45
|
+
__classPrivateFieldSet(this, _TokenDetector_getTokenListState, getTokenListState, "f");
|
|
46
|
+
}
|
|
45
47
|
/**
|
|
46
48
|
* Execute a poll cycle (required by base class).
|
|
47
49
|
* Detects tokens and calls the update callback.
|
|
@@ -49,30 +51,17 @@ export class TokenDetector extends StaticIntervalPollingControllerOnly() {
|
|
|
49
51
|
* @param input - The polling input.
|
|
50
52
|
*/
|
|
51
53
|
async _executePoll(input) {
|
|
52
|
-
// Check if token list is available for this chain
|
|
53
|
-
const tokensToCheck = this.getTokensToCheck(input.chainId);
|
|
54
|
-
if (tokensToCheck.length === 0) {
|
|
55
|
-
// No tokens in list for chain, will retry on next poll
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
54
|
const result = await this.detectTokens(input.chainId, input.accountId, input.accountAddress);
|
|
59
55
|
if (__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f") && result.detectedAssets.length > 0) {
|
|
60
56
|
__classPrivateFieldGet(this, _TokenDetector_onDetectionUpdate, "f").call(this, result);
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
getTokensToCheck(chainId) {
|
|
64
|
-
const tokenListState = __classPrivateFieldGet(this,
|
|
65
|
-
|
|
66
|
-
if (!tokenListState?.tokensChainsCache) {
|
|
60
|
+
const tokenListState = __classPrivateFieldGet(this, _TokenDetector_getTokenListState, "f")?.call(this);
|
|
61
|
+
if (!tokenListState) {
|
|
67
62
|
return [];
|
|
68
63
|
}
|
|
69
|
-
|
|
70
|
-
let chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
71
|
-
// If not found, try normalizing the chain ID (e.g., 0x0a -> 0xa)
|
|
72
|
-
if (!chainCacheEntry) {
|
|
73
|
-
const normalizedChainId = `0x${parseInt(chainId, 16).toString(16)}`;
|
|
74
|
-
chainCacheEntry = tokenListState.tokensChainsCache[normalizedChainId];
|
|
75
|
-
}
|
|
64
|
+
const chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|
|
76
65
|
const chainTokenList = chainCacheEntry?.data;
|
|
77
66
|
if (!chainTokenList) {
|
|
78
67
|
return [];
|
|
@@ -122,7 +111,7 @@ export class TokenDetector extends StaticIntervalPollingControllerOnly() {
|
|
|
122
111
|
};
|
|
123
112
|
}
|
|
124
113
|
}
|
|
125
|
-
_TokenDetector_multicallClient = new WeakMap(),
|
|
114
|
+
_TokenDetector_multicallClient = new WeakMap(), _TokenDetector_config = new WeakMap(), _TokenDetector_getTokenListState = new WeakMap(), _TokenDetector_onDetectionUpdate = new WeakMap(), _TokenDetector_instances = new WeakSet(), _TokenDetector_processBalanceResponses = function _TokenDetector_processBalanceResponses(responses, accumulator, chainId, accountId, timestamp) {
|
|
126
115
|
const { detectedAssets, detectedBalances, zeroBalanceAddresses, failedAddresses, } = accumulator;
|
|
127
116
|
for (const response of responses) {
|
|
128
117
|
if (!response.success) {
|
|
@@ -172,8 +161,8 @@ _TokenDetector_multicallClient = new WeakMap(), _TokenDetector_messenger = new W
|
|
|
172
161
|
return rawBalance;
|
|
173
162
|
}
|
|
174
163
|
}, _TokenDetector_getTokenMetadata = function _TokenDetector_getTokenMetadata(chainId, tokenAddress) {
|
|
175
|
-
const tokenListState = __classPrivateFieldGet(this,
|
|
176
|
-
if (!tokenListState
|
|
164
|
+
const tokenListState = __classPrivateFieldGet(this, _TokenDetector_getTokenListState, "f")?.call(this);
|
|
165
|
+
if (!tokenListState) {
|
|
177
166
|
return undefined;
|
|
178
167
|
}
|
|
179
168
|
const chainCacheEntry = tokenListState.tokensChainsCache[chainId];
|