@metamask/assets-controllers 29.0.0 → 31.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 (206) hide show
  1. package/CHANGELOG.md +105 -1
  2. package/README.md +29 -0
  3. package/dist/AccountTrackerController.js +2 -2
  4. package/dist/AccountTrackerController.mjs +1 -1
  5. package/dist/AssetsContractController.js +6 -6
  6. package/dist/AssetsContractController.mjs +5 -5
  7. package/dist/CurrencyRateController.js +4 -3
  8. package/dist/CurrencyRateController.mjs +3 -2
  9. package/dist/NftController.js +3 -3
  10. package/dist/NftController.mjs +4 -4
  11. package/dist/NftDetectionController.js +2 -2
  12. package/dist/NftDetectionController.mjs +1 -1
  13. package/dist/RatesController/RatesController.js +14 -0
  14. package/dist/RatesController/RatesController.mjs +14 -0
  15. package/dist/RatesController/index.js +13 -0
  16. package/dist/RatesController/index.js.map +1 -0
  17. package/dist/RatesController/index.mjs +13 -0
  18. package/dist/RatesController/index.mjs.map +1 -0
  19. package/dist/RatesController/types.js +1 -0
  20. package/dist/RatesController/types.js.map +1 -0
  21. package/dist/RatesController/types.mjs +1 -0
  22. package/dist/RatesController/types.mjs.map +1 -0
  23. package/dist/Standards/ERC20Standard.js +3 -3
  24. package/dist/Standards/ERC20Standard.mjs +2 -2
  25. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js +3 -3
  26. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.mjs +2 -2
  27. package/dist/Standards/NftStandards/ERC721/ERC721Standard.js +3 -3
  28. package/dist/Standards/NftStandards/ERC721/ERC721Standard.mjs +2 -2
  29. package/dist/TokenBalancesController.js +2 -2
  30. package/dist/TokenBalancesController.mjs +1 -1
  31. package/dist/TokenDetectionController.js +3 -3
  32. package/dist/TokenDetectionController.mjs +2 -2
  33. package/dist/TokenListController.js +4 -4
  34. package/dist/TokenListController.mjs +3 -3
  35. package/dist/TokenRatesController.js +6 -4
  36. package/dist/TokenRatesController.mjs +5 -3
  37. package/dist/TokensController.js +6 -6
  38. package/dist/TokensController.mjs +5 -5
  39. package/dist/assetsUtil.js +2 -2
  40. package/dist/assetsUtil.mjs +1 -1
  41. package/dist/chunk-4AC3X2U5.js +771 -0
  42. package/dist/chunk-4AC3X2U5.js.map +1 -0
  43. package/dist/chunk-543CC74T.mjs +253 -0
  44. package/dist/chunk-543CC74T.mjs.map +1 -0
  45. package/dist/{chunk-NXGX7LZJ.mjs → chunk-5W5OO2Q5.mjs} +17 -6
  46. package/dist/chunk-5W5OO2Q5.mjs.map +1 -0
  47. package/dist/chunk-62T7RKU3.mjs +1265 -0
  48. package/dist/chunk-62T7RKU3.mjs.map +1 -0
  49. package/dist/{chunk-PAJTKWEC.mjs → chunk-65PB33TE.mjs} +1 -1
  50. package/dist/chunk-65PB33TE.mjs.map +1 -0
  51. package/dist/{chunk-6XOM7KOQ.mjs → chunk-73F3SN5O.mjs} +337 -270
  52. package/dist/chunk-73F3SN5O.mjs.map +1 -0
  53. package/dist/chunk-7K6PSEAA.js +1 -0
  54. package/dist/chunk-7K6PSEAA.js.map +1 -0
  55. package/dist/{chunk-RUE635TV.mjs → chunk-A2DDWXMS.mjs} +3 -3
  56. package/dist/{chunk-V46CHMOU.mjs → chunk-ASA5RLBY.mjs} +2 -2
  57. package/dist/chunk-B5YVX5IO.mjs +202 -0
  58. package/dist/chunk-B5YVX5IO.mjs.map +1 -0
  59. package/dist/{chunk-BO2WZDUM.js → chunk-B5YY22QQ.js} +3 -3
  60. package/dist/{chunk-QWACHXRH.mjs → chunk-B6W4CQOR.mjs} +2 -2
  61. package/dist/chunk-B6W4CQOR.mjs.map +1 -0
  62. package/dist/{chunk-3ZHN4GFT.js → chunk-BOTVAG4A.js} +5 -5
  63. package/dist/chunk-CGLUTXI7.js +1265 -0
  64. package/dist/chunk-CGLUTXI7.js.map +1 -0
  65. package/dist/chunk-CNKVITJO.mjs +66 -0
  66. package/dist/chunk-CNKVITJO.mjs.map +1 -0
  67. package/dist/{chunk-LLYYJY7H.mjs → chunk-D3K5MPMW.mjs} +2 -2
  68. package/dist/{chunk-OWTCG2N3.mjs → chunk-E4ECCGJV.mjs} +53 -39
  69. package/dist/chunk-E4ECCGJV.mjs.map +1 -0
  70. package/dist/{chunk-LRKDZWS6.js → chunk-ELSMS5S7.js} +3 -3
  71. package/dist/chunk-ELSMS5S7.js.map +1 -0
  72. package/dist/{chunk-V4ZO3F2S.js → chunk-GU53EI7A.js} +1 -1
  73. package/dist/chunk-GU53EI7A.js.map +1 -0
  74. package/dist/{chunk-I53XGBU3.js → chunk-HDI4L2DD.js} +5 -5
  75. package/dist/{chunk-5MRF7YPD.mjs → chunk-HLCGZGPA.mjs} +6 -6
  76. package/dist/chunk-HLCGZGPA.mjs.map +1 -0
  77. package/dist/{chunk-LD4GC7OR.js → chunk-IBK6AXPP.js} +8 -9
  78. package/dist/chunk-IBK6AXPP.js.map +1 -0
  79. package/dist/{chunk-DEQZ35QE.js → chunk-JUI3XNEF.js} +7 -7
  80. package/dist/chunk-JYHAAA6W.mjs +1 -0
  81. package/dist/chunk-JYHAAA6W.mjs.map +1 -0
  82. package/dist/{chunk-S7UA2DU7.mjs → chunk-LZ5ZGQEX.mjs} +2 -2
  83. package/dist/{chunk-D3PCUDTX.mjs → chunk-MR6EF4B7.mjs} +2 -2
  84. package/dist/{chunk-LS6R3HQL.js → chunk-NEXY7SE2.js} +2 -2
  85. package/dist/chunk-NEXY7SE2.js.map +1 -0
  86. package/dist/{chunk-OHSQRYVL.js → chunk-PYMUBJQX.js} +17 -6
  87. package/dist/chunk-PYMUBJQX.js.map +1 -0
  88. package/dist/{chunk-X5PLVMOQ.mjs → chunk-Q5JRBGWO.mjs} +2 -2
  89. package/dist/chunk-Q5JRBGWO.mjs.map +1 -0
  90. package/dist/chunk-QFDTOEYR.js +253 -0
  91. package/dist/chunk-QFDTOEYR.js.map +1 -0
  92. package/dist/{chunk-QHU4H6HP.js → chunk-QHWKLZUC.js} +7 -7
  93. package/dist/chunk-QHWKLZUC.js.map +1 -0
  94. package/dist/chunk-TTH3ES66.mjs +1 -0
  95. package/dist/chunk-TTH3ES66.mjs.map +1 -0
  96. package/dist/{chunk-OBOWNEJU.js → chunk-U3DJJN4X.js} +4 -4
  97. package/dist/{chunk-IOQX3VTD.js → chunk-UEDNQBJN.js} +56 -42
  98. package/dist/chunk-UEDNQBJN.js.map +1 -0
  99. package/dist/chunk-WB6KJX4N.js +66 -0
  100. package/dist/chunk-WB6KJX4N.js.map +1 -0
  101. package/dist/{chunk-4LY47RPI.mjs → chunk-WCCLSUOI.mjs} +8 -9
  102. package/dist/{chunk-4LY47RPI.mjs.map → chunk-WCCLSUOI.mjs.map} +1 -1
  103. package/dist/chunk-XC3SOOGC.js +1 -0
  104. package/dist/chunk-XC3SOOGC.js.map +1 -0
  105. package/dist/{chunk-OSEZFHQ3.mjs → chunk-Y35SM7TO.mjs} +19 -14
  106. package/dist/chunk-Y35SM7TO.mjs.map +1 -0
  107. package/dist/chunk-YIFA2HXH.js +202 -0
  108. package/dist/chunk-YIFA2HXH.js.map +1 -0
  109. package/dist/{chunk-4FMVFW2T.js → chunk-Z6TBQQE5.js} +20 -15
  110. package/dist/chunk-Z6TBQQE5.js.map +1 -0
  111. package/dist/crypto-compare-service/crypto-compare.js +10 -0
  112. package/dist/crypto-compare-service/crypto-compare.js.map +1 -0
  113. package/dist/crypto-compare-service/crypto-compare.mjs +10 -0
  114. package/dist/crypto-compare-service/crypto-compare.mjs.map +1 -0
  115. package/dist/crypto-compare-service/index.js +11 -0
  116. package/dist/crypto-compare-service/index.js.map +1 -0
  117. package/dist/crypto-compare-service/index.mjs +11 -0
  118. package/dist/crypto-compare-service/index.mjs.map +1 -0
  119. package/dist/index.js +24 -18
  120. package/dist/index.mjs +29 -23
  121. package/dist/token-prices-service/codefi-v2.js +4 -2
  122. package/dist/token-prices-service/codefi-v2.mjs +5 -3
  123. package/dist/token-prices-service/index.js +2 -2
  124. package/dist/token-prices-service/index.mjs +1 -1
  125. package/dist/token-service.js +3 -3
  126. package/dist/token-service.mjs +2 -2
  127. package/dist/tsconfig.build.tsbuildinfo +1 -1
  128. package/dist/types/AccountTrackerController.d.ts +1 -1
  129. package/dist/types/AccountTrackerController.d.ts.map +1 -1
  130. package/dist/types/AssetsContractController.d.ts +1 -1
  131. package/dist/types/AssetsContractController.d.ts.map +1 -1
  132. package/dist/types/CurrencyRateController.d.ts +1 -1
  133. package/dist/types/CurrencyRateController.d.ts.map +1 -1
  134. package/dist/types/NftController.d.ts +65 -184
  135. package/dist/types/NftController.d.ts.map +1 -1
  136. package/dist/types/NftDetectionController.d.ts +34 -75
  137. package/dist/types/NftDetectionController.d.ts.map +1 -1
  138. package/dist/types/RatesController/RatesController.d.ts +44 -0
  139. package/dist/types/RatesController/RatesController.d.ts.map +1 -0
  140. package/dist/types/RatesController/index.d.ts +3 -0
  141. package/dist/types/RatesController/index.d.ts.map +1 -0
  142. package/dist/types/RatesController/types.d.ts +100 -0
  143. package/dist/types/RatesController/types.d.ts.map +1 -0
  144. package/dist/types/TokenRatesController.d.ts +32 -10
  145. package/dist/types/TokenRatesController.d.ts.map +1 -1
  146. package/dist/types/TokensController.d.ts +27 -131
  147. package/dist/types/TokensController.d.ts.map +1 -1
  148. package/dist/types/crypto-compare-service/crypto-compare.d.ts +22 -0
  149. package/dist/types/crypto-compare-service/crypto-compare.d.ts.map +1 -0
  150. package/dist/types/crypto-compare-service/index.d.ts +2 -0
  151. package/dist/types/crypto-compare-service/index.d.ts.map +1 -0
  152. package/dist/types/index.d.ts +7 -3
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/dist/types/token-prices-service/abstract-token-prices-service.d.ts +18 -0
  155. package/dist/types/token-prices-service/abstract-token-prices-service.d.ts.map +1 -1
  156. package/dist/types/token-prices-service/codefi-v2.d.ts +7 -0
  157. package/dist/types/token-prices-service/codefi-v2.d.ts.map +1 -1
  158. package/dist/types/token-service.d.ts +1 -1
  159. package/dist/types/token-service.d.ts.map +1 -1
  160. package/package.json +14 -14
  161. package/dist/chunk-4FMVFW2T.js.map +0 -1
  162. package/dist/chunk-5MRF7YPD.mjs.map +0 -1
  163. package/dist/chunk-6XOM7KOQ.mjs.map +0 -1
  164. package/dist/chunk-ASFD56OL.mjs +0 -235
  165. package/dist/chunk-ASFD56OL.mjs.map +0 -1
  166. package/dist/chunk-DYH5P3VY.js +0 -35
  167. package/dist/chunk-DYH5P3VY.js.map +0 -1
  168. package/dist/chunk-IOQX3VTD.js.map +0 -1
  169. package/dist/chunk-KM3J4DO6.js +0 -1182
  170. package/dist/chunk-KM3J4DO6.js.map +0 -1
  171. package/dist/chunk-LD4GC7OR.js.map +0 -1
  172. package/dist/chunk-LRKDZWS6.js.map +0 -1
  173. package/dist/chunk-LS6R3HQL.js.map +0 -1
  174. package/dist/chunk-NXGX7LZJ.mjs.map +0 -1
  175. package/dist/chunk-OHSQRYVL.js.map +0 -1
  176. package/dist/chunk-OSEZFHQ3.mjs.map +0 -1
  177. package/dist/chunk-OWTCG2N3.mjs.map +0 -1
  178. package/dist/chunk-PAJTKWEC.mjs.map +0 -1
  179. package/dist/chunk-PWZE6KJV.mjs +0 -35
  180. package/dist/chunk-PWZE6KJV.mjs.map +0 -1
  181. package/dist/chunk-QHU4H6HP.js.map +0 -1
  182. package/dist/chunk-QWACHXRH.mjs.map +0 -1
  183. package/dist/chunk-S6CZP74C.mjs +0 -1182
  184. package/dist/chunk-S6CZP74C.mjs.map +0 -1
  185. package/dist/chunk-SOK5YX7I.js +0 -704
  186. package/dist/chunk-SOK5YX7I.js.map +0 -1
  187. package/dist/chunk-V4ZO3F2S.js.map +0 -1
  188. package/dist/chunk-X2MMIBYW.js +0 -235
  189. package/dist/chunk-X2MMIBYW.js.map +0 -1
  190. package/dist/chunk-X5PLVMOQ.mjs.map +0 -1
  191. package/dist/crypto-compare.js +0 -8
  192. package/dist/crypto-compare.mjs +0 -8
  193. package/dist/types/crypto-compare.d.ts +0 -13
  194. package/dist/types/crypto-compare.d.ts.map +0 -1
  195. /package/dist/{crypto-compare.js.map → RatesController/RatesController.js.map} +0 -0
  196. /package/dist/{crypto-compare.mjs.map → RatesController/RatesController.mjs.map} +0 -0
  197. /package/dist/{chunk-RUE635TV.mjs.map → chunk-A2DDWXMS.mjs.map} +0 -0
  198. /package/dist/{chunk-V46CHMOU.mjs.map → chunk-ASA5RLBY.mjs.map} +0 -0
  199. /package/dist/{chunk-BO2WZDUM.js.map → chunk-B5YY22QQ.js.map} +0 -0
  200. /package/dist/{chunk-3ZHN4GFT.js.map → chunk-BOTVAG4A.js.map} +0 -0
  201. /package/dist/{chunk-LLYYJY7H.mjs.map → chunk-D3K5MPMW.mjs.map} +0 -0
  202. /package/dist/{chunk-I53XGBU3.js.map → chunk-HDI4L2DD.js.map} +0 -0
  203. /package/dist/{chunk-DEQZ35QE.js.map → chunk-JUI3XNEF.js.map} +0 -0
  204. /package/dist/{chunk-S7UA2DU7.mjs.map → chunk-LZ5ZGQEX.mjs.map} +0 -0
  205. /package/dist/{chunk-D3PCUDTX.mjs.map → chunk-MR6EF4B7.mjs.map} +0 -0
  206. /package/dist/{chunk-OBOWNEJU.js.map → chunk-U3DJJN4X.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/TokensController.ts"],"sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type { AddApprovalRequest } from '@metamask/approval-controller';\nimport type {\n BaseConfig,\n BaseState,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { BaseControllerV1 } from '@metamask/base-controller';\nimport contractsMap from '@metamask/contract-metadata';\nimport {\n toChecksumHexAddress,\n ERC721_INTERFACE_ID,\n ORIGIN_METAMASK,\n ApprovalType,\n ERC20,\n ERC721,\n ERC1155,\n isValidHexAddress,\n safelyExecute,\n} from '@metamask/controller-utils';\nimport { abiERC721 } from '@metamask/metamask-eth-abis';\nimport type {\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerNetworkDidChangeEvent,\n Provider,\n} from '@metamask/network-controller';\nimport type { PreferencesControllerStateChangeEvent } from '@metamask/preferences-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport { EventEmitter } from 'events';\nimport type { Patch } from 'immer/dist/immer';\nimport { v1 as random } from 'uuid';\n\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\nimport { ERC20Standard } from './Standards/ERC20Standard';\nimport { ERC1155Standard } from './Standards/NftStandards/ERC1155/ERC1155Standard';\nimport {\n fetchTokenMetadata,\n TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from './token-service';\nimport type {\n TokenListMap,\n TokenListStateChange,\n TokenListToken,\n} from './TokenListController';\nimport type { Token } from './TokenRatesController';\n\n/**\n * @type TokensConfig\n *\n * Tokens controller configuration\n * @property selectedAddress - Vault selected address\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokensConfig extends BaseConfig {\n selectedAddress: string;\n chainId: Hex;\n provider: Provider | undefined;\n}\n\n/**\n * @type SuggestedAssetMeta\n *\n * Suggested asset by EIP747 meta data\n * @property id - Generated UUID associated with this suggested asset\n * @property time - Timestamp associated with this this suggested asset\n * @property type - Type type this suggested asset\n * @property asset - Asset suggested object\n * @property interactingAddress - Account address that requested watch asset\n */\ntype SuggestedAssetMeta = {\n id: string;\n time: number;\n type: string;\n asset: Token;\n interactingAddress: string;\n};\n\n/**\n * @type TokensState\n *\n * Assets controller state\n * @property tokens - List of tokens associated with the active network and address pair\n * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair\n * @property detectedTokens - List of detected tokens associated with the active network and address pair\n * @property allTokens - Object containing tokens by network and account\n * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account\n * @property allDetectedTokens - Object containing tokens detected with non-zero balances\n */\nexport type TokensState = {\n tokens: Token[];\n ignoredTokens: string[];\n detectedTokens: Token[];\n allTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n allIgnoredTokens: { [chainId: Hex]: { [key: string]: string[] } };\n allDetectedTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n};\n\n/**\n * The name of the {@link TokensController}.\n */\nconst controllerName = 'TokensController';\n\nexport type TokensControllerActions =\n | TokensControllerGetStateAction\n | TokensControllerAddDetectedTokensAction;\n\n// TODO: Once `TokensController` is upgraded to V2, rewrite this type using the `ControllerGetStateAction` type, which constrains `TokensState` as `Record<string, Json>`.\nexport type TokensControllerGetStateAction = {\n type: `${typeof controllerName}:getState`;\n handler: () => TokensState;\n};\n\nexport type TokensControllerAddDetectedTokensAction = {\n type: `${typeof controllerName}:addDetectedTokens`;\n handler: TokensController['addDetectedTokens'];\n};\n\n/**\n * The external actions available to the {@link TokensController}.\n */\nexport type AllowedActions =\n | AddApprovalRequest\n | NetworkControllerGetNetworkClientByIdAction;\n\n// TODO: Once `TokensController` is upgraded to V2, rewrite this type using the `ControllerStateChangeEvent` type, which constrains `TokensState` as `Record<string, Json>`.\nexport type TokensControllerStateChangeEvent = {\n type: `${typeof controllerName}:stateChange`;\n payload: [TokensState, Patch[]];\n};\n\nexport type TokensControllerEvents = TokensControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerNetworkDidChangeEvent\n | PreferencesControllerStateChangeEvent\n | TokenListStateChange;\n\n/**\n * The messenger of the {@link TokensController}.\n */\nexport type TokensControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokensControllerActions | AllowedActions,\n TokensControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport const getDefaultTokensState = (): TokensState => {\n return {\n tokens: [],\n ignoredTokens: [],\n detectedTokens: [],\n allTokens: {},\n allIgnoredTokens: {},\n allDetectedTokens: {},\n };\n};\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class TokensController extends BaseControllerV1<\n TokensConfig,\n TokensState & BaseState\n> {\n private readonly mutex = new Mutex();\n\n private abortController: AbortController;\n\n private readonly messagingSystem: TokensControllerMessenger;\n\n /**\n * Fetch metadata for a token.\n *\n * @param tokenAddress - The address of the token.\n * @returns The token metadata.\n */\n private async fetchTokenMetadata(\n tokenAddress: string,\n ): Promise<TokenListToken | undefined> {\n try {\n const token = await fetchTokenMetadata<TokenListToken>(\n this.config.chainId,\n tokenAddress,\n this.abortController.signal,\n );\n return token;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(TOKEN_METADATA_NO_SUPPORT_ERROR)\n ) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokensController';\n\n /**\n * Creates a TokensController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.config - Initial options used to configure this controller.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller messenger.\n */\n constructor({\n chainId: initialChainId,\n config,\n state,\n messenger,\n }: {\n chainId: Hex;\n config?: Partial<TokensConfig>;\n state?: Partial<TokensState>;\n messenger: TokensControllerMessenger;\n }) {\n super(config, state);\n\n this.defaultConfig = {\n selectedAddress: '',\n chainId: initialChainId,\n provider: undefined,\n ...config,\n };\n\n this.defaultState = {\n ...getDefaultTokensState(),\n ...state,\n };\n\n this.initialize();\n this.abortController = new AbortController();\n\n this.messagingSystem = messenger;\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addDetectedTokens` as const,\n this.addDetectedTokens.bind(this),\n );\n\n this.messagingSystem.subscribe(\n 'PreferencesController:stateChange',\n ({ selectedAddress }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId } = this.config;\n this.configure({ selectedAddress });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] ?? [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] ?? [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] ?? [],\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n ({ providerConfig }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { selectedAddress } = this.config;\n const { chainId } = providerConfig;\n this.abortController.abort();\n this.abortController = new AbortController();\n this.configure({ chainId });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'TokenListController:stateChange',\n ({ tokenList }) => {\n const { tokens } = this.state;\n if (tokens.length && !tokens[0].name) {\n this.updateTokensAttribute(tokenList, 'name');\n }\n },\n );\n }\n\n /**\n * Adds a token to the stored token list.\n *\n * @param options - The method argument object.\n * @param options.address - Hex address of the token contract.\n * @param options.symbol - Symbol of the token.\n * @param options.decimals - Number of decimals the token uses.\n * @param options.name - Name of the token.\n * @param options.image - Image of the token.\n * @param options.interactingAddress - The address of the account to add a token to.\n * @param options.networkClientId - Network Client ID.\n * @returns Current token list.\n */\n async addToken({\n address,\n symbol,\n decimals,\n name,\n image,\n interactingAddress,\n networkClientId,\n }: {\n address: string;\n symbol: string;\n decimals: number;\n name?: string;\n image?: string;\n interactingAddress?: string;\n networkClientId?: NetworkClientId;\n }): Promise<Token[]> {\n const { chainId, selectedAddress } = this.config;\n const releaseLock = await this.mutex.acquire();\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n let currentChainId = chainId;\n if (networkClientId) {\n currentChainId = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).configuration.chainId;\n }\n\n const accountAddress = interactingAddress || selectedAddress;\n const isInteractingWithWalletAccount = accountAddress === selectedAddress;\n\n try {\n address = toChecksumHexAddress(address);\n const tokens = allTokens[currentChainId]?.[accountAddress] || [];\n const ignoredTokens =\n allIgnoredTokens[currentChainId]?.[accountAddress] || [];\n const detectedTokens =\n allDetectedTokens[currentChainId]?.[accountAddress] || [];\n const newTokens: Token[] = [...tokens];\n const [isERC721, tokenMetadata] = await Promise.all([\n this._detectIsERC721(address, networkClientId),\n // TODO parameterize the token metadata fetch by networkClientId\n this.fetchTokenMetadata(address),\n ]);\n // TODO remove this once this method is fully parameterized by networkClientId\n if (!networkClientId && currentChainId !== this.config.chainId) {\n throw new Error(\n 'TokensController Error: Switched networks while adding token',\n );\n }\n const newEntry: Token = {\n address,\n symbol,\n decimals,\n image:\n image ||\n formatIconUrlWithProxy({\n chainId: currentChainId,\n tokenAddress: address,\n }),\n isERC721,\n aggregators: formatAggregatorNames(tokenMetadata?.aggregators || []),\n name,\n };\n const previousIndex = newTokens.findIndex(\n (token) => token.address.toLowerCase() === address.toLowerCase(),\n );\n if (previousIndex !== -1) {\n newTokens[previousIndex] = newEntry;\n } else {\n newTokens.push(newEntry);\n }\n\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase(),\n );\n const newDetectedTokens = detectedTokens.filter(\n (token) => token.address.toLowerCase() !== address.toLowerCase(),\n );\n\n const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } =\n this._getNewAllTokensState({\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n interactingAddress: accountAddress,\n interactingChainId: currentChainId,\n });\n\n let newState: Partial<TokensState> = {\n allTokens: newAllTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n };\n\n // Only update active tokens if user is interacting with their active wallet account.\n if (isInteractingWithWalletAccount) {\n newState = {\n ...newState,\n tokens: newTokens,\n ignoredTokens: newIgnoredTokens,\n detectedTokens: newDetectedTokens,\n };\n }\n\n this.update(newState);\n return newTokens;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a batch of tokens.\n *\n * @param tokensToImport - Array of tokens to import.\n * @param networkClientId - Optional network client ID used to determine interacting chain ID.\n */\n async addTokens(tokensToImport: Token[], networkClientId?: NetworkClientId) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const importedTokensMap: { [key: string]: true } = {};\n // Used later to dedupe imported tokens\n const newTokensMap = tokens.reduce((output, current) => {\n output[current.address] = current;\n return output;\n }, {} as { [address: string]: Token });\n try {\n tokensToImport.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators, name } =\n tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const formattedToken: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n aggregators,\n name,\n };\n newTokensMap[address] = formattedToken;\n importedTokensMap[address.toLowerCase()] = true;\n return formattedToken;\n });\n const newTokens = Object.values(newTokensMap);\n\n const newDetectedTokens = detectedTokens.filter(\n (token) => !importedTokensMap[token.address.toLowerCase()],\n );\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()],\n );\n\n let interactingChainId;\n if (networkClientId) {\n interactingChainId = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).configuration.chainId;\n }\n\n const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } =\n this._getNewAllTokensState({\n newTokens,\n newDetectedTokens,\n newIgnoredTokens,\n interactingChainId,\n });\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n ignoredTokens: newIgnoredTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Ignore a batch of tokens.\n *\n * @param tokenAddressesToIgnore - Array of token addresses to ignore.\n */\n ignoreTokens(tokenAddressesToIgnore: string[]) {\n const { ignoredTokens, detectedTokens, tokens } = this.state;\n const ignoredTokensMap: { [key: string]: true } = {};\n let newIgnoredTokens: string[] = [...ignoredTokens];\n\n const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {\n const checksumAddress = toChecksumHexAddress(address);\n ignoredTokensMap[address.toLowerCase()] = true;\n return checksumAddress;\n });\n newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses];\n const newDetectedTokens = detectedTokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n const newTokens = tokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n\n const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } =\n this._getNewAllTokensState({\n newIgnoredTokens,\n newDetectedTokens,\n newTokens,\n });\n\n this.update({\n ignoredTokens: newIgnoredTokens,\n tokens: newTokens,\n detectedTokens: newDetectedTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n allTokens: newAllTokens,\n });\n }\n\n /**\n * Adds a batch of detected tokens to the stored token list.\n *\n * @param incomingDetectedTokens - Array of detected tokens to be added or updated.\n * @param detectionDetails - An object containing the chain ID and address of the currently selected network on which the incomingDetectedTokens were detected.\n * @param detectionDetails.selectedAddress - the account address on which the incomingDetectedTokens were detected.\n * @param detectionDetails.chainId - the chainId on which the incomingDetectedTokens were detected.\n */\n async addDetectedTokens(\n incomingDetectedTokens: Token[],\n detectionDetails?: { selectedAddress: string; chainId: Hex },\n ) {\n const releaseLock = await this.mutex.acquire();\n\n // Get existing tokens for the chain + account\n const chainId = detectionDetails?.chainId ?? this.config.chainId;\n const accountAddress =\n detectionDetails?.selectedAddress ?? this.config.selectedAddress;\n\n const { allTokens, allDetectedTokens, allIgnoredTokens } = this.state;\n let newTokens = [...(allTokens?.[chainId]?.[accountAddress] ?? [])];\n let newDetectedTokens = [\n ...(allDetectedTokens?.[chainId]?.[accountAddress] ?? []),\n ];\n\n try {\n incomingDetectedTokens.forEach((tokenToAdd) => {\n const {\n address,\n symbol,\n decimals,\n image,\n aggregators,\n isERC721,\n name,\n } = tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const newEntry: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n isERC721,\n aggregators,\n name,\n };\n const previousImportedIndex = newTokens.findIndex(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousImportedIndex !== -1) {\n // Update existing data of imported token\n newTokens[previousImportedIndex] = newEntry;\n } else {\n const ignoredTokenIndex =\n allIgnoredTokens?.[chainId]?.[accountAddress]?.indexOf(address) ??\n -1;\n\n if (ignoredTokenIndex === -1) {\n // Add detected token\n const previousDetectedIndex = newDetectedTokens.findIndex(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousDetectedIndex !== -1) {\n newDetectedTokens[previousDetectedIndex] = newEntry;\n } else {\n newDetectedTokens.push(newEntry);\n }\n }\n }\n });\n\n const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState(\n {\n newTokens,\n newDetectedTokens,\n interactingAddress: accountAddress,\n interactingChainId: chainId,\n },\n );\n\n // We may be detecting tokens on a different chain/account pair than are currently configured.\n // Re-point `tokens` and `detectedTokens` to keep them referencing the current chain/account.\n const { chainId: currentChain, selectedAddress: currentAddress } =\n this.config;\n\n newTokens = newAllTokens?.[currentChain]?.[currentAddress] || [];\n newDetectedTokens =\n newAllDetectedTokens?.[currentChain]?.[currentAddress] || [];\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds isERC721 field to token object. This is called when a user attempts to add tokens that\n * were previously added which do not yet had isERC721 field.\n *\n * @param tokenAddress - The contract address of the token requiring the isERC721 field added.\n * @returns The new token object with the added isERC721 field.\n */\n async updateTokenType(tokenAddress: string) {\n const isERC721 = await this._detectIsERC721(tokenAddress);\n const { tokens } = this.state;\n const tokenIndex = tokens.findIndex((token) => {\n return token.address.toLowerCase() === tokenAddress.toLowerCase();\n });\n tokens[tokenIndex].isERC721 = isERC721;\n this.update({ tokens });\n return tokens[tokenIndex];\n }\n\n /**\n * This is a function that updates the tokens name for the tokens name if it is not defined.\n *\n * @param tokenList - Represents the fetched token list from service API\n * @param tokenAttribute - Represents the token attribute that we want to update on the token list\n */\n private updateTokensAttribute(\n tokenList: TokenListMap,\n tokenAttribute: keyof Token & keyof TokenListToken,\n ) {\n const { tokens } = this.state;\n\n const newTokens = tokens.map((token) => {\n const newToken = tokenList[token.address.toLowerCase()];\n\n return !token[tokenAttribute] && newToken?.[tokenAttribute]\n ? { ...token, [tokenAttribute]: newToken[tokenAttribute] }\n : { ...token };\n });\n\n this.update({ tokens: newTokens });\n }\n\n /**\n * Detects whether or not a token is ERC-721 compatible.\n *\n * @param tokenAddress - The token contract address.\n * @param networkClientId - Optional network client ID to fetch contract info with.\n * @returns A boolean indicating whether the token address passed in supports the EIP-721\n * interface.\n */\n async _detectIsERC721(\n tokenAddress: string,\n networkClientId?: NetworkClientId,\n ) {\n const checksumAddress = toChecksumHexAddress(tokenAddress);\n // if this token is already in our contract metadata map we don't need\n // to check against the contract\n if (contractsMap[checksumAddress]?.erc721 === true) {\n return Promise.resolve(true);\n } else if (contractsMap[checksumAddress]?.erc20 === true) {\n return Promise.resolve(false);\n }\n\n const tokenContract = this._createEthersContract(\n tokenAddress,\n abiERC721,\n networkClientId,\n );\n try {\n return await tokenContract.supportsInterface(ERC721_INTERFACE_ID);\n } catch (error) {\n // currently we see a variety of errors across different networks when\n // token contracts are not ERC721 compatible. We need to figure out a better\n // way of differentiating token interface types but for now if we get an error\n // we have to assume the token is not ERC721 compatible.\n return false;\n }\n }\n\n _getProvider(networkClientId?: NetworkClientId): Web3Provider {\n return new Web3Provider(\n // @ts-expect-error TODO: remove this annotation once the `Eip1193Provider` class is released\n networkClientId\n ? this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).provider\n : this.config.provider,\n );\n }\n\n _createEthersContract(\n tokenAddress: string,\n abi: string,\n networkClientId?: NetworkClientId,\n ): Contract {\n const web3provider = this._getProvider(networkClientId);\n const tokenContract = new Contract(tokenAddress, abi, web3provider);\n return tokenContract;\n }\n\n _generateRandomId(): string {\n return random();\n }\n\n /**\n * Adds a new suggestedAsset to the list of watched assets.\n * Parameters will be validated according to the asset type being watched.\n *\n * @param options - The method options.\n * @param options.asset - The asset to be watched. For now only ERC20 tokens are accepted.\n * @param options.type - The asset type.\n * @param options.interactingAddress - The address of the account that is requesting to watch the asset.\n * @param options.networkClientId - Network Client ID.\n * @returns A promise that resolves if the asset was watched successfully, and rejects otherwise.\n */\n async watchAsset({\n asset,\n type,\n interactingAddress,\n networkClientId,\n }: {\n asset: Token;\n type: string;\n interactingAddress?: string;\n networkClientId?: NetworkClientId;\n }): Promise<void> {\n if (type !== ERC20) {\n throw new Error(`Asset of type ${type} not supported`);\n }\n\n if (!asset.address) {\n throw rpcErrors.invalidParams('Address must be specified');\n }\n\n if (!isValidHexAddress(asset.address)) {\n throw rpcErrors.invalidParams(`Invalid address \"${asset.address}\"`);\n }\n\n // Validate contract\n\n if (await this._detectIsERC721(asset.address, networkClientId)) {\n throw rpcErrors.invalidParams(\n `Contract ${asset.address} must match type ${type}, but was detected as ${ERC721}`,\n );\n }\n\n const provider = this._getProvider(networkClientId);\n const isErc1155 = await safelyExecute(() =>\n new ERC1155Standard(provider).contractSupportsBase1155Interface(\n asset.address,\n ),\n );\n if (isErc1155) {\n throw rpcErrors.invalidParams(\n `Contract ${asset.address} must match type ${type}, but was detected as ${ERC1155}`,\n );\n }\n\n const erc20 = new ERC20Standard(provider);\n const [contractName, contractSymbol, contractDecimals] = await Promise.all([\n safelyExecute(() => erc20.getTokenName(asset.address)),\n safelyExecute(() => erc20.getTokenSymbol(asset.address)),\n safelyExecute(async () => erc20.getTokenDecimals(asset.address)),\n ]);\n\n asset.name = contractName;\n\n // Validate symbol\n\n if (!asset.symbol && !contractSymbol) {\n throw rpcErrors.invalidParams(\n 'A symbol is required, but was not found in either the request or contract',\n );\n }\n\n if (\n contractSymbol !== undefined &&\n asset.symbol !== undefined &&\n asset.symbol.toUpperCase() !== contractSymbol.toUpperCase()\n ) {\n throw rpcErrors.invalidParams(\n `The symbol in the request (${asset.symbol}) does not match the symbol in the contract (${contractSymbol})`,\n );\n }\n\n asset.symbol = contractSymbol ?? asset.symbol;\n if (typeof asset.symbol !== 'string') {\n throw rpcErrors.invalidParams(`Invalid symbol: not a string`);\n }\n\n if (asset.symbol.length > 11) {\n throw rpcErrors.invalidParams(\n `Invalid symbol \"${asset.symbol}\": longer than 11 characters`,\n );\n }\n\n // Validate decimals\n\n if (asset.decimals === undefined && contractDecimals === undefined) {\n throw rpcErrors.invalidParams(\n 'Decimals are required, but were not found in either the request or contract',\n );\n }\n\n if (\n contractDecimals !== undefined &&\n asset.decimals !== undefined &&\n String(asset.decimals) !== contractDecimals\n ) {\n throw rpcErrors.invalidParams(\n `The decimals in the request (${asset.decimals}) do not match the decimals in the contract (${contractDecimals})`,\n );\n }\n\n const decimalsStr = contractDecimals ?? asset.decimals;\n const decimalsNum = parseInt(decimalsStr as unknown as string, 10);\n if (!Number.isInteger(decimalsNum) || decimalsNum > 36 || decimalsNum < 0) {\n throw rpcErrors.invalidParams(\n `Invalid decimals \"${decimalsStr}\": must be an integer 0 <= 36`,\n );\n }\n asset.decimals = decimalsNum;\n\n const suggestedAssetMeta: SuggestedAssetMeta = {\n asset,\n id: this._generateRandomId(),\n time: Date.now(),\n type,\n interactingAddress: interactingAddress || this.config.selectedAddress,\n };\n\n await this._requestApproval(suggestedAssetMeta);\n\n const { address, symbol, decimals, name, image } = asset;\n await this.addToken({\n address,\n symbol,\n decimals,\n name,\n image,\n interactingAddress: suggestedAssetMeta.interactingAddress,\n networkClientId,\n });\n }\n\n /**\n * Takes a new tokens and ignoredTokens array for the current network/account combination\n * and returns new allTokens and allIgnoredTokens state to update to.\n *\n * @param params - Object that holds token params.\n * @param params.newTokens - The new tokens to set for the current network and selected account.\n * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account.\n * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account.\n * @param params.interactingAddress - The account address to use to store the tokens.\n * @param params.interactingChainId - The chainId to use to store the tokens.\n * @returns The updated `allTokens` and `allIgnoredTokens` state.\n */\n _getNewAllTokensState(params: {\n newTokens?: Token[];\n newIgnoredTokens?: string[];\n newDetectedTokens?: Token[];\n interactingAddress?: string;\n interactingChainId?: Hex;\n }) {\n const {\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n interactingAddress,\n interactingChainId,\n } = params;\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId, selectedAddress } = this.config;\n\n const userAddressToAddTokens = interactingAddress ?? selectedAddress;\n const chainIdToAddTokens = interactingChainId ?? chainId;\n\n let newAllTokens = allTokens;\n if (\n newTokens?.length ||\n (newTokens &&\n allTokens &&\n allTokens[chainIdToAddTokens] &&\n allTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkTokens = allTokens[chainIdToAddTokens];\n const newNetworkTokens = {\n ...networkTokens,\n ...{ [userAddressToAddTokens]: newTokens },\n };\n newAllTokens = {\n ...allTokens,\n ...{ [chainIdToAddTokens]: newNetworkTokens },\n };\n }\n\n let newAllIgnoredTokens = allIgnoredTokens;\n if (\n newIgnoredTokens?.length ||\n (newIgnoredTokens &&\n allIgnoredTokens &&\n allIgnoredTokens[chainIdToAddTokens] &&\n allIgnoredTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkIgnoredTokens = allIgnoredTokens[chainIdToAddTokens];\n const newIgnoredNetworkTokens = {\n ...networkIgnoredTokens,\n ...{ [userAddressToAddTokens]: newIgnoredTokens },\n };\n newAllIgnoredTokens = {\n ...allIgnoredTokens,\n ...{ [chainIdToAddTokens]: newIgnoredNetworkTokens },\n };\n }\n\n let newAllDetectedTokens = allDetectedTokens;\n if (\n newDetectedTokens?.length ||\n (newDetectedTokens &&\n allDetectedTokens &&\n allDetectedTokens[chainIdToAddTokens] &&\n allDetectedTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkDetectedTokens = allDetectedTokens[chainIdToAddTokens];\n const newDetectedNetworkTokens = {\n ...networkDetectedTokens,\n ...{ [userAddressToAddTokens]: newDetectedTokens },\n };\n newAllDetectedTokens = {\n ...allDetectedTokens,\n ...{ [chainIdToAddTokens]: newDetectedNetworkTokens },\n };\n }\n return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens };\n }\n\n /**\n * Removes all tokens from the ignored list.\n */\n clearIgnoredTokens() {\n this.update({ ignoredTokens: [], allIgnoredTokens: {} });\n }\n\n async _requestApproval(suggestedAssetMeta: SuggestedAssetMeta) {\n return this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id: suggestedAssetMeta.id,\n origin: ORIGIN_METAMASK,\n type: ApprovalType.WatchAsset,\n requestData: {\n id: suggestedAssetMeta.id,\n interactingAddress: suggestedAssetMeta.interactingAddress,\n asset: {\n address: suggestedAssetMeta.asset.address,\n decimals: suggestedAssetMeta.asset.decimals,\n symbol: suggestedAssetMeta.asset.symbol,\n image: suggestedAssetMeta.asset.image || null,\n },\n },\n },\n true,\n );\n }\n}\n\nexport default TokensController;\n"],"mappings":"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","names":[]}
@@ -1,235 +0,0 @@
1
- // src/NftDetectionController.ts
2
- import {
3
- fetchWithErrorHandling,
4
- toChecksumHexAddress,
5
- ChainId,
6
- NFT_API_BASE_URL
7
- } from "@metamask/controller-utils";
8
- import { StaticIntervalPollingControllerV1 } from "@metamask/polling-controller";
9
- var DEFAULT_INTERVAL = 18e4;
10
- var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => {
11
- BlockaidResultType2["Benign"] = "Benign";
12
- BlockaidResultType2["Spam"] = "Spam";
13
- BlockaidResultType2["Warning"] = "Warning";
14
- BlockaidResultType2["Malicious"] = "Malicious";
15
- return BlockaidResultType2;
16
- })(BlockaidResultType || {});
17
- var NftDetectionController = class extends StaticIntervalPollingControllerV1 {
18
- /**
19
- * Creates an NftDetectionController instance.
20
- *
21
- * @param options - The controller options.
22
- * @param options.chainId - The chain ID of the current network.
23
- * @param options.onNftsStateChange - Allows subscribing to assets controller state changes.
24
- * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.
25
- * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
26
- * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.
27
- * @param options.addNft - Add an NFT.
28
- * @param options.getNftApi - Gets the URL to fetch an NFT from OpenSea.
29
- * @param options.getNftState - Gets the current state of the Assets controller.
30
- * @param options.disabled - Represents previous value of useNftDetection. Used to detect changes of useNftDetection. Default value is true.
31
- * @param options.selectedAddress - Represents current selected address.
32
- * @param options.getNetworkClientById - Gets the network client by ID, from the NetworkController.
33
- * @param config - Initial options used to configure this controller.
34
- * @param state - Initial state to set on this controller.
35
- */
36
- constructor({
37
- chainId: initialChainId,
38
- getNetworkClientById,
39
- onPreferencesStateChange,
40
- onNetworkStateChange,
41
- getOpenSeaApiKey,
42
- addNft,
43
- getNftApi,
44
- getNftState,
45
- disabled: initialDisabled,
46
- selectedAddress: initialSelectedAddress
47
- }, config, state) {
48
- super(config, state);
49
- /**
50
- * Name of this controller used during composition
51
- */
52
- this.name = "NftDetectionController";
53
- /**
54
- * Checks whether network is mainnet or not.
55
- *
56
- * @returns Whether current network is mainnet.
57
- */
58
- this.isMainnet = () => this.config.chainId === ChainId.mainnet;
59
- this.isMainnetByNetworkClientId = (networkClient) => {
60
- return networkClient.configuration.chainId === ChainId.mainnet;
61
- };
62
- this.defaultConfig = {
63
- interval: DEFAULT_INTERVAL,
64
- chainId: initialChainId,
65
- selectedAddress: initialSelectedAddress,
66
- disabled: initialDisabled
67
- };
68
- this.initialize();
69
- this.getNftState = getNftState;
70
- this.getNetworkClientById = getNetworkClientById;
71
- onPreferencesStateChange(({ selectedAddress, useNftDetection }) => {
72
- const { selectedAddress: previouslySelectedAddress, disabled } = this.config;
73
- if (selectedAddress !== previouslySelectedAddress || !useNftDetection !== disabled) {
74
- this.configure({ selectedAddress, disabled: !useNftDetection });
75
- if (useNftDetection) {
76
- this.start();
77
- } else {
78
- this.stop();
79
- }
80
- }
81
- });
82
- onNetworkStateChange(({ providerConfig }) => {
83
- this.configure({
84
- chainId: providerConfig.chainId
85
- });
86
- });
87
- this.getOpenSeaApiKey = getOpenSeaApiKey;
88
- this.addNft = addNft;
89
- this.getNftApi = getNftApi;
90
- this.setIntervalLength(this.config.interval);
91
- }
92
- getOwnerNftApi({
93
- address,
94
- next
95
- }) {
96
- return `${NFT_API_BASE_URL}/users/${address}/tokens?chainIds=1&limit=50&includeTopBid=true&continuation=${next ?? ""}`;
97
- }
98
- async getOwnerNfts(address) {
99
- let nftApiResponse;
100
- let nfts = [];
101
- let next;
102
- do {
103
- nftApiResponse = await fetchWithErrorHandling({
104
- url: this.getOwnerNftApi({ address, next }),
105
- options: {
106
- headers: {
107
- Version: "1"
108
- }
109
- },
110
- timeout: 15e3
111
- });
112
- if (!nftApiResponse) {
113
- return nfts;
114
- }
115
- const newNfts = nftApiResponse.tokens.filter(
116
- (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true)
117
- );
118
- nfts = [...nfts, ...newNfts];
119
- } while (next = nftApiResponse.continuation);
120
- return nfts;
121
- }
122
- async _executePoll(networkClientId, options) {
123
- await this.detectNfts({ networkClientId, userAddress: options.address });
124
- }
125
- /**
126
- * Start polling for the currency rate.
127
- */
128
- async start() {
129
- if (!this.isMainnet() || this.disabled) {
130
- return;
131
- }
132
- await this.startPolling();
133
- }
134
- /**
135
- * Stop polling for the currency rate.
136
- */
137
- stop() {
138
- this.stopPolling();
139
- }
140
- stopPolling() {
141
- if (this.intervalId) {
142
- clearInterval(this.intervalId);
143
- }
144
- }
145
- /**
146
- * Starts a new polling interval.
147
- *
148
- * @param interval - An interval on which to poll.
149
- */
150
- async startPolling(interval) {
151
- interval && this.configure({ interval }, false, false);
152
- this.stopPolling();
153
- await this.detectNfts();
154
- this.intervalId = setInterval(async () => {
155
- await this.detectNfts();
156
- }, this.config.interval);
157
- }
158
- /**
159
- * Triggers asset ERC721 token auto detection on mainnet. Any newly detected NFTs are
160
- * added.
161
- *
162
- * @param options - Options bag.
163
- * @param options.networkClientId - The network client ID to detect NFTs on.
164
- * @param options.userAddress - The address to detect NFTs for.
165
- */
166
- async detectNfts({
167
- networkClientId,
168
- userAddress
169
- } = { userAddress: this.config.selectedAddress }) {
170
- if (!this.isMainnet() || this.disabled) {
171
- return;
172
- }
173
- if (!userAddress) {
174
- return;
175
- }
176
- const apiNfts = await this.getOwnerNfts(userAddress);
177
- const addNftPromises = apiNfts.map(async (nft) => {
178
- const {
179
- tokenId: token_id,
180
- contract,
181
- kind,
182
- image: image_url,
183
- imageSmall: image_thumbnail_url,
184
- metadata: { imageOriginal: image_original_url } = {},
185
- name,
186
- description,
187
- attributes,
188
- topBid,
189
- lastSale,
190
- rarityRank,
191
- rarityScore,
192
- collection
193
- } = nft.token;
194
- let ignored;
195
- const { ignoredNfts } = this.getNftState();
196
- if (ignoredNfts.length) {
197
- ignored = ignoredNfts.find((c) => {
198
- return c.address === toChecksumHexAddress(contract) && c.tokenId === token_id;
199
- });
200
- }
201
- if (!ignored) {
202
- const nftMetadata = Object.assign(
203
- {},
204
- { name },
205
- description && { description },
206
- image_url && { image: image_url },
207
- image_thumbnail_url && { imageThumbnail: image_thumbnail_url },
208
- image_original_url && { imageOriginal: image_original_url },
209
- kind && { standard: kind.toUpperCase() },
210
- lastSale && { lastSale },
211
- attributes && { attributes },
212
- topBid && { topBid },
213
- rarityRank && { rarityRank },
214
- rarityScore && { rarityScore },
215
- collection && { collection }
216
- );
217
- await this.addNft(contract, token_id, {
218
- nftMetadata,
219
- userAddress,
220
- source: "detected" /* Detected */,
221
- networkClientId
222
- });
223
- }
224
- });
225
- await Promise.all(addNftPromises);
226
- }
227
- };
228
- var NftDetectionController_default = NftDetectionController;
229
-
230
- export {
231
- BlockaidResultType,
232
- NftDetectionController,
233
- NftDetectionController_default
234
- };
235
- //# sourceMappingURL=chunk-ASFD56OL.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/NftDetectionController.ts"],"sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport {\n fetchWithErrorHandling,\n toChecksumHexAddress,\n ChainId,\n NFT_API_BASE_URL,\n} from '@metamask/controller-utils';\nimport type {\n NetworkClientId,\n NetworkController,\n NetworkState,\n NetworkClient,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { Source } from './constants';\nimport {\n type NftController,\n type NftState,\n type NftMetadata,\n} from './NftController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type ApiNft\n *\n * NFT object coming from OpenSea api\n * @property token_id - The NFT identifier\n * @property num_sales - Number of sales\n * @property background_color - The background color to be displayed with the item\n * @property image_url - URI of an image associated with this NFT\n * @property image_preview_url - URI of a smaller image associated with this NFT\n * @property image_thumbnail_url - URI of a thumbnail image associated with this NFT\n * @property image_original_url - URI of the original image associated with this NFT\n * @property animation_url - URI of a animation associated with this NFT\n * @property animation_original_url - URI of the original animation associated with this NFT\n * @property name - The NFT name\n * @property description - The NFT description\n * @property external_link - External link containing additional information\n * @property assetContract - The NFT contract information object\n * @property creator - The NFT owner information object\n * @property lastSale - When this item was last sold\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface ApiNft {\n token_id: string;\n num_sales: number | null;\n background_color: string | null;\n image_url: string | null;\n image_preview_url: string | null;\n image_thumbnail_url: string | null;\n image_original_url: string | null;\n animation_url: string | null;\n animation_original_url: string | null;\n name: string | null;\n description: string | null;\n external_link: string | null;\n asset_contract: ApiNftContract;\n creator: ApiNftCreator;\n last_sale: ApiNftLastSale | null;\n}\n\n/**\n * @type ApiNftContract\n *\n * NFT contract object coming from OpenSea api\n * @property address - Address of the NFT contract\n * @property asset_contract_type - The NFT type, it could be `semi-fungible` or `non-fungible`\n * @property created_date - Creation date\n * @property collection - Object containing the contract name and URI of an image associated\n * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property symbol - The NFT contract symbol\n * @property total_supply - Total supply of NFTs\n * @property description - The NFT contract description\n * @property external_link - External link containing additional information\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface ApiNftContract {\n address: string;\n asset_contract_type: string | null;\n created_date: string | null;\n schema_name: string | null;\n symbol: string | null;\n total_supply: string | null;\n description: string | null;\n external_link: string | null;\n collection: {\n name: string | null;\n image_url?: string | null;\n tokenCount?: string | null;\n };\n}\n\n/**\n * @type ApiNftLastSale\n *\n * NFT sale object coming from OpenSea api\n * @property event_timestamp - Object containing a `username`\n * @property total_price - URI of NFT image associated with this owner\n * @property transaction - Object containing transaction_hash and block_hash\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface ApiNftLastSale {\n event_timestamp: string;\n total_price: string;\n transaction: { transaction_hash: string; block_hash: string };\n}\n\n/**\n * @type ApiNftCreator\n *\n * NFT creator object coming from OpenSea api\n * @property user - Object containing a `username`\n * @property profile_img_url - URI of NFT image associated with this owner\n * @property address - The owner address\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface ApiNftCreator {\n user: { username: string };\n profile_img_url: string;\n address: string;\n}\n\n/**\n * @type NftDetectionConfig\n *\n * NftDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property chainId - Current chain ID\n * @property selectedAddress - Vault selected address\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface NftDetectionConfig extends BaseConfig {\n interval: number;\n chainId: Hex;\n selectedAddress: string;\n}\n\nexport type ReservoirResponse = {\n tokens: TokensResponse[];\n continuation?: string;\n};\n\nexport type TokensResponse = {\n token: TokenResponse;\n ownership: Ownership;\n market?: Market;\n blockaidResult?: Blockaid;\n};\n\nexport enum BlockaidResultType {\n Benign = 'Benign',\n Spam = 'Spam',\n Warning = 'Warning',\n Malicious = 'Malicious',\n}\n\nexport type Blockaid = {\n contract: string;\n chainId: number;\n result_type: BlockaidResultType;\n malicious_score: string;\n attack_types: object;\n};\n\nexport type Market = {\n floorAsk?: FloorAsk;\n topBid?: TopBid;\n};\n\nexport type TokenResponse = {\n chainId: number;\n contract: string;\n tokenId: string;\n kind?: string;\n name?: string;\n image?: string;\n imageSmall?: string;\n imageLarge?: string;\n metadata?: Metadata;\n description?: string;\n supply?: number;\n remainingSupply?: number;\n rarityScore?: number;\n rarity?: number;\n rarityRank?: number;\n media?: string;\n isFlagged?: boolean;\n isSpam?: boolean;\n isNsfw?: boolean;\n metadataDisabled?: boolean;\n lastFlagUpdate?: string;\n lastFlagChange?: string;\n collection?: Collection;\n lastSale?: LastSale;\n topBid?: TopBid;\n lastAppraisalValue?: number;\n attributes?: Attributes[];\n};\n\nexport type TopBid = {\n id?: string;\n price?: Price;\n source?: {\n id?: string;\n domain?: string;\n name?: string;\n icon?: string;\n url?: string;\n };\n};\n\nexport type LastSale = {\n saleId?: string;\n token?: {\n contract?: string;\n tokenId?: string;\n name?: string;\n image?: string;\n collection?: {\n id?: string;\n name?: string;\n };\n };\n orderSource?: string;\n orderSide?: 'ask' | 'bid';\n orderKind?: string;\n orderId?: string;\n from?: string;\n to?: string;\n amount?: string;\n fillSource?: string;\n block?: number;\n txHash?: string;\n logIndex?: number;\n batchIndex?: number;\n timestamp?: number;\n price?: Price;\n washTradingScore?: number;\n royaltyFeeBps?: number;\n marketplaceFeeBps?: number;\n paidFullRoyalty?: boolean;\n feeBreakdown?: FeeBreakdown[];\n isDeleted?: boolean;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type FeeBreakdown = {\n kind?: string;\n bps?: number;\n recipient?: string;\n source?: string;\n rawAmount?: string;\n};\n\nexport type Attributes = {\n key?: string;\n kind?: string;\n value: string;\n tokenCount?: number;\n onSaleCount?: number;\n floorAskPrice?: Price | null;\n topBidValue?: number | null;\n createdAt?: string;\n};\n\nexport type Collection = {\n id?: string;\n name?: string;\n slug?: string;\n symbol?: string;\n imageUrl?: string;\n image?: string;\n isSpam?: boolean;\n isNsfw?: boolean;\n creator?: string;\n tokenCount?: string;\n metadataDisabled?: boolean;\n openseaVerificationStatus?: string;\n floorAskPrice?: Price;\n royaltiesBps?: number;\n royalties?: Royalties[];\n};\n\nexport type Royalties = {\n bps?: number;\n recipient?: string;\n};\n\nexport type Ownership = {\n tokenCount?: string;\n onSaleCount?: string;\n floorAsk?: FloorAsk;\n acquiredAt?: string;\n};\n\nexport type FloorAsk = {\n id?: string;\n price?: Price;\n maker?: string;\n kind?: string;\n validFrom?: number;\n validUntil?: number;\n source?: Source;\n rawData?: Metadata;\n isNativeOffChainCancellable?: boolean;\n};\n\nexport type Price = {\n currency?: {\n contract?: string;\n name?: string;\n symbol?: string;\n decimals?: number;\n chainId?: number;\n };\n amount?: {\n raw?: string;\n decimal?: number;\n usd?: number;\n native?: number;\n };\n netAmount?: {\n raw?: string;\n decimal?: number;\n usd?: number;\n native?: number;\n };\n};\n\nexport type Metadata = {\n imageOriginal?: string;\n tokenURI?: string;\n};\n\n/**\n * Controller that passively polls on a set interval for NFT auto detection\n */\nexport class NftDetectionController extends StaticIntervalPollingControllerV1<\n NftDetectionConfig,\n BaseState\n> {\n private intervalId?: ReturnType<typeof setTimeout>;\n\n private getOwnerNftApi({\n address,\n next,\n }: {\n address: string;\n next?: string;\n }) {\n return `${NFT_API_BASE_URL}/users/${address}/tokens?chainIds=1&limit=50&includeTopBid=true&continuation=${\n next ?? ''\n }`;\n }\n\n private async getOwnerNfts(address: string) {\n let nftApiResponse: ReservoirResponse;\n let nfts: TokensResponse[] = [];\n let next;\n\n do {\n nftApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerNftApi({ address, next }),\n options: {\n headers: {\n Version: '1',\n },\n },\n timeout: 15000,\n });\n\n if (!nftApiResponse) {\n return nfts;\n }\n\n const newNfts = nftApiResponse.tokens.filter(\n (elm) =>\n elm.token.isSpam === false &&\n (elm.blockaidResult?.result_type\n ? elm.blockaidResult?.result_type === BlockaidResultType.Benign\n : true),\n );\n\n nfts = [...nfts, ...newNfts];\n } while ((next = nftApiResponse.continuation));\n\n return nfts;\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'NftDetectionController';\n\n private readonly getOpenSeaApiKey: () => string | undefined;\n\n private readonly addNft: NftController['addNft'];\n\n private readonly getNftApi: NftController['getNftApi'];\n\n private readonly getNftState: () => NftState;\n\n private readonly getNetworkClientById: NetworkController['getNetworkClientById'];\n\n /**\n * Creates an NftDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNftsStateChange - Allows subscribing to assets controller state changes.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.\n * @param options.addNft - Add an NFT.\n * @param options.getNftApi - Gets the URL to fetch an NFT from OpenSea.\n * @param options.getNftState - Gets the current state of the Assets controller.\n * @param options.disabled - Represents previous value of useNftDetection. Used to detect changes of useNftDetection. Default value is true.\n * @param options.selectedAddress - Represents current selected address.\n * @param options.getNetworkClientById - Gets the network client by ID, from the NetworkController.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n chainId: initialChainId,\n getNetworkClientById,\n onPreferencesStateChange,\n onNetworkStateChange,\n getOpenSeaApiKey,\n addNft,\n getNftApi,\n getNftState,\n disabled: initialDisabled,\n selectedAddress: initialSelectedAddress,\n }: {\n chainId: Hex;\n getNetworkClientById: NetworkController['getNetworkClientById'];\n onNftsStateChange: (listener: (nftsState: NftState) => void) => void;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n getOpenSeaApiKey: () => string | undefined;\n addNft: NftController['addNft'];\n getNftApi: NftController['getNftApi'];\n getNftState: () => NftState;\n disabled: boolean;\n selectedAddress: string;\n },\n config?: Partial<NftDetectionConfig>,\n state?: Partial<BaseState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n chainId: initialChainId,\n selectedAddress: initialSelectedAddress,\n disabled: initialDisabled,\n };\n this.initialize();\n this.getNftState = getNftState;\n this.getNetworkClientById = getNetworkClientById;\n onPreferencesStateChange(({ selectedAddress, useNftDetection }) => {\n const { selectedAddress: previouslySelectedAddress, disabled } =\n this.config;\n\n if (\n selectedAddress !== previouslySelectedAddress ||\n !useNftDetection !== disabled\n ) {\n this.configure({ selectedAddress, disabled: !useNftDetection });\n if (useNftDetection) {\n this.start();\n } else {\n this.stop();\n }\n }\n });\n\n onNetworkStateChange(({ providerConfig }) => {\n this.configure({\n chainId: providerConfig.chainId,\n });\n });\n this.getOpenSeaApiKey = getOpenSeaApiKey;\n this.addNft = addNft;\n this.getNftApi = getNftApi;\n this.setIntervalLength(this.config.interval);\n }\n\n async _executePoll(\n networkClientId: string,\n options: { address: string },\n ): Promise<void> {\n await this.detectNfts({ networkClientId, userAddress: options.address });\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise<void> {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectNfts();\n this.intervalId = setInterval(async () => {\n await this.detectNfts();\n }, this.config.interval);\n }\n\n /**\n * Checks whether network is mainnet or not.\n *\n * @returns Whether current network is mainnet.\n */\n isMainnet = (): boolean => this.config.chainId === ChainId.mainnet;\n\n isMainnetByNetworkClientId = (networkClient: NetworkClient): boolean => {\n return networkClient.configuration.chainId === ChainId.mainnet;\n };\n\n /**\n * Triggers asset ERC721 token auto detection on mainnet. Any newly detected NFTs are\n * added.\n *\n * @param options - Options bag.\n * @param options.networkClientId - The network client ID to detect NFTs on.\n * @param options.userAddress - The address to detect NFTs for.\n */\n async detectNfts(\n {\n networkClientId,\n userAddress,\n }: {\n networkClientId?: NetworkClientId;\n userAddress: string;\n } = { userAddress: this.config.selectedAddress },\n ) {\n /* istanbul ignore if */\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n /* istanbul ignore else */\n if (!userAddress) {\n return;\n }\n\n const apiNfts = await this.getOwnerNfts(userAddress);\n const addNftPromises = apiNfts.map(async (nft) => {\n const {\n tokenId: token_id,\n contract,\n kind,\n image: image_url,\n imageSmall: image_thumbnail_url,\n metadata: { imageOriginal: image_original_url } = {},\n name,\n description,\n attributes,\n topBid,\n lastSale,\n rarityRank,\n rarityScore,\n collection,\n } = nft.token;\n\n let ignored;\n /* istanbul ignore else */\n const { ignoredNfts } = this.getNftState();\n if (ignoredNfts.length) {\n ignored = ignoredNfts.find((c) => {\n /* istanbul ignore next */\n return (\n c.address === toChecksumHexAddress(contract) &&\n c.tokenId === token_id\n );\n });\n }\n\n /* istanbul ignore else */\n if (!ignored) {\n /* istanbul ignore next */\n const nftMetadata: NftMetadata = Object.assign(\n {},\n { name },\n description && { description },\n image_url && { image: image_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n kind && { standard: kind.toUpperCase() },\n lastSale && { lastSale },\n attributes && { attributes },\n topBid && { topBid },\n rarityRank && { rarityRank },\n rarityScore && { rarityScore },\n collection && { collection },\n );\n\n await this.addNft(contract, token_id, {\n nftMetadata,\n userAddress,\n source: Source.Detected,\n networkClientId,\n });\n }\n });\n await Promise.all(addNftPromises);\n }\n}\n\nexport default NftDetectionController;\n"],"mappings":"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","names":["BlockaidResultType"]}
@@ -1,35 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/crypto-compare.ts
2
- var _controllerutils = require('@metamask/controller-utils');
3
- var nativeSymbolOverrides = /* @__PURE__ */ new Map([["MNT", "MANTLE"]]);
4
- function getPricingURL(currentCurrency, nativeCurrency, includeUSDRate) {
5
- nativeCurrency = nativeCurrency.toUpperCase();
6
- const fsym = nativeSymbolOverrides.get(nativeCurrency) ?? nativeCurrency;
7
- return `https://min-api.cryptocompare.com/data/price?fsym=${fsym}&tsyms=${currentCurrency.toUpperCase()}${includeUSDRate && currentCurrency.toUpperCase() !== "USD" ? ",USD" : ""}`;
8
- }
9
- async function fetchExchangeRate(currency, nativeCurrency, includeUSDRate) {
10
- const json = await _controllerutils.handleFetch.call(void 0,
11
- getPricingURL(currency, nativeCurrency, includeUSDRate)
12
- );
13
- if (json.Response === "Error") {
14
- throw new Error(json.Message);
15
- }
16
- const conversionRate = Number(json[currency.toUpperCase()]);
17
- const usdConversionRate = Number(json.USD);
18
- if (!Number.isFinite(conversionRate)) {
19
- throw new Error(
20
- `Invalid response for ${currency.toUpperCase()}: ${json[currency.toUpperCase()]}`
21
- );
22
- }
23
- if (includeUSDRate && !Number.isFinite(usdConversionRate)) {
24
- throw new Error(`Invalid response for usdConversionRate: ${json.USD}`);
25
- }
26
- return {
27
- conversionRate,
28
- usdConversionRate
29
- };
30
- }
31
-
32
-
33
-
34
- exports.fetchExchangeRate = fetchExchangeRate;
35
- //# sourceMappingURL=chunk-DYH5P3VY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/crypto-compare.ts"],"names":[],"mappings":";AAAA,SAAS,mBAAmB;AAM5B,IAAM,wBAAwB,oBAAI,IAAI,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC;AAazD,SAAS,cACP,iBACA,gBACA,gBACA;AACA,mBAAiB,eAAe,YAAY;AAC5C,QAAM,OAAO,sBAAsB,IAAI,cAAc,KAAK;AAC1D,SACE,qDACG,IAAI,UAAU,gBAAgB,YAAY,CAAC,GAC3C,kBAAkB,gBAAgB,YAAY,MAAM,QAAQ,SAAS,EAAE;AAE9E;AAUA,eAAsB,kBACpB,UACA,gBACA,gBAIC;AACD,QAAM,OAAO,MAAM;AAAA,IACjB,cAAc,UAAU,gBAAgB,cAAc;AAAA,EACxD;AAUA,MAAI,KAAK,aAAa,SAAS;AAC7B,UAAM,IAAI,MAAM,KAAK,OAAO;AAAA,EAC9B;AAEA,QAAM,iBAAiB,OAAO,KAAK,SAAS,YAAY,CAAC,CAAC;AAE1D,QAAM,oBAAoB,OAAO,KAAK,GAAG;AACzC,MAAI,CAAC,OAAO,SAAS,cAAc,GAAG;AACpC,UAAM,IAAI;AAAA,MACR,wBAAwB,SAAS,YAAY,CAAC,KAC5C,KAAK,SAAS,YAAY,CAAC,CAC7B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,kBAAkB,CAAC,OAAO,SAAS,iBAAiB,GAAG;AACzD,UAAM,IAAI,MAAM,2CAA2C,KAAK,GAAG,EAAE;AAAA,EACvE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF","sourcesContent":["import { handleFetch } from '@metamask/controller-utils';\n\n/**\n * A map from native currency symbol to CryptoCompare identifier.\n * This is only needed when the values don't match.\n */\nconst nativeSymbolOverrides = new Map([['MNT', 'MANTLE']]);\n\n/**\n * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to\n * the given currency. Optionally, the conversion rate from the native currency to USD can also be\n * included in the response.\n *\n * @param currentCurrency - The currency to get a conversion rate for.\n * @param nativeCurrency - The native currency to convert from.\n * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be\n * included in the response as well.\n * @returns The API URL for getting the conversion rate.\n */\nfunction getPricingURL(\n currentCurrency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n) {\n nativeCurrency = nativeCurrency.toUpperCase();\n const fsym = nativeSymbolOverrides.get(nativeCurrency) ?? nativeCurrency;\n return (\n `https://min-api.cryptocompare.com/data/price?fsym=` +\n `${fsym}&tsyms=${currentCurrency.toUpperCase()}` +\n `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}`\n );\n}\n\n/**\n * Fetches the exchange rate for a given currency.\n *\n * @param currency - ISO 4217 currency code.\n * @param nativeCurrency - Symbol for base asset.\n * @param includeUSDRate - Whether to add the USD rate to the fetch.\n * @returns Promise resolving to exchange rate for given currency.\n */\nexport async function fetchExchangeRate(\n currency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n): Promise<{\n conversionRate: number;\n usdConversionRate: number;\n}> {\n const json = await handleFetch(\n getPricingURL(currency, nativeCurrency, includeUSDRate),\n );\n\n /*\n Example expected error response (if pair is not found)\n {\n Response: \"Error\",\n Message: \"cccagg_or_exchange market does not exist for this coin pair (ETH-<NON_EXISTENT_TOKEN>)\",\n HasWarning: false,\n }\n */\n if (json.Response === 'Error') {\n throw new Error(json.Message);\n }\n\n const conversionRate = Number(json[currency.toUpperCase()]);\n\n const usdConversionRate = Number(json.USD);\n if (!Number.isFinite(conversionRate)) {\n throw new Error(\n `Invalid response for ${currency.toUpperCase()}: ${\n json[currency.toUpperCase()]\n }`,\n );\n }\n\n if (includeUSDRate && !Number.isFinite(usdConversionRate)) {\n throw new Error(`Invalid response for usdConversionRate: ${json.USD}`);\n }\n\n return {\n conversionRate,\n usdConversionRate,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/TokenRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport {\n safelyExecute,\n toChecksumHexAddress,\n FALL_BACK_VS_CURRENCY,\n toHex,\n} from '@metamask/controller-utils';\nimport type {\n NetworkClientId,\n NetworkController,\n NetworkState,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport { createDeferredPromise, type Hex } from '@metamask/utils';\nimport { isEqual } from 'lodash';\n\nimport { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';\nimport { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\nimport type { TokensState } from './TokensController';\n\n/**\n * @type Token\n *\n * Token representation\n * @property address - Hex address of the token contract\n * @property decimals - Number of decimals the token uses\n * @property symbol - Symbol of the token\n * @property image - Image of the token, url or bit32 image\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface Token {\n address: string;\n decimals: number;\n symbol: string;\n aggregators?: string[];\n image?: string;\n balanceError?: unknown;\n isERC721?: boolean;\n name?: string;\n}\n\n/**\n * @type TokenRatesConfig\n *\n * Token rates controller configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property nativeCurrency - Current native currency selected to use base of rates\n * @property chainId - Current network chainId\n * @property tokens - List of tokens to track exchange rates for\n * @property threshold - Threshold to invalidate the supportedChains\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesConfig extends BaseConfig {\n interval: number;\n nativeCurrency: string;\n chainId: Hex;\n selectedAddress: string;\n allTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n allDetectedTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n threshold: number;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface ContractExchangeRates {\n [address: string]: number | undefined;\n}\n\nenum PollState {\n Active = 'Active',\n Inactive = 'Inactive',\n}\n\n/**\n * @type TokenRatesState\n *\n * Token rates controller state\n * @property contractExchangeRates - Hash of token contract addresses to exchange rates (single globally selected chain, will be deprecated soon)\n * @property contractExchangeRatesByChainId - Hash of token contract addresses to exchange rates keyed by chain ID and native currency (ticker)\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesState extends BaseState {\n contractExchangeRates: ContractExchangeRates;\n contractExchangeRatesByChainId: Record<\n Hex,\n Record<string, ContractExchangeRates>\n >;\n}\n\n/**\n * Uses the CryptoCompare API to fetch the exchange rate between one currency\n * and another, i.e., the multiplier to apply the amount of one currency in\n * order to convert it to another.\n *\n * @param args - The arguments to this function.\n * @param args.from - The currency to convert from.\n * @param args.to - The currency to convert to.\n * @returns The exchange rate between `fromCurrency` to `toCurrency` if one\n * exists, or null if one does not.\n */\nasync function getCurrencyConversionRate({\n from,\n to,\n}: {\n from: string;\n to: string;\n}) {\n const includeUSDRate = false;\n try {\n const result = await fetchNativeCurrencyExchangeRate(\n to,\n from,\n includeUSDRate,\n );\n return result.conversionRate;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n ) {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Controller that passively polls on a set interval for token-to-fiat exchange rates\n * for tokens stored in the TokensController\n */\nexport class TokenRatesController extends StaticIntervalPollingControllerV1<\n TokenRatesConfig,\n TokenRatesState\n> {\n private handle?: ReturnType<typeof setTimeout>;\n\n #pollState = PollState.Inactive;\n\n #tokenPricesService: AbstractTokenPricesService;\n\n #inProcessExchangeRateUpdates: Record<`${Hex}:${string}`, Promise<void>> = {};\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenRatesController';\n\n private readonly getNetworkClientById: NetworkController['getNetworkClientById'];\n\n /**\n * Creates a TokenRatesController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The polling interval in ms\n * @param options.threshold - The duration in ms before metadata fetched from CoinGecko is considered stale\n * @param options.getNetworkClientById - Gets the network client with the given id from the NetworkController.\n * @param options.chainId - The chain ID of the current network.\n * @param options.ticker - The ticker for the current network.\n * @param options.selectedAddress - The current selected address.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onTokensStateChange - Allows subscribing to token controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network state changes.\n * @param options.tokenPricesService - An object in charge of retrieving token prices.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n interval = 3 * 60 * 1000,\n threshold = 6 * 60 * 60 * 1000,\n getNetworkClientById,\n chainId: initialChainId,\n ticker: initialTicker,\n selectedAddress: initialSelectedAddress,\n onPreferencesStateChange,\n onTokensStateChange,\n onNetworkStateChange,\n tokenPricesService,\n }: {\n interval?: number;\n threshold?: number;\n getNetworkClientById: NetworkController['getNetworkClientById'];\n chainId: Hex;\n ticker: string;\n selectedAddress: string;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onTokensStateChange: (\n listener: (tokensState: TokensState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n tokenPricesService: AbstractTokenPricesService;\n },\n config?: Partial<TokenRatesConfig>,\n state?: Partial<TokenRatesState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval,\n threshold,\n disabled: false,\n nativeCurrency: initialTicker,\n chainId: initialChainId,\n selectedAddress: initialSelectedAddress,\n allTokens: {}, // TODO: initialize these correctly, maybe as part of BaseControllerV2 migration\n allDetectedTokens: {},\n };\n\n this.defaultState = {\n contractExchangeRates: {},\n contractExchangeRatesByChainId: {},\n };\n this.initialize();\n this.setIntervalLength(interval);\n this.getNetworkClientById = getNetworkClientById;\n this.#tokenPricesService = tokenPricesService;\n\n if (config?.disabled) {\n this.configure({ disabled: true }, false, false);\n }\n\n onPreferencesStateChange(async ({ selectedAddress }) => {\n if (this.config.selectedAddress !== selectedAddress) {\n this.configure({ selectedAddress });\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n\n onTokensStateChange(async ({ allTokens, allDetectedTokens }) => {\n const previousTokenAddresses = this.#getTokenAddresses(\n this.config.chainId,\n );\n this.configure({ allTokens, allDetectedTokens });\n const newTokenAddresses = this.#getTokenAddresses(this.config.chainId);\n if (\n !isEqual(previousTokenAddresses, newTokenAddresses) &&\n this.#pollState === PollState.Active\n ) {\n await this.updateExchangeRates();\n }\n });\n\n onNetworkStateChange(async ({ providerConfig }) => {\n const { chainId, ticker } = providerConfig;\n if (\n this.config.chainId !== chainId ||\n this.config.nativeCurrency !== ticker\n ) {\n this.update({ contractExchangeRates: {} });\n this.configure({ chainId, nativeCurrency: ticker });\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n }\n\n /**\n * Get the user's tokens for the given chain.\n *\n * @param chainId - The chain ID.\n * @returns The list of tokens addresses for the current chain\n */\n #getTokenAddresses(chainId: Hex): Hex[] {\n const { allTokens, allDetectedTokens } = this.config;\n const tokens = allTokens[chainId]?.[this.config.selectedAddress] || [];\n const detectedTokens =\n allDetectedTokens[chainId]?.[this.config.selectedAddress] || [];\n\n return [\n ...new Set(\n [...tokens, ...detectedTokens].map((token) =>\n toHex(toChecksumHexAddress(token.address)),\n ),\n ),\n ].sort();\n }\n\n /**\n * Start (or restart) polling.\n */\n async start() {\n this.#stopPoll();\n this.#pollState = PollState.Active;\n await this.#poll();\n }\n\n /**\n * Stop polling.\n */\n stop() {\n this.#stopPoll();\n this.#pollState = PollState.Inactive;\n }\n\n /**\n * Clear the active polling timer, if present.\n */\n #stopPoll() {\n if (this.handle) {\n clearTimeout(this.handle);\n }\n }\n\n /**\n * Poll for exchange rate updates.\n */\n async #poll() {\n await safelyExecute(() => this.updateExchangeRates());\n\n // Poll using recursive `setTimeout` instead of `setInterval` so that\n // requests don't stack if they take longer than the polling interval\n this.handle = setTimeout(() => {\n this.#poll();\n }, this.config.interval);\n }\n\n /**\n * Updates exchange rates for all tokens.\n */\n async updateExchangeRates() {\n const { chainId, nativeCurrency } = this.config;\n await this.updateExchangeRatesByChainId({\n chainId,\n nativeCurrency,\n });\n }\n\n /**\n * Updates exchange rates for all tokens.\n *\n * @param options - The options to fetch exchange rates.\n * @param options.chainId - The chain ID.\n * @param options.nativeCurrency - The ticker for the chain.\n */\n async updateExchangeRatesByChainId({\n chainId,\n nativeCurrency,\n }: {\n chainId: Hex;\n nativeCurrency: string;\n }) {\n if (this.disabled) {\n return;\n }\n\n const tokenAddresses = this.#getTokenAddresses(chainId);\n if (tokenAddresses.length === 0) {\n return;\n }\n\n const updateKey: `${Hex}:${string}` = `${chainId}:${nativeCurrency}`;\n if (updateKey in this.#inProcessExchangeRateUpdates) {\n // This prevents redundant updates\n // This promise is resolved after the in-progress update has finished,\n // and state has been updated.\n await this.#inProcessExchangeRateUpdates[updateKey];\n return;\n }\n\n const {\n promise: inProgressUpdate,\n resolve: updateSucceeded,\n reject: updateFailed,\n } = createDeferredPromise({ suppressUnhandledRejection: true });\n this.#inProcessExchangeRateUpdates[updateKey] = inProgressUpdate;\n\n try {\n const newContractExchangeRates = await this.#fetchAndMapExchangeRates({\n tokenAddresses,\n chainId,\n nativeCurrency,\n });\n\n const existingContractExchangeRates = this.state.contractExchangeRates;\n const updatedContractExchangeRates =\n chainId === this.config.chainId &&\n nativeCurrency === this.config.nativeCurrency\n ? newContractExchangeRates\n : existingContractExchangeRates;\n\n const existingContractExchangeRatesForChainId =\n this.state.contractExchangeRatesByChainId[chainId] ?? {};\n const updatedContractExchangeRatesForChainId = {\n ...this.state.contractExchangeRatesByChainId,\n [chainId]: {\n ...existingContractExchangeRatesForChainId,\n [nativeCurrency]: {\n ...existingContractExchangeRatesForChainId[nativeCurrency],\n ...newContractExchangeRates,\n },\n },\n };\n\n this.update({\n contractExchangeRates: updatedContractExchangeRates,\n contractExchangeRatesByChainId: updatedContractExchangeRatesForChainId,\n });\n updateSucceeded();\n } catch (error: unknown) {\n updateFailed(error);\n throw error;\n } finally {\n delete this.#inProcessExchangeRateUpdates[updateKey];\n }\n }\n\n /**\n * Uses the token prices service to retrieve exchange rates for tokens in a\n * particular currency.\n *\n * If the price API does not support the given chain ID, returns an empty\n * object.\n *\n * If the price API does not support the given currency, retrieves exchange\n * rates in a known currency instead, then converts those rates using the\n * exchange rate between the known currency and desired currency.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.chainId - The EIP-155 ID of the chain where the tokens live.\n * @param args.nativeCurrency - The native currency in which to request\n * exchange rates.\n * @returns A map from token address to its exchange rate in the native\n * currency, or an empty map if no exchange rates can be obtained for the\n * chain ID.\n */\n async #fetchAndMapExchangeRates({\n tokenAddresses,\n chainId,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n chainId: Hex;\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n if (!this.#tokenPricesService.validateChainIdSupported(chainId)) {\n return tokenAddresses.reduce((obj, tokenAddress) => {\n return {\n ...obj,\n [tokenAddress]: undefined,\n };\n }, {});\n }\n\n if (this.#tokenPricesService.validateCurrencySupported(nativeCurrency)) {\n return await this.#fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId,\n nativeCurrency,\n });\n }\n\n return await this.#fetchAndMapExchangeRatesForUnsupportedNativeCurrency({\n tokenAddresses,\n nativeCurrency,\n });\n }\n\n /**\n * Updates token rates for the given networkClientId\n *\n * @param networkClientId - The network client ID used to get a ticker value.\n * @returns The controller state.\n */\n async _executePoll(networkClientId: NetworkClientId): Promise<void> {\n const networkClient = this.getNetworkClientById(networkClientId);\n await this.updateExchangeRatesByChainId({\n chainId: networkClient.configuration.chainId,\n nativeCurrency: networkClient.configuration.ticker,\n });\n }\n\n /**\n * Retrieves prices in the given currency for the given tokens on the given\n * chain. Ensures that token addresses are checksum addresses.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.chainId - The EIP-155 ID of the chain where the tokens live.\n * @param args.nativeCurrency - The native currency in which to request\n * prices.\n * @returns A map of the token addresses (as checksums) to their prices in the\n * native currency.\n */\n async #fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n chainId: Hex;\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n const tokenPricesByTokenAddress = await reduceInBatchesSerially<\n Hex,\n Awaited<ReturnType<AbstractTokenPricesService['fetchTokenPrices']>>\n >({\n values: [...tokenAddresses].sort(),\n batchSize: TOKEN_PRICES_BATCH_SIZE,\n eachBatch: async (allTokenPricesByTokenAddress, batch) => {\n const tokenPricesByTokenAddressForBatch =\n await this.#tokenPricesService.fetchTokenPrices({\n tokenAddresses: batch,\n chainId,\n currency: nativeCurrency,\n });\n\n return {\n ...allTokenPricesByTokenAddress,\n ...tokenPricesByTokenAddressForBatch,\n };\n },\n initialResult: {},\n });\n\n return Object.entries(tokenPricesByTokenAddress).reduce(\n (obj, [tokenAddress, tokenPrice]) => {\n return {\n ...obj,\n [tokenAddress]: tokenPrice?.value,\n };\n },\n {},\n );\n }\n\n /**\n * If the price API does not support a given native currency, then we need to\n * convert it to a fallback currency and feed that currency into the price\n * API, then convert the prices to our desired native currency.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.nativeCurrency - The native currency in which to request\n * prices.\n * @returns A map of the token addresses (as checksums) to their prices in the\n * native currency.\n */\n async #fetchAndMapExchangeRatesForUnsupportedNativeCurrency({\n tokenAddresses,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n const [\n contractExchangeRates,\n fallbackCurrencyToNativeCurrencyConversionRate,\n ] = await Promise.all([\n this.#fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId: this.config.chainId,\n nativeCurrency: FALL_BACK_VS_CURRENCY,\n }),\n getCurrencyConversionRate({\n from: FALL_BACK_VS_CURRENCY,\n to: nativeCurrency,\n }),\n ]);\n\n if (fallbackCurrencyToNativeCurrencyConversionRate === null) {\n return {};\n }\n\n return Object.entries(contractExchangeRates).reduce(\n (obj, [tokenAddress, tokenValue]) => {\n return {\n ...obj,\n [tokenAddress]: tokenValue\n ? tokenValue * fallbackCurrencyToNativeCurrencyConversionRate\n : undefined,\n };\n },\n {},\n );\n }\n}\n\nexport default TokenRatesController;\n"]}