@metamask/assets-controller 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/dist/AssetsController.cjs +175 -64
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +68 -9
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +68 -9
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +176 -65
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.cjs +70 -15
  11. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.cts +22 -3
  13. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.d.mts +22 -3
  15. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  16. package/dist/data-sources/AccountsApiDataSource.mjs +66 -12
  17. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.cjs +7 -5
  19. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  21. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.mjs +7 -5
  23. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  24. package/dist/data-sources/PriceDataSource.cjs +17 -4
  25. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  26. package/dist/data-sources/PriceDataSource.d.cts +8 -4
  27. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  28. package/dist/data-sources/PriceDataSource.d.mts +8 -4
  29. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  30. package/dist/data-sources/PriceDataSource.mjs +17 -4
  31. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  32. package/dist/data-sources/RpcDataSource.cjs +122 -42
  33. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  34. package/dist/data-sources/RpcDataSource.d.cts +16 -62
  35. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  36. package/dist/data-sources/RpcDataSource.d.mts +16 -62
  37. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  38. package/dist/data-sources/RpcDataSource.mjs +123 -43
  39. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  40. package/dist/data-sources/SnapDataSource.cjs +22 -10
  41. package/dist/data-sources/SnapDataSource.cjs.map +1 -1
  42. package/dist/data-sources/SnapDataSource.d.cts +2 -1
  43. package/dist/data-sources/SnapDataSource.d.cts.map +1 -1
  44. package/dist/data-sources/SnapDataSource.d.mts +2 -1
  45. package/dist/data-sources/SnapDataSource.d.mts.map +1 -1
  46. package/dist/data-sources/SnapDataSource.mjs +22 -10
  47. package/dist/data-sources/SnapDataSource.mjs.map +1 -1
  48. package/dist/data-sources/StakedBalanceDataSource.cjs +609 -0
  49. package/dist/data-sources/StakedBalanceDataSource.cjs.map +1 -0
  50. package/dist/data-sources/StakedBalanceDataSource.d.cts +72 -0
  51. package/dist/data-sources/StakedBalanceDataSource.d.cts.map +1 -0
  52. package/dist/data-sources/StakedBalanceDataSource.d.mts +72 -0
  53. package/dist/data-sources/StakedBalanceDataSource.d.mts.map +1 -0
  54. package/dist/data-sources/StakedBalanceDataSource.mjs +605 -0
  55. package/dist/data-sources/StakedBalanceDataSource.mjs.map +1 -0
  56. package/dist/data-sources/TokenDataSource.cjs +12 -5
  57. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  58. package/dist/data-sources/TokenDataSource.d.cts +1 -0
  59. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  60. package/dist/data-sources/TokenDataSource.d.mts +1 -0
  61. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  62. package/dist/data-sources/TokenDataSource.mjs +12 -5
  63. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  64. package/dist/data-sources/evm-rpc-services/index.cjs +5 -1
  65. package/dist/data-sources/evm-rpc-services/index.cjs.map +1 -1
  66. package/dist/data-sources/evm-rpc-services/index.d.cts +1 -1
  67. package/dist/data-sources/evm-rpc-services/index.d.cts.map +1 -1
  68. package/dist/data-sources/evm-rpc-services/index.d.mts +1 -1
  69. package/dist/data-sources/evm-rpc-services/index.d.mts.map +1 -1
  70. package/dist/data-sources/evm-rpc-services/index.mjs +1 -1
  71. package/dist/data-sources/evm-rpc-services/index.mjs.map +1 -1
  72. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.cjs +132 -0
  73. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.cjs.map +1 -0
  74. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.cts +78 -0
  75. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.cts.map +1 -0
  76. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.mts +78 -0
  77. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.mts.map +1 -0
  78. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.mjs +126 -0
  79. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.mjs.map +1 -0
  80. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +16 -0
  81. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  82. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +5 -1
  83. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  84. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts +5 -1
  85. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  86. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +16 -0
  87. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  88. package/dist/data-sources/evm-rpc-services/services/index.cjs +6 -1
  89. package/dist/data-sources/evm-rpc-services/services/index.cjs.map +1 -1
  90. package/dist/data-sources/evm-rpc-services/services/index.d.cts +1 -0
  91. package/dist/data-sources/evm-rpc-services/services/index.d.cts.map +1 -1
  92. package/dist/data-sources/evm-rpc-services/services/index.d.mts +1 -0
  93. package/dist/data-sources/evm-rpc-services/services/index.d.mts.map +1 -1
  94. package/dist/data-sources/evm-rpc-services/services/index.mjs +1 -0
  95. package/dist/data-sources/evm-rpc-services/services/index.mjs.map +1 -1
  96. package/dist/data-sources/evm-rpc-services/types/services.cjs.map +1 -1
  97. package/dist/data-sources/evm-rpc-services/types/services.d.cts +4 -0
  98. package/dist/data-sources/evm-rpc-services/types/services.d.cts.map +1 -1
  99. package/dist/data-sources/evm-rpc-services/types/services.d.mts +4 -0
  100. package/dist/data-sources/evm-rpc-services/types/services.d.mts.map +1 -1
  101. package/dist/data-sources/evm-rpc-services/types/services.mjs.map +1 -1
  102. package/dist/data-sources/evm-rpc-services/types/state.cjs.map +1 -1
  103. package/dist/data-sources/evm-rpc-services/types/state.d.cts +3 -3
  104. package/dist/data-sources/evm-rpc-services/types/state.d.cts.map +1 -1
  105. package/dist/data-sources/evm-rpc-services/types/state.d.mts +3 -3
  106. package/dist/data-sources/evm-rpc-services/types/state.d.mts.map +1 -1
  107. package/dist/data-sources/evm-rpc-services/types/state.mjs.map +1 -1
  108. package/dist/data-sources/evm-rpc-services/utils/index.cjs +9 -1
  109. package/dist/data-sources/evm-rpc-services/utils/index.cjs.map +1 -1
  110. package/dist/data-sources/evm-rpc-services/utils/index.d.cts +2 -0
  111. package/dist/data-sources/evm-rpc-services/utils/index.d.cts.map +1 -1
  112. package/dist/data-sources/evm-rpc-services/utils/index.d.mts +2 -0
  113. package/dist/data-sources/evm-rpc-services/utils/index.d.mts.map +1 -1
  114. package/dist/data-sources/evm-rpc-services/utils/index.mjs +2 -0
  115. package/dist/data-sources/evm-rpc-services/utils/index.mjs.map +1 -1
  116. package/dist/data-sources/evm-rpc-services/utils/parsing.cjs +47 -0
  117. package/dist/data-sources/evm-rpc-services/utils/parsing.cjs.map +1 -0
  118. package/dist/data-sources/evm-rpc-services/utils/parsing.d.cts +25 -0
  119. package/dist/data-sources/evm-rpc-services/utils/parsing.d.cts.map +1 -0
  120. package/dist/data-sources/evm-rpc-services/utils/parsing.d.mts +25 -0
  121. package/dist/data-sources/evm-rpc-services/utils/parsing.d.mts.map +1 -0
  122. package/dist/data-sources/evm-rpc-services/utils/parsing.mjs +39 -0
  123. package/dist/data-sources/evm-rpc-services/utils/parsing.mjs.map +1 -0
  124. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.cjs +68 -0
  125. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.cjs.map +1 -0
  126. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.d.cts +25 -0
  127. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.d.cts.map +1 -0
  128. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.d.mts +25 -0
  129. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.d.mts.map +1 -0
  130. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.mjs +62 -0
  131. package/dist/data-sources/evm-rpc-services/utils/staking-contracts.mjs.map +1 -0
  132. package/dist/data-sources/index.cjs +3 -1
  133. package/dist/data-sources/index.cjs.map +1 -1
  134. package/dist/data-sources/index.d.cts +3 -2
  135. package/dist/data-sources/index.d.cts.map +1 -1
  136. package/dist/data-sources/index.d.mts +3 -2
  137. package/dist/data-sources/index.d.mts.map +1 -1
  138. package/dist/data-sources/index.mjs +1 -0
  139. package/dist/data-sources/index.mjs.map +1 -1
  140. package/dist/index.cjs.map +1 -1
  141. package/dist/index.d.cts +3 -3
  142. package/dist/index.d.cts.map +1 -1
  143. package/dist/index.d.mts +3 -3
  144. package/dist/index.d.mts.map +1 -1
  145. package/dist/index.mjs.map +1 -1
  146. package/dist/middlewares/DetectionMiddleware.cjs +4 -1
  147. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  148. package/dist/middlewares/DetectionMiddleware.d.cts +1 -0
  149. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  150. package/dist/middlewares/DetectionMiddleware.d.mts +1 -0
  151. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  152. package/dist/middlewares/DetectionMiddleware.mjs +4 -1
  153. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  154. package/dist/selectors/balance.cjs +2 -2
  155. package/dist/selectors/balance.cjs.map +1 -1
  156. package/dist/selectors/balance.d.cts.map +1 -1
  157. package/dist/selectors/balance.d.mts.map +1 -1
  158. package/dist/selectors/balance.mjs +2 -2
  159. package/dist/selectors/balance.mjs.map +1 -1
  160. package/dist/types.cjs.map +1 -1
  161. package/dist/types.d.cts +3 -3
  162. package/dist/types.d.cts.map +1 -1
  163. package/dist/types.d.mts +3 -3
  164. package/dist/types.d.mts.map +1 -1
  165. package/dist/types.mjs.map +1 -1
  166. package/package.json +13 -10
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StakedBalanceFetcher.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/StakedBalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { Interface } from '@ethersproject/abi';\nimport { Web3Provider } from '@ethersproject/providers';\nimport { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\n\nimport type { Address, AccountId, ChainId } from '../types';\nimport {\n getStakingContractAddress,\n getSupportedStakingChainIds,\n isStakingContractAssetId,\n weiToHumanReadable,\n} from '../utils';\n\nexport {\n getStakingContractAddress,\n getSupportedStakingChainIds,\n isStakingContractAssetId,\n};\n\nexport type StakedBalancePollingInput = {\n /** Chain ID (hex format, e.g. 0x1) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/** Human-readable staked balance (e.g. \"1.5\" for 1.5 ETH). */\nexport type StakedBalance = {\n amount: string;\n};\n\n/** Result reported via the update callback. */\nexport type StakedBalanceFetchResult = {\n /** Account ID (UUID). */\n accountId: AccountId;\n /** Hex chain ID. */\n chainId: ChainId;\n /** Human-readable staked balance. */\n balance: StakedBalance;\n};\n\n/**\n * Callback type for staked balance updates.\n */\nexport type OnStakedBalanceUpdateCallback = (\n result: StakedBalanceFetchResult,\n) => void;\n\n/** Staking contract ABI: getShares(account) and convertToAssets(shares). */\nconst STAKING_CONTRACT_ABI = [\n {\n inputs: [{ internalType: 'address', name: 'account', type: 'address' }],\n name: 'getShares',\n outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],\n stateMutability: 'view',\n type: 'function',\n },\n {\n inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],\n name: 'convertToAssets',\n outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],\n stateMutability: 'view',\n type: 'function',\n },\n];\n\nconst STAKING_INTERFACE = new Interface(STAKING_CONTRACT_ABI);\n\nconst STAKING_DECIMALS = 18;\n\nexport type StakedBalanceFetcherConfig = {\n /** Polling interval in ms (default: 180s) */\n pollingInterval?: number;\n /** Returns the network provider for the given chain. Required for fetchStakedBalance. */\n getNetworkProvider?: (chainId: ChainId) => Web3Provider | undefined;\n};\n\nconst DEFAULT_STAKED_BALANCE_INTERVAL = 180_000; // 3 minutes\n\nexport class StakedBalanceFetcher extends StaticIntervalPollingControllerOnly<StakedBalancePollingInput>() {\n readonly #providerGetter?: (chainId: ChainId) => Web3Provider | undefined;\n\n #onStakedBalanceUpdate: OnStakedBalanceUpdateCallback | undefined;\n\n constructor(config?: StakedBalanceFetcherConfig) {\n super();\n this.#providerGetter = config?.getNetworkProvider;\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_STAKED_BALANCE_INTERVAL,\n );\n }\n\n /**\n * Register a callback that is invoked after every successful poll with\n * the staked balance (including zero). Zero is reported so that merged\n * updates can clear prior non-zero state.\n *\n * @param callback - The callback to invoke.\n */\n setOnStakedBalanceUpdate(callback: OnStakedBalanceUpdateCallback): void {\n this.#onStakedBalanceUpdate = callback;\n }\n\n async _executePoll(input: StakedBalancePollingInput): Promise<void> {\n let result: StakedBalance;\n try {\n result = await this.fetchStakedBalance(input);\n } catch {\n // Do not push an update on provider/RPC failure; otherwise we would\n // overwrite existing non-zero staked balances with zero in state.\n return;\n }\n\n if (this.#onStakedBalanceUpdate) {\n this.#onStakedBalanceUpdate({\n accountId: input.accountId,\n chainId: input.chainId,\n balance: result,\n });\n }\n }\n\n /**\n * Fetches the staked balance for an account on a chain using the same\n * staking contract as AccountTrackerController (getShares then convertToAssets).\n * Returns a human-readable amount string (e.g. \"1.5\" for 1.5 ETH).\n * Throws when no provider is available or when the RPC/contract call fails, so\n * callers do not persist a false zero and overwrite existing balances.\n *\n * @param input - Chain, account ID, and address to query.\n * @returns Human-readable staked balance (amount string).\n * @throws When provider is missing or when getShares/convertToAssets fails.\n */\n async fetchStakedBalance(\n input: StakedBalancePollingInput,\n ): Promise<StakedBalance> {\n const { chainId, accountAddress } = input;\n const provider = this.#providerGetter?.(chainId);\n if (!provider) {\n throw new Error('StakedBalanceFetcher: no provider available for chain');\n }\n const contractAddress = getStakingContractAddress(chainId);\n\n if (!contractAddress) {\n return { amount: '0' };\n }\n\n try {\n const sharesCalldata = STAKING_INTERFACE.encodeFunctionData('getShares', [\n accountAddress,\n ]);\n const sharesResult = await provider.call({\n to: contractAddress,\n data: sharesCalldata,\n });\n const sharesRaw = STAKING_INTERFACE.decodeFunctionResult(\n 'getShares',\n sharesResult,\n )[0];\n const sharesBigNum = BigInt(sharesRaw.toString());\n\n if (sharesBigNum === 0n) {\n return { amount: '0' };\n }\n\n const assetsCalldata = STAKING_INTERFACE.encodeFunctionData(\n 'convertToAssets',\n [sharesBigNum],\n );\n const assetsResult = await provider.call({\n to: contractAddress,\n data: assetsCalldata,\n });\n const assetsRaw = STAKING_INTERFACE.decodeFunctionResult(\n 'convertToAssets',\n assetsResult,\n )[0];\n const assetsWei = BigInt(assetsRaw.toString());\n\n const amount = weiToHumanReadable(assetsWei, STAKING_DECIMALS);\n return { amount };\n } catch (error) {\n throw error instanceof Error\n ? error\n : new Error('StakedBalanceFetcher: failed to fetch staked balance');\n }\n }\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { Web3Provider } from "@ethersproject/providers";
2
+ import type { Address, AccountId, ChainId } from "../types/index.cjs";
3
+ import { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId } from "../utils/index.cjs";
4
+ export { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId, };
5
+ export type StakedBalancePollingInput = {
6
+ /** Chain ID (hex format, e.g. 0x1) */
7
+ chainId: ChainId;
8
+ /** Account ID */
9
+ accountId: AccountId;
10
+ /** Account address */
11
+ accountAddress: Address;
12
+ };
13
+ /** Human-readable staked balance (e.g. "1.5" for 1.5 ETH). */
14
+ export type StakedBalance = {
15
+ amount: string;
16
+ };
17
+ /** Result reported via the update callback. */
18
+ export type StakedBalanceFetchResult = {
19
+ /** Account ID (UUID). */
20
+ accountId: AccountId;
21
+ /** Hex chain ID. */
22
+ chainId: ChainId;
23
+ /** Human-readable staked balance. */
24
+ balance: StakedBalance;
25
+ };
26
+ /**
27
+ * Callback type for staked balance updates.
28
+ */
29
+ export type OnStakedBalanceUpdateCallback = (result: StakedBalanceFetchResult) => void;
30
+ export type StakedBalanceFetcherConfig = {
31
+ /** Polling interval in ms (default: 180s) */
32
+ pollingInterval?: number;
33
+ /** Returns the network provider for the given chain. Required for fetchStakedBalance. */
34
+ getNetworkProvider?: (chainId: ChainId) => Web3Provider | undefined;
35
+ };
36
+ declare const StakedBalanceFetcher_base: (abstract new (...args: any[]) => {
37
+ readonly "__#15@#intervalIds": Record<string, NodeJS.Timeout>;
38
+ "__#15@#intervalLength": number | undefined;
39
+ setIntervalLength(intervalLength: number): void;
40
+ getIntervalLength(): number | undefined; /** Chain ID (hex format, e.g. 0x1) */
41
+ _startPolling(input: StakedBalancePollingInput): void;
42
+ _stopPollingByPollingTokenSetId(key: string): void;
43
+ readonly "__#3@#pollingTokenSets": Map<string, Set<string>>;
44
+ readonly "__#3@#callbacks": Map<string, Set<(input: StakedBalancePollingInput) => void>>;
45
+ _executePoll(input: StakedBalancePollingInput): Promise<void>;
46
+ startPolling(input: StakedBalancePollingInput): string;
47
+ stopAllPolling(): void;
48
+ stopPollingByPollingToken(pollingToken: string): void;
49
+ onPollingComplete(input: StakedBalancePollingInput, callback: (input: StakedBalancePollingInput) => void): void;
50
+ }) & {
51
+ new (): {};
52
+ };
53
+ export declare class StakedBalanceFetcher extends StakedBalanceFetcher_base {
54
+ #private;
55
+ constructor(config?: StakedBalanceFetcherConfig);
56
+ /**
57
+ * Register a callback that is invoked after every successful poll with
58
+ * the staked balance (including zero). Zero is reported so that merged
59
+ * updates can clear prior non-zero state.
60
+ *
61
+ * @param callback - The callback to invoke.
62
+ */
63
+ setOnStakedBalanceUpdate(callback: OnStakedBalanceUpdateCallback): void;
64
+ _executePoll(input: StakedBalancePollingInput): Promise<void>;
65
+ /**
66
+ * Fetches the staked balance for an account on a chain using the same
67
+ * staking contract as AccountTrackerController (getShares then convertToAssets).
68
+ * Returns a human-readable amount string (e.g. "1.5" for 1.5 ETH).
69
+ * Throws when no provider is available or when the RPC/contract call fails, so
70
+ * callers do not persist a false zero and overwrite existing balances.
71
+ *
72
+ * @param input - Chain, account ID, and address to query.
73
+ * @returns Human-readable staked balance (amount string).
74
+ * @throws When provider is missing or when getShares/convertToAssets fails.
75
+ */
76
+ fetchStakedBalance(input: StakedBalancePollingInput): Promise<StakedBalance>;
77
+ }
78
+ //# sourceMappingURL=StakedBalanceFetcher.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StakedBalanceFetcher.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/StakedBalanceFetcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iCAAiC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,2BAAiB;AAC5D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,EAEzB,2BAAiB;AAElB,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,wBAAwB,GAAG;IACrC,yBAAyB;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,oBAAoB;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,MAAM,EAAE,wBAAwB,KAC7B,IAAI,CAAC;AAwBV,MAAM,MAAM,0BAA0B,GAAG;IACvC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,YAAY,GAAG,SAAS,CAAC;CACrE,CAAC;;;;;6CAzDA,sCAAsC;;;;;;;;;;;;;AA6DxC,qBAAa,oBAAqB,SAAQ,yBAAgE;;gBAK5F,MAAM,CAAC,EAAE,0BAA0B;IAS/C;;;;;;OAMG;IACH,wBAAwB,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAIjE,YAAY,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBnE;;;;;;;;;;OAUG;IACG,kBAAkB,CACtB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,aAAa,CAAC;CAoD1B"}
@@ -0,0 +1,78 @@
1
+ import { Web3Provider } from "@ethersproject/providers";
2
+ import type { Address, AccountId, ChainId } from "../types/index.mjs";
3
+ import { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId } from "../utils/index.mjs";
4
+ export { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId, };
5
+ export type StakedBalancePollingInput = {
6
+ /** Chain ID (hex format, e.g. 0x1) */
7
+ chainId: ChainId;
8
+ /** Account ID */
9
+ accountId: AccountId;
10
+ /** Account address */
11
+ accountAddress: Address;
12
+ };
13
+ /** Human-readable staked balance (e.g. "1.5" for 1.5 ETH). */
14
+ export type StakedBalance = {
15
+ amount: string;
16
+ };
17
+ /** Result reported via the update callback. */
18
+ export type StakedBalanceFetchResult = {
19
+ /** Account ID (UUID). */
20
+ accountId: AccountId;
21
+ /** Hex chain ID. */
22
+ chainId: ChainId;
23
+ /** Human-readable staked balance. */
24
+ balance: StakedBalance;
25
+ };
26
+ /**
27
+ * Callback type for staked balance updates.
28
+ */
29
+ export type OnStakedBalanceUpdateCallback = (result: StakedBalanceFetchResult) => void;
30
+ export type StakedBalanceFetcherConfig = {
31
+ /** Polling interval in ms (default: 180s) */
32
+ pollingInterval?: number;
33
+ /** Returns the network provider for the given chain. Required for fetchStakedBalance. */
34
+ getNetworkProvider?: (chainId: ChainId) => Web3Provider | undefined;
35
+ };
36
+ declare const StakedBalanceFetcher_base: (abstract new (...args: any[]) => {
37
+ readonly "__#15@#intervalIds": Record<string, NodeJS.Timeout>;
38
+ "__#15@#intervalLength": number | undefined;
39
+ setIntervalLength(intervalLength: number): void;
40
+ getIntervalLength(): number | undefined; /** Chain ID (hex format, e.g. 0x1) */
41
+ _startPolling(input: StakedBalancePollingInput): void;
42
+ _stopPollingByPollingTokenSetId(key: string): void;
43
+ readonly "__#3@#pollingTokenSets": Map<string, Set<string>>;
44
+ readonly "__#3@#callbacks": Map<string, Set<(input: StakedBalancePollingInput) => void>>;
45
+ _executePoll(input: StakedBalancePollingInput): Promise<void>;
46
+ startPolling(input: StakedBalancePollingInput): string;
47
+ stopAllPolling(): void;
48
+ stopPollingByPollingToken(pollingToken: string): void;
49
+ onPollingComplete(input: StakedBalancePollingInput, callback: (input: StakedBalancePollingInput) => void): void;
50
+ }) & {
51
+ new (): {};
52
+ };
53
+ export declare class StakedBalanceFetcher extends StakedBalanceFetcher_base {
54
+ #private;
55
+ constructor(config?: StakedBalanceFetcherConfig);
56
+ /**
57
+ * Register a callback that is invoked after every successful poll with
58
+ * the staked balance (including zero). Zero is reported so that merged
59
+ * updates can clear prior non-zero state.
60
+ *
61
+ * @param callback - The callback to invoke.
62
+ */
63
+ setOnStakedBalanceUpdate(callback: OnStakedBalanceUpdateCallback): void;
64
+ _executePoll(input: StakedBalancePollingInput): Promise<void>;
65
+ /**
66
+ * Fetches the staked balance for an account on a chain using the same
67
+ * staking contract as AccountTrackerController (getShares then convertToAssets).
68
+ * Returns a human-readable amount string (e.g. "1.5" for 1.5 ETH).
69
+ * Throws when no provider is available or when the RPC/contract call fails, so
70
+ * callers do not persist a false zero and overwrite existing balances.
71
+ *
72
+ * @param input - Chain, account ID, and address to query.
73
+ * @returns Human-readable staked balance (amount string).
74
+ * @throws When provider is missing or when getShares/convertToAssets fails.
75
+ */
76
+ fetchStakedBalance(input: StakedBalancePollingInput): Promise<StakedBalance>;
77
+ }
78
+ //# sourceMappingURL=StakedBalanceFetcher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StakedBalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/StakedBalanceFetcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iCAAiC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,2BAAiB;AAC5D,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,EAEzB,2BAAiB;AAElB,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,wBAAwB,GAAG;IACrC,yBAAyB;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,oBAAoB;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,MAAM,EAAE,wBAAwB,KAC7B,IAAI,CAAC;AAwBV,MAAM,MAAM,0BAA0B,GAAG;IACvC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,YAAY,GAAG,SAAS,CAAC;CACrE,CAAC;;;;;6CAzDA,sCAAsC;;;;;;;;;;;;;AA6DxC,qBAAa,oBAAqB,SAAQ,yBAAgE;;gBAK5F,MAAM,CAAC,EAAE,0BAA0B;IAS/C;;;;;;OAMG;IACH,wBAAwB,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAIjE,YAAY,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBnE;;;;;;;;;;OAUG;IACG,kBAAkB,CACtB,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,aAAa,CAAC;CAoD1B"}
@@ -0,0 +1,126 @@
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 _StakedBalanceFetcher_providerGetter, _StakedBalanceFetcher_onStakedBalanceUpdate;
13
+ import { Interface } from "@ethersproject/abi";
14
+ import { Web3Provider } from "@ethersproject/providers";
15
+ import { StaticIntervalPollingControllerOnly } from "@metamask/polling-controller";
16
+ import { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId, weiToHumanReadable } from "../utils/index.mjs";
17
+ export { getStakingContractAddress, getSupportedStakingChainIds, isStakingContractAssetId };
18
+ /** Staking contract ABI: getShares(account) and convertToAssets(shares). */
19
+ const STAKING_CONTRACT_ABI = [
20
+ {
21
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
22
+ name: 'getShares',
23
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
24
+ stateMutability: 'view',
25
+ type: 'function',
26
+ },
27
+ {
28
+ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
29
+ name: 'convertToAssets',
30
+ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],
31
+ stateMutability: 'view',
32
+ type: 'function',
33
+ },
34
+ ];
35
+ const STAKING_INTERFACE = new Interface(STAKING_CONTRACT_ABI);
36
+ const STAKING_DECIMALS = 18;
37
+ const DEFAULT_STAKED_BALANCE_INTERVAL = 180000; // 3 minutes
38
+ export class StakedBalanceFetcher extends StaticIntervalPollingControllerOnly() {
39
+ constructor(config) {
40
+ super();
41
+ _StakedBalanceFetcher_providerGetter.set(this, void 0);
42
+ _StakedBalanceFetcher_onStakedBalanceUpdate.set(this, void 0);
43
+ __classPrivateFieldSet(this, _StakedBalanceFetcher_providerGetter, config?.getNetworkProvider, "f");
44
+ this.setIntervalLength(config?.pollingInterval ?? DEFAULT_STAKED_BALANCE_INTERVAL);
45
+ }
46
+ /**
47
+ * Register a callback that is invoked after every successful poll with
48
+ * the staked balance (including zero). Zero is reported so that merged
49
+ * updates can clear prior non-zero state.
50
+ *
51
+ * @param callback - The callback to invoke.
52
+ */
53
+ setOnStakedBalanceUpdate(callback) {
54
+ __classPrivateFieldSet(this, _StakedBalanceFetcher_onStakedBalanceUpdate, callback, "f");
55
+ }
56
+ async _executePoll(input) {
57
+ let result;
58
+ try {
59
+ result = await this.fetchStakedBalance(input);
60
+ }
61
+ catch {
62
+ // Do not push an update on provider/RPC failure; otherwise we would
63
+ // overwrite existing non-zero staked balances with zero in state.
64
+ return;
65
+ }
66
+ if (__classPrivateFieldGet(this, _StakedBalanceFetcher_onStakedBalanceUpdate, "f")) {
67
+ __classPrivateFieldGet(this, _StakedBalanceFetcher_onStakedBalanceUpdate, "f").call(this, {
68
+ accountId: input.accountId,
69
+ chainId: input.chainId,
70
+ balance: result,
71
+ });
72
+ }
73
+ }
74
+ /**
75
+ * Fetches the staked balance for an account on a chain using the same
76
+ * staking contract as AccountTrackerController (getShares then convertToAssets).
77
+ * Returns a human-readable amount string (e.g. "1.5" for 1.5 ETH).
78
+ * Throws when no provider is available or when the RPC/contract call fails, so
79
+ * callers do not persist a false zero and overwrite existing balances.
80
+ *
81
+ * @param input - Chain, account ID, and address to query.
82
+ * @returns Human-readable staked balance (amount string).
83
+ * @throws When provider is missing or when getShares/convertToAssets fails.
84
+ */
85
+ async fetchStakedBalance(input) {
86
+ const { chainId, accountAddress } = input;
87
+ const provider = __classPrivateFieldGet(this, _StakedBalanceFetcher_providerGetter, "f")?.call(this, chainId);
88
+ if (!provider) {
89
+ throw new Error('StakedBalanceFetcher: no provider available for chain');
90
+ }
91
+ const contractAddress = getStakingContractAddress(chainId);
92
+ if (!contractAddress) {
93
+ return { amount: '0' };
94
+ }
95
+ try {
96
+ const sharesCalldata = STAKING_INTERFACE.encodeFunctionData('getShares', [
97
+ accountAddress,
98
+ ]);
99
+ const sharesResult = await provider.call({
100
+ to: contractAddress,
101
+ data: sharesCalldata,
102
+ });
103
+ const sharesRaw = STAKING_INTERFACE.decodeFunctionResult('getShares', sharesResult)[0];
104
+ const sharesBigNum = BigInt(sharesRaw.toString());
105
+ if (sharesBigNum === 0n) {
106
+ return { amount: '0' };
107
+ }
108
+ const assetsCalldata = STAKING_INTERFACE.encodeFunctionData('convertToAssets', [sharesBigNum]);
109
+ const assetsResult = await provider.call({
110
+ to: contractAddress,
111
+ data: assetsCalldata,
112
+ });
113
+ const assetsRaw = STAKING_INTERFACE.decodeFunctionResult('convertToAssets', assetsResult)[0];
114
+ const assetsWei = BigInt(assetsRaw.toString());
115
+ const amount = weiToHumanReadable(assetsWei, STAKING_DECIMALS);
116
+ return { amount };
117
+ }
118
+ catch (error) {
119
+ throw error instanceof Error
120
+ ? error
121
+ : new Error('StakedBalanceFetcher: failed to fetch staked balance');
122
+ }
123
+ }
124
+ }
125
+ _StakedBalanceFetcher_providerGetter = new WeakMap(), _StakedBalanceFetcher_onStakedBalanceUpdate = new WeakMap();
126
+ //# sourceMappingURL=StakedBalanceFetcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StakedBalanceFetcher.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/StakedBalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { Interface } from '@ethersproject/abi';\nimport { Web3Provider } from '@ethersproject/providers';\nimport { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\n\nimport type { Address, AccountId, ChainId } from '../types';\nimport {\n getStakingContractAddress,\n getSupportedStakingChainIds,\n isStakingContractAssetId,\n weiToHumanReadable,\n} from '../utils';\n\nexport {\n getStakingContractAddress,\n getSupportedStakingChainIds,\n isStakingContractAssetId,\n};\n\nexport type StakedBalancePollingInput = {\n /** Chain ID (hex format, e.g. 0x1) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/** Human-readable staked balance (e.g. \"1.5\" for 1.5 ETH). */\nexport type StakedBalance = {\n amount: string;\n};\n\n/** Result reported via the update callback. */\nexport type StakedBalanceFetchResult = {\n /** Account ID (UUID). */\n accountId: AccountId;\n /** Hex chain ID. */\n chainId: ChainId;\n /** Human-readable staked balance. */\n balance: StakedBalance;\n};\n\n/**\n * Callback type for staked balance updates.\n */\nexport type OnStakedBalanceUpdateCallback = (\n result: StakedBalanceFetchResult,\n) => void;\n\n/** Staking contract ABI: getShares(account) and convertToAssets(shares). */\nconst STAKING_CONTRACT_ABI = [\n {\n inputs: [{ internalType: 'address', name: 'account', type: 'address' }],\n name: 'getShares',\n outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],\n stateMutability: 'view',\n type: 'function',\n },\n {\n inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],\n name: 'convertToAssets',\n outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],\n stateMutability: 'view',\n type: 'function',\n },\n];\n\nconst STAKING_INTERFACE = new Interface(STAKING_CONTRACT_ABI);\n\nconst STAKING_DECIMALS = 18;\n\nexport type StakedBalanceFetcherConfig = {\n /** Polling interval in ms (default: 180s) */\n pollingInterval?: number;\n /** Returns the network provider for the given chain. Required for fetchStakedBalance. */\n getNetworkProvider?: (chainId: ChainId) => Web3Provider | undefined;\n};\n\nconst DEFAULT_STAKED_BALANCE_INTERVAL = 180_000; // 3 minutes\n\nexport class StakedBalanceFetcher extends StaticIntervalPollingControllerOnly<StakedBalancePollingInput>() {\n readonly #providerGetter?: (chainId: ChainId) => Web3Provider | undefined;\n\n #onStakedBalanceUpdate: OnStakedBalanceUpdateCallback | undefined;\n\n constructor(config?: StakedBalanceFetcherConfig) {\n super();\n this.#providerGetter = config?.getNetworkProvider;\n\n this.setIntervalLength(\n config?.pollingInterval ?? DEFAULT_STAKED_BALANCE_INTERVAL,\n );\n }\n\n /**\n * Register a callback that is invoked after every successful poll with\n * the staked balance (including zero). Zero is reported so that merged\n * updates can clear prior non-zero state.\n *\n * @param callback - The callback to invoke.\n */\n setOnStakedBalanceUpdate(callback: OnStakedBalanceUpdateCallback): void {\n this.#onStakedBalanceUpdate = callback;\n }\n\n async _executePoll(input: StakedBalancePollingInput): Promise<void> {\n let result: StakedBalance;\n try {\n result = await this.fetchStakedBalance(input);\n } catch {\n // Do not push an update on provider/RPC failure; otherwise we would\n // overwrite existing non-zero staked balances with zero in state.\n return;\n }\n\n if (this.#onStakedBalanceUpdate) {\n this.#onStakedBalanceUpdate({\n accountId: input.accountId,\n chainId: input.chainId,\n balance: result,\n });\n }\n }\n\n /**\n * Fetches the staked balance for an account on a chain using the same\n * staking contract as AccountTrackerController (getShares then convertToAssets).\n * Returns a human-readable amount string (e.g. \"1.5\" for 1.5 ETH).\n * Throws when no provider is available or when the RPC/contract call fails, so\n * callers do not persist a false zero and overwrite existing balances.\n *\n * @param input - Chain, account ID, and address to query.\n * @returns Human-readable staked balance (amount string).\n * @throws When provider is missing or when getShares/convertToAssets fails.\n */\n async fetchStakedBalance(\n input: StakedBalancePollingInput,\n ): Promise<StakedBalance> {\n const { chainId, accountAddress } = input;\n const provider = this.#providerGetter?.(chainId);\n if (!provider) {\n throw new Error('StakedBalanceFetcher: no provider available for chain');\n }\n const contractAddress = getStakingContractAddress(chainId);\n\n if (!contractAddress) {\n return { amount: '0' };\n }\n\n try {\n const sharesCalldata = STAKING_INTERFACE.encodeFunctionData('getShares', [\n accountAddress,\n ]);\n const sharesResult = await provider.call({\n to: contractAddress,\n data: sharesCalldata,\n });\n const sharesRaw = STAKING_INTERFACE.decodeFunctionResult(\n 'getShares',\n sharesResult,\n )[0];\n const sharesBigNum = BigInt(sharesRaw.toString());\n\n if (sharesBigNum === 0n) {\n return { amount: '0' };\n }\n\n const assetsCalldata = STAKING_INTERFACE.encodeFunctionData(\n 'convertToAssets',\n [sharesBigNum],\n );\n const assetsResult = await provider.call({\n to: contractAddress,\n data: assetsCalldata,\n });\n const assetsRaw = STAKING_INTERFACE.decodeFunctionResult(\n 'convertToAssets',\n assetsResult,\n )[0];\n const assetsWei = BigInt(assetsRaw.toString());\n\n const amount = weiToHumanReadable(assetsWei, STAKING_DECIMALS);\n return { amount };\n } catch (error) {\n throw error instanceof Error\n ? error\n : new Error('StakedBalanceFetcher: failed to fetch staked balance');\n }\n }\n}\n"]}
@@ -31,6 +31,8 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
31
31
  __classPrivateFieldSet(this, _TokenDetector_multicallClient, multicallClient, "f");
