diff --git a/CHANGELOG.md b/CHANGELOG.md index a3fa114654c..ddee844be5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Current Main Branch + +## 7.12.1 - Dec 5, 2023 +### Fixed +- [#7991](https://github.com/MetaMask/metamask-mobile/pull/7991): fix: patch for token rates controller with coin gecko endpoint + ## 7.12.0 - Dec 4, 2023 ### Added - [#7037](https://github.com/MetaMask/metamask-mobile/pull/7037): feat(off-ramp): add off-ramp feature diff --git a/android/app/build.gradle b/android/app/build.gradle index 06721df6aec..c2ce7232cc5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -137,8 +137,8 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1214 - versionName "7.12.0" + versionCode 1221 + versionName "7.12.1" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index 134f4e40e33..55481ca85c7 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -401,7 +401,7 @@ const Tokens: React.FC = ({ tokens }) => { TokenDetectionController.detectTokens(), AccountTrackerController.refresh(), CurrencyRateController.start(), - TokenRatesController.poll(), + TokenRatesController.updateExchangeRates(), ]; await Promise.all(actions); setRefreshing(false); diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 68b713adb1e..1812f6c8e83 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -147,11 +147,6 @@ const Wallet = ({ navigation }: any) => { }, [navigate, providerConfig.chainId]); const { colors: themeColors } = useTheme(); - useEffect(() => { - const { TokenRatesController } = Engine.context; - TokenRatesController.poll(); - }, [tokens]); - /** * Check to see if we need to show What's New modal */ diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 08a44dcf93d..aeb9229b708 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -539,25 +539,20 @@ class Engine { }, { interval: 10000 }, ), - new TokenRatesController( - { - onTokensStateChange: (listener) => - tokensController.subscribe(listener), - onCurrencyRateStateChange: (listener) => - this.controllerMessenger.subscribe( - `${currencyRateController.name}:stateChange`, - listener, - ), - onNetworkStateChange: (listener) => - this.controllerMessenger.subscribe( - AppConstants.NETWORK_STATE_CHANGE_EVENT, - listener, - ), - }, - { - chainId: networkController.state.providerConfig.chainId, - }, - ), + new TokenRatesController({ + onTokensStateChange: (listener) => tokensController.subscribe(listener), + onNetworkStateChange: (listener) => + this.controllerMessenger.subscribe( + AppConstants.NETWORK_STATE_CHANGE_EVENT, + listener, + ), + onPreferencesStateChange: (listener) => + preferencesController.subscribe(listener), + chainId: networkController.state.providerConfig.chainId, + ticker: networkController.state.providerConfig.ticker ?? 'ETH', + selectedAddress: preferencesController.state.selectedAddress, + coinGeckoHeader: process.env.COIN_GECKO_HEADER as string, + }), new TransactionController({ blockTracker: networkController.getProviderAndBlockTracker().blockTracker, @@ -809,12 +804,14 @@ class Engine { TokenDetectionController, TokenListController, TransactionController, + TokenRatesController, } = this.context; TokenListController.start(); NftDetectionController.start(); TokenDetectionController.start(); TransactionController.startIncomingTransactionPolling(); + TokenRatesController.start(); } configureControllersOnNetworkChange() { diff --git a/bitrise.yml b/bitrise.yml index f675ec30051..ba83c50fbca 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -989,10 +989,10 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.12.0 + VERSION_NAME: 7.12.1 - opts: is_expand: false - VERSION_NUMBER: 1214 + VERSION_NUMBER: 1221 - opts: is_expand: false FLASK_VERSION_NAME: 0.0.4 diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index edcac158dde..805d790e394 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1343,7 +1343,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1214; + CURRENT_PROJECT_VERSION = 1221; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1377,7 +1377,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.12.0; + MARKETING_VERSION = 7.12.1; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1406,7 +1406,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1214; + CURRENT_PROJECT_VERSION = 1221; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1440,7 +1440,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.12.0; + MARKETING_VERSION = 7.12.1; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1693,7 +1693,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1214; + CURRENT_PROJECT_VERSION = 1221; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1731,7 +1731,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.12.0; + MARKETING_VERSION = 7.12.1; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1763,7 +1763,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1214; + CURRENT_PROJECT_VERSION = 1221; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1801,7 +1801,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.12.0; + MARKETING_VERSION = 7.12.1; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index da9c19cfc17..ecb32187844 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.12.0", + "version": "7.12.1", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", diff --git a/patches/@metamask+assets-controllers+7.0.0.patch b/patches/@metamask+assets-controllers+7.0.0.patch index 9e86d7654ab..6508379f858 100644 --- a/patches/@metamask+assets-controllers+7.0.0.patch +++ b/patches/@metamask+assets-controllers+7.0.0.patch @@ -492,180 +492,440 @@ index 4ed4990..df0524b 100644 } } diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.ts b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.ts -index 8bb7c20..f6f43f2 100644 +index 8bb7c20..810d961 100644 --- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.ts +++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.ts -@@ -57,6 +57,7 @@ export interface TokenRatesConfig extends BaseConfig { +@@ -1,7 +1,8 @@ +-import { BaseController, BaseConfig, BaseState } from '@metamask/base-controller'; ++import type { BaseConfig, BaseState } from '@metamask/base-controller'; ++import { BaseController } from '@metamask/base-controller'; + import type { NetworkState } from '@metamask/network-controller'; ++import type { PreferencesState } from '@metamask/preferences-controller'; + import type { TokensState } from './TokensController'; +-import type { CurrencyRateState } from './CurrencyRateController'; + /** + * @type CoinGeckoResponse + * +@@ -40,6 +41,7 @@ export interface Token { + image?: string; + balanceError?: unknown; + isERC721?: boolean; ++ name?: string; + } + /** + * @type TokenRatesConfig +@@ -55,7 +57,17 @@ export interface TokenRatesConfig extends BaseConfig { + interval: number; + nativeCurrency: string; chainId: string; - tokens: Token[]; +- tokens: Token[]; ++ selectedAddress: string; ++ allTokens: { ++ [chainId: string]: { ++ [key: string]: Token[]; ++ }; ++ }; ++ allDetectedTokens: { ++ [chainId: string]: { ++ [key: string]: Token[]; ++ }; ++ }; threshold: number; -+ previousNativeCurrency: string; } interface ContractExchangeRates { - [address: string]: number | undefined; -@@ -148,8 +149,11 @@ export declare class TokenRatesController extends BaseController; +@@ -76,6 +88,7 @@ export interface TokenRatesState extends BaseState { + * for tokens stored in the TokensController + */ + export declare class TokenRatesController extends BaseController { ++ #private; + private handle?; + private tokenList; + private supportedChains; +@@ -88,41 +101,32 @@ export declare class TokenRatesController extends BaseController void) => void; + onTokensStateChange: (listener: (tokensState: TokensState) => void) => void; +- onCurrencyRateStateChange: (listener: (currencyRateState: CurrencyRateState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + }, config?: Partial, state?: Partial); /** - * Updates exchange rates for all tokens. -+ * -+ * @param forceFetch - Force a currency rate update, even when the -+ * native currency has not recently changed. +- * Sets a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token rates. ++ * Start (or restart) polling. */ -- updateExchangeRates(): Promise; -+ updateExchangeRates(forceFetch?: boolean): Promise; +- poll(interval?: number): Promise; ++ start(): Promise; /** - * Checks if the active network's native currency is supported by the coingecko API. - * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. +- * Sets a new chainId. +- * +- * TODO: Replace this with a method. +- * +- * @param _chainId - The current chain ID. +- */ +- set chainId(_chainId: string); +- get chainId(): string; +- /** +- * Sets a new token list to track prices. +- * +- * TODO: Replace this with a method. +- * +- * @param tokens - List of tokens to track exchange rates for. ++ * Stop polling. + */ +- set tokens(tokens: Token[]); +- get tokens(): Token[]; ++ stop(): void; + /** + * Fetches a pairs of token address and native currency. + * diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.js b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.js -index e3f81e9..b8e07f6 100644 +index e3f81e9..10994ef 100644 --- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.js +++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.js -@@ -1,4 +1,11 @@ - "use strict"; -+// PATCH NOTES: -+// This patch is based upon the core branch `assets-controllers-v7-token-rate-controller-patch` -+// It includes the following changes: -+// - Prevent unnecessary fetch calls -+// - Fetch calls are currently limited to one per polling cycle, or upon a change in native currency. -+// - Clear conversion rates immediately when an update starts, before waiting for the result -+// - Clear conversion rates upon update failure - var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { -@@ -77,6 +84,7 @@ class TokenRatesController extends base_controller_1.BaseController { - chainId: '', - tokens: [], +@@ -8,11 +8,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ 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"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; ++var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { ++ if (kind === "m") throw new TypeError("Private method is not writable"); ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); ++ 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"); ++ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; ++}; ++var _TokenRatesController_instances, _TokenRatesController_pollState, _TokenRatesController_updateTokenList, _TokenRatesController_stopPoll, _TokenRatesController_poll; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.TokenRatesController = void 0; + const base_controller_1 = require("@metamask/base-controller"); + const controller_utils_1 = require("@metamask/controller-utils"); + const crypto_compare_1 = require("./crypto-compare"); ++const fast_deep_equal_1 = __importDefault(require("fast-deep-equal")); ++var PollState; ++(function (PollState) { ++ PollState["Active"] = "Active"; ++ PollState["Inactive"] = "Inactive"; ++})(PollState || (PollState = {})); ++ + const CoinGeckoApi = { + BASE_URL: 'https://api.coingecko.com/api/v3', + getTokenPriceURL(chainSlug, query) { +@@ -49,15 +68,20 @@ class TokenRatesController extends base_controller_1.BaseController { + * Creates a TokenRatesController instance. + * + * @param options - The controller options. ++ * @param options.chainId - The chain ID of the current network. ++ * @param options.ticker - The ticker for the current network. ++ * @param options.selectedAddress - The current selected address. ++ * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onTokensStateChange - Allows subscribing to token controller state changes. +- * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network state changes. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ +- constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }, config, state) { ++ constructor({ chainId: initialChainId, ticker: initialTicker, selectedAddress: initialSelectedAddress, onPreferencesStateChange, onTokensStateChange, onNetworkStateChange, coinGeckoHeader}, config, state) { + super(config, state); ++ _TokenRatesController_instances.add(this); + this.tokenList = []; ++ this.coinGeckoHeader = coinGeckoHeader; + this.supportedChains = { + timestamp: 0, + data: null, +@@ -66,16 +90,19 @@ class TokenRatesController extends base_controller_1.BaseController { + timestamp: 0, + data: [], + }; ++ _TokenRatesController_pollState.set(this, PollState.Inactive); + /** + * Name of this controller used during composition + */ + this.name = 'TokenRatesController'; + this.defaultConfig = { + disabled: false, +- interval: 3 * 60 * 1000, +- nativeCurrency: 'eth', +- chainId: '', +- tokens: [], ++ interval: 30 * 60 * 1000, ++ nativeCurrency: initialTicker, ++ chainId: initialChainId, ++ selectedAddress: initialSelectedAddress, ++ allTokens: {}, ++ allDetectedTokens: {}, threshold: 6 * 60 * 60 * 1000, -+ previousNativeCurrency: '', }; this.defaultState = { - contractExchangeRates: {}, -@@ -93,7 +101,6 @@ class TokenRatesController extends base_controller_1.BaseController { - }); - onNetworkStateChange(({ providerConfig }) => { - const { chainId } = providerConfig; +@@ -85,60 +112,57 @@ class TokenRatesController extends base_controller_1.BaseController { + if (config === null || config === void 0 ? void 0 : config.disabled) { + this.configure({ disabled: true }, false, false); + } +- onTokensStateChange(({ tokens, detectedTokens }) => { +- this.configure({ tokens: [...tokens, ...detectedTokens] }); +- }); +- onCurrencyRateStateChange((currencyRateState) => { +- this.configure({ nativeCurrency: currencyRateState.nativeCurrency }); +- }); +- onNetworkStateChange(({ providerConfig }) => { +- const { chainId } = providerConfig; - this.update({ contractExchangeRates: {} }); - this.configure({ chainId }); - }); - this.poll(); -@@ -107,7 +114,7 @@ class TokenRatesController extends base_controller_1.BaseController { +- this.configure({ chainId }); +- }); +- this.poll(); ++ __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this); ++ onPreferencesStateChange(({ selectedAddress }) => __awaiter(this, void 0, void 0, function* () { ++ if (this.config.selectedAddress !== selectedAddress) { ++ this.configure({ selectedAddress }); ++ const isEqual = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this); ++ if (__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active && !isEqual) { ++ yield this.updateExchangeRates(); ++ } ++ } ++ })); ++ onTokensStateChange(({ allTokens, allDetectedTokens }) => __awaiter(this, void 0, void 0, function* () { ++ // These two state properties are assumed to be immutable ++ if (this.config.allTokens !== allTokens || ++ this.config.allDetectedTokens !== allDetectedTokens) { ++ this.configure({ allTokens, allDetectedTokens }); ++ const isEqual = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this); ++ if (__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active && !isEqual) { ++ yield this.updateExchangeRates(); ++ } ++ } ++ })); ++ onNetworkStateChange(({ providerConfig }) => __awaiter(this, void 0, void 0, function* () { ++ const { chainId, ticker } = providerConfig; ++ if (this.config.chainId !== chainId || ++ this.config.nativeCurrency !== ticker) { ++ this.update({ contractExchangeRates: {} }); ++ this.configure({ chainId, nativeCurrency: ticker ?? 'ETH' }); ++ const isEqual = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this); ++ if (__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active && !isEqual) { ++ yield this.updateExchangeRates(); ++ } ++ } ++ })); + } ++ + /** +- * Sets a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token rates. ++ * Start (or restart) polling. + */ +- poll(interval) { ++ start() { return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); +- interval && this.configure({ interval }, false, false); +- this.handle && clearTimeout(this.handle); - yield (0, controller_utils_1.safelyExecute)(() => this.updateExchangeRates()); -+ yield (0, controller_utils_1.safelyExecute)(() => this.updateExchangeRates(true)); - this.handle = setTimeout(() => { - this.poll(this.config.interval); - }, this.config.interval); -@@ -201,8 +208,11 @@ class TokenRatesController extends base_controller_1.BaseController { +- this.handle = setTimeout(() => { +- this.poll(this.config.interval); +- }, this.config.interval); ++ __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_stopPoll).call(this); ++ __classPrivateFieldSet(this, _TokenRatesController_pollState, PollState.Active, "f"); ++ yield __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_poll).call(this); + }); } /** - * Updates exchange rates for all tokens. -+ * -+ * @param forceFetch - Force a currency rate update, even when the -+ * native currency has not recently changed. +- * Sets a new chainId. +- * +- * TODO: Replace this with a method. +- * +- * @param _chainId - The current chain ID. ++ * Stop polling. */ -- updateExchangeRates() { -+ updateExchangeRates(forceFetch = false) { - return __awaiter(this, void 0, void 0, function* () { - if (this.tokenList.length === 0 || this.disabled) { - return; -@@ -217,7 +227,13 @@ class TokenRatesController extends base_controller_1.BaseController { - } - else { +- set chainId(_chainId) { +- !this.disabled && (0, controller_utils_1.safelyExecute)(() => this.updateExchangeRates()); +- } +- get chainId() { +- throw new Error('Property only used for setting'); +- } +- /** +- * Sets a new token list to track prices. +- * +- * TODO: Replace this with a method. +- * +- * @param tokens - List of tokens to track exchange rates for. +- */ +- set tokens(tokens) { +- this.tokenList = tokens; +- !this.disabled && (0, controller_utils_1.safelyExecute)(() => this.updateExchangeRates()); +- } +- get tokens() { +- throw new Error('Property only used for setting'); ++ stop() { ++ __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_stopPoll).call(this); ++ __classPrivateFieldSet(this, _TokenRatesController_pollState, PollState.Inactive, "f"); + } + /** + * Fetches a pairs of token address and native currency. +@@ -148,11 +172,34 @@ class TokenRatesController extends base_controller_1.BaseController { + * @returns The exchange rates for the given pairs. + */ + fetchExchangeRate(chainSlug, vsCurrency) { +- return __awaiter(this, void 0, void 0, function* () { +- const tokenPairs = this.tokenList.map((token) => token.address).join(','); +- const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`; +- return (0, controller_utils_1.handleFetch)(CoinGeckoApi.getTokenPriceURL(chainSlug, query)); +- }); ++ const tokenPairs = this.tokenList; ++ ++ const tokenPairsChunks = (chunkSize) => ++ tokenPairs.reduce((resultArray, item, index) => { ++ const chunkIndex = Math.floor(index / chunkSize); ++ if (!resultArray[chunkIndex]) { ++ resultArray[chunkIndex] = []; ++ } ++ ++ resultArray[chunkIndex].push(item); ++ ++ return resultArray; ++ }, []); ++ ++ ++ const tokensPairsChunks = tokenPairsChunks(20); ++ ++ return tokensPairsChunks.map((tokenPairsChunk) => { ++ const query = `contract_addresses=${tokenPairsChunk.join( ++ ',', ++ )}&vs_currencies=${vsCurrency.toLowerCase()}`; ++ return (0, controller_utils_1.handleFetch)(CoinGeckoApi.getTokenPriceURL(chainSlug, query), { ++ headers: { ++ 'Content-Type': 'application/json', ++ 'X-Requested-With': this.coinGeckoHeader ++ }, ++ });; ++ }); + } + /** + * Checks if the current native currency is a supported vs currency to use +@@ -210,12 +257,11 @@ class TokenRatesController extends base_controller_1.BaseController { + const slug = yield this.getChainSlug(); + let newContractExchangeRates = {}; + if (!slug) { +- this.tokenList.forEach((token) => { +- const address = (0, controller_utils_1.toChecksumHexAddress)(token.address); ++ this.tokenList.forEach((tokenAddress) => { ++ const address = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress); + newContractExchangeRates[address] = undefined; + }); +- } +- else { ++ } else { const { nativeCurrency } = this.config; -- newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); -+ // Only fetch if native curency is different or when forced (used in polling) -+ if (this.config.previousNativeCurrency !== nativeCurrency || forceFetch) { -+ newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); -+ } -+ else { -+ newContractExchangeRates = this.state.contractExchangeRates; -+ } + newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); } - this.update({ contractExchangeRates: newContractExchangeRates }); - }); -@@ -236,46 +252,47 @@ class TokenRatesController extends base_controller_1.BaseController { - */ +@@ -237,14 +283,23 @@ class TokenRatesController extends base_controller_1.BaseController { fetchAndMapExchangeRates(nativeCurrency, slug) { return __awaiter(this, void 0, void 0, function* () { -+ // Clear exchange rates before fetching new ones. -+ this.update({ contractExchangeRates: {} }); -+ // Set new native currency to prevent this method from being called multiple times -+ this.configure({ previousNativeCurrency: nativeCurrency }); const contractExchangeRates = {}; - // check if native currency is supported as a vs_currency by the API - const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency(nativeCurrency); -- if (nativeCurrencySupported) { -- // If it is we can do a simple fetch against the CoinGecko API ++ if(this.tokenList.length === 0){ ++ return contractExchangeRates; ++ } ++ ++ const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency( ++ nativeCurrency, ++ ); ++ + if (nativeCurrencySupported) { + // If it is we can do a simple fetch against the CoinGecko API - const prices = yield this.fetchExchangeRate(slug, nativeCurrency); - this.tokenList.forEach((token) => { - const price = prices[token.address.toLowerCase()]; - contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(token.address)] = price -- ? price[nativeCurrency.toLowerCase()] -- : 0; -- }); -- } -- else { -- // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates -- // in token/fallback-currency format and convert them to expected token/nativeCurrency format. ++ const res = yield Promise.all( ++ this.fetchExchangeRate(slug, nativeCurrency), ++ ); ++ const prices = Object.assign({}, ...res); ++ this.tokenList.forEach((tokenAddress) => { ++ const price = prices[tokenAddress.toLowerCase()]; ++ contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(tokenAddress)] = price + ? price[nativeCurrency.toLowerCase()] + : 0; + }); +@@ -252,14 +307,14 @@ class TokenRatesController extends base_controller_1.BaseController { + else { + // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates + // in token/fallback-currency format and convert them to expected token/nativeCurrency format. - let tokenExchangeRates; -- let vsCurrencyToNativeCurrencyConversionRate = 0; -- try { -- [ ++ let tokenExchangeRatesResponse; + let vsCurrencyToNativeCurrencyConversionRate = 0; + try { + [ - tokenExchangeRates, -- { conversionRate: vsCurrencyToNativeCurrencyConversionRate }, -- ] = yield Promise.all([ -+ try { -+ // check if native currency is supported as a vs_currency by the API -+ const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency(nativeCurrency); -+ if (nativeCurrencySupported) { -+ // If it is we can do a simple fetch against the CoinGecko API -+ const prices = yield this.fetchExchangeRate(slug, nativeCurrency); -+ this.tokenList.forEach((token) => { -+ const price = prices[token.address.toLowerCase()]; -+ contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(token.address)] = price -+ ? price[nativeCurrency.toLowerCase()] -+ : 0; -+ }); -+ } -+ else { -+ // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates -+ // in token/fallback-currency format and convert them to expected token/nativeCurrency format. -+ const [tokenExchangeRates, { conversionRate: vsCurrencyToNativeCurrencyConversionRate = 0 },] = yield Promise.all([ - this.fetchExchangeRate(slug, controller_utils_1.FALL_BACK_VS_CURRENCY), ++ tokenExchangeRatesResponse, + { conversionRate: vsCurrencyToNativeCurrencyConversionRate }, + ] = yield Promise.all([ +- this.fetchExchangeRate(slug, controller_utils_1.FALL_BACK_VS_CURRENCY), ++ Promise.all(this.fetchExchangeRate(slug, controller_utils_1.FALL_BACK_VS_CURRENCY)), (0, crypto_compare_1.fetchExchangeRate)(nativeCurrency, controller_utils_1.FALL_BACK_VS_CURRENCY, false), ]); -- } -- catch (error) { -- if (error instanceof Error && -- error.message.includes('market does not exist for this coin pair')) { -- return {}; -+ for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { -+ const tokenToVsCurrencyConversionRate = conversion[controller_utils_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; -+ contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(tokenAddress)] = -+ tokenToVsCurrencyConversionRate * -+ vsCurrencyToNativeCurrencyConversionRate; - } -- throw error; } -- for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { -- const tokenToVsCurrencyConversionRate = conversion[controller_utils_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; -- contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(tokenAddress)] = -- tokenToVsCurrencyConversionRate * -- vsCurrencyToNativeCurrencyConversionRate; -+ } -+ catch (error) { -+ // If there is an error catched we clean the state -+ this.update({ contractExchangeRates: {} }); -+ if (error instanceof Error && -+ error.message.includes('market does not exist for this coin pair')) { -+ return {}; +@@ -270,6 +325,7 @@ class TokenRatesController extends base_controller_1.BaseController { + } + throw error; } -+ throw error; - } - return contractExchangeRates; - }); ++ const tokenExchangeRates = Object.assign({}, ...tokenExchangeRatesResponse); + for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { + const tokenToVsCurrencyConversionRate = conversion[controller_utils_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; + contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(tokenAddress)] = +@@ -282,5 +338,29 @@ class TokenRatesController extends base_controller_1.BaseController { + } + } + exports.TokenRatesController = TokenRatesController; ++_TokenRatesController_pollState = new WeakMap(), _TokenRatesController_instances = new WeakSet(), _TokenRatesController_updateTokenList = function _TokenRatesController_updateTokenList() { ++ var _a, _b; ++ const { allTokens, allDetectedTokens } = this.config; ++ const oldTokenList = this.tokenList; ++ const tokens = ((_a = allTokens[this.config.chainId]) === null || _a === void 0 ? void 0 : _a[this.config.selectedAddress]) || []; ++ const detectedTokens = ((_b = allDetectedTokens[this.config.chainId]) === null || _b === void 0 ? void 0 : _b[this.config.selectedAddress]) || ++ []; ++ const newTokenList = [...tokens, ...detectedTokens].map((token) => token.address); ++ this.tokenList = newTokenList; ++ return (0, fast_deep_equal_1.default)(oldTokenList, newTokenList); ++}, _TokenRatesController_stopPoll = function _TokenRatesController_stopPoll() { ++ if (this.handle) { ++ clearTimeout(this.handle); ++ } ++}, _TokenRatesController_poll = function _TokenRatesController_poll() { ++ return __awaiter(this, void 0, void 0, function* () { ++ yield (0, controller_utils_1.safelyExecute)(() => this.updateExchangeRates()); ++ // Poll using recursive `setTimeout` instead of `setInterval` so that ++ // requests don't stack if they take longer than the polling interval ++ this.handle = setTimeout(() => { ++ __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_poll).call(this); ++ }, this.config.interval); ++ }); ++}; + exports.default = TokenRatesController; + //# sourceMappingURL=TokenRatesController.js.map +\ No newline at end of file diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.js b/node_modules/@metamask/assets-controllers/dist/TokensController.js index 0e03b88..bd7ddbd 100644 --- a/node_modules/@metamask/assets-controllers/dist/TokensController.js