32
32
  __classPrivateFieldSet(this, _TokenDetector_messenger, messenger, "f");
33
33
  __classPrivateFieldSet(this, _TokenDetector_config, {
34
+ tokenDetectionEnabled: config?.tokenDetectionEnabled ?? (() => true),
35
+ useExternalService: config?.useExternalService ?? (() => true),
34
36
  defaultBatchSize: config?.defaultBatchSize ?? 300,
35
37
  defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
36
38
  }, "f");
@@ -83,6 +85,20 @@ class TokenDetector extends (0, polling_controller_1.StaticIntervalPollingContro
83
85
  return Object.keys(chainTokenList);
84
86
  }
85
87
  async detectTokens(chainId, accountId, accountAddress, options) {
88
+ const tokenDetectionEnabled = options?.tokenDetectionEnabled ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").tokenDetectionEnabled();
89
+ const useExternalService = options?.useExternalService ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").useExternalService();
90
+ if (!tokenDetectionEnabled || !useExternalService) {
91
+ return {
92
+ chainId,
93
+ accountId,
94
+ accountAddress,
95
+ detectedAssets: [],
96
+ detectedBalances: [],
97
+ zeroBalanceAddresses: [],
98
+ failedAddresses: [],
99
+ timestamp: Date.now(),
100
+ };
101
+ }
86
102
  const batchSize = options?.batchSize ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").defaultBatchSize;
87
103
  const timestamp = Date.now();
88
104
  const tokensToCheck = this.getTokensToCheck(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\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 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 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"]}
@@ -7,6 +7,10 @@ export type TokenDetectorMessenger = {
7
7
  call: (action: 'TokenListController:getState') => TokenListState;
8
8
  };
9
9
  export type TokenDetectorConfig = {
10
+ /** Function returning whether token detection is enabled (avoids stale value) */
11
+ tokenDetectionEnabled?: () => boolean;
12
+ /** Function returning whether external services are allowed (avoids stale value; default: () => true) */
13
+ useExternalService?: () => boolean;
10
14
  defaultBatchSize?: number;
11
15
  defaultTimeoutMs?: number;
12
16
  /** Polling interval in ms (default: 3 minutes) */
@@ -39,7 +43,7 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
39
43
  _executePoll(input: DetectionPollingInput): Promise<void>;
40
44
  startPolling(input: DetectionPollingInput): string;
41
45
  stopAllPolling(): void;
42
- stopPollingByPollingToken(pollingToken: string): void; /** Chain ID (hex format) */
46
+ stopPollingByPollingToken(pollingToken: string): void;
43
47
  onPollingComplete(input: DetectionPollingInput, callback: (input: DetectionPollingInput) => void): void;
44
48
  }) & {
45
49
  new (): {};
@@ -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;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,cAAc,CAAC;CAClE,CAAC;AAEF,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;;;;;;;;;;;;;2DAX7E,4BAA4B;;;;;AAa9B;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBAU3F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,CAAC,EAAE,mBAAmB;IAgB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IA4BvC,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
+ {"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;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,cAAc,CAAC;CAClE,CAAC;AAEF,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;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBAU3F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,CAAC,EAAE,mBAAmB;IAmB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IA4BvC,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CAqOjC"}
@@ -7,6 +7,10 @@ export type TokenDetectorMessenger = {
7
7
  call: (action: 'TokenListController:getState') => TokenListState;
8
8
  };
9
9
  export type TokenDetectorConfig = {
10
+ /** Function returning whether token detection is enabled (avoids stale value) */
11
+ tokenDetectionEnabled?: () => boolean;
12
+ /** Function returning whether external services are allowed (avoids stale value; default: () => true) */
13
+ useExternalService?: () => boolean;
10
14
  defaultBatchSize?: number;
11
15
  defaultTimeoutMs?: number;
12
16
  /** Polling interval in ms (default: 3 minutes) */
@@ -39,7 +43,7 @@ declare const TokenDetector_base: (abstract new (...args: any[]) => {
39
43
  _executePoll(input: DetectionPollingInput): Promise<void>;
40
44
  startPolling(input: DetectionPollingInput): string;
41
45
  stopAllPolling(): void;
42
- stopPollingByPollingToken(pollingToken: string): void; /** Chain ID (hex format) */
46
+ stopPollingByPollingToken(pollingToken: string): void;
43
47
  onPollingComplete(input: DetectionPollingInput, callback: (input: DetectionPollingInput) => void): void;
44
48
  }) & {
45
49
  new (): {};
@@ -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;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,cAAc,CAAC;CAClE,CAAC;AAEF,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;;;;;;;;;;;;;2DAX7E,4BAA4B;;;;;AAa9B;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBAU3F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,CAAC,EAAE,mBAAmB;IAgB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IA4BvC,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
+ {"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;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,cAAc,CAAC;CAClE,CAAC;AAEF,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;;;GAGG;AACH,qBAAa,aAAc,SAAQ,kBAA4D;;gBAU3F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,CAAC,EAAE,mBAAmB;IAmB9B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAI/D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/D,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE;IA4BvC,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;CAqOjC"}
@@ -28,6 +28,8 @@ export class TokenDetector extends StaticIntervalPollingControllerOnly() {
28
28
  __classPrivateFieldSet(this, _TokenDetector_multicallClient, multicallClient, "f");
29
29
  __classPrivateFieldSet(this, _TokenDetector_messenger, messenger, "f");
30
30
  __classPrivateFieldSet(this, _TokenDetector_config, {
31
+ tokenDetectionEnabled: config?.tokenDetectionEnabled ?? (() => true),
32
+ useExternalService: config?.useExternalService ?? (() => true),
31
33
  defaultBatchSize: config?.defaultBatchSize ?? 300,
32
34
  defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,
33
35
  }, "f");
@@ -80,6 +82,20 @@ export class TokenDetector extends StaticIntervalPollingControllerOnly() {
80
82
  return Object.keys(chainTokenList);
81
83
  }
82
84
  async detectTokens(chainId, accountId, accountAddress, options) {
85
+ const tokenDetectionEnabled = options?.tokenDetectionEnabled ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").tokenDetectionEnabled();
86
+ const useExternalService = options?.useExternalService ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").useExternalService();
87
+ if (!tokenDetectionEnabled || !useExternalService) {
88
+ return {
89
+ chainId,
90
+ accountId,
91
+ accountAddress,
92
+ detectedAssets: [],
93
+ detectedBalances: [],
94
+ zeroBalanceAddresses: [],
95
+ failedAddresses: [],
96
+ timestamp: Date.now(),
97
+ };
98
+ }
83
99
  const batchSize = options?.batchSize ?? __classPrivateFieldGet(this, _TokenDetector_config, "f").defaultBatchSize;
84
100
  const timestamp = Date.now();
85
101
  const tokensToCheck = this.getTokensToCheck(chainId);