From 2554c92057fd1dafe2a8deb0aa6d0cbabc8f42d8 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Wed, 1 May 2024 18:40:52 -0400 Subject: [PATCH 01/69] chore: move crypto-compare service into new directory --- packages/assets-controllers/src/CurrencyRateController.ts | 2 +- packages/assets-controllers/src/TokenRatesController.ts | 2 +- .../src/{ => crypto-compare-service}/crypto-compare.test.ts | 0 .../src/{ => crypto-compare-service}/crypto-compare.ts | 0 .../assets-controllers/src/crypto-compare-service/index.ts | 3 +++ 5 files changed, 5 insertions(+), 2 deletions(-) rename packages/assets-controllers/src/{ => crypto-compare-service}/crypto-compare.test.ts (100%) rename packages/assets-controllers/src/{ => crypto-compare-service}/crypto-compare.ts (100%) create mode 100644 packages/assets-controllers/src/crypto-compare-service/index.ts diff --git a/packages/assets-controllers/src/CurrencyRateController.ts b/packages/assets-controllers/src/CurrencyRateController.ts index bf3509f310..62a8069c5a 100644 --- a/packages/assets-controllers/src/CurrencyRateController.ts +++ b/packages/assets-controllers/src/CurrencyRateController.ts @@ -14,7 +14,7 @@ import type { import { StaticIntervalPollingController } from '@metamask/polling-controller'; import { Mutex } from 'async-mutex'; -import { fetchExchangeRate as defaultFetchExchangeRate } from './crypto-compare'; +import { fetchExchangeRate as defaultFetchExchangeRate } from './crypto-compare-service'; /** * @type CurrencyRateState diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index 2863bb7b96..099e500004 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -16,7 +16,7 @@ import { createDeferredPromise, type Hex } from '@metamask/utils'; import { isEqual } from 'lodash'; import { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil'; -import { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare'; +import { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare-service'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service'; import type { TokensState } from './TokensController'; diff --git a/packages/assets-controllers/src/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts similarity index 100% rename from packages/assets-controllers/src/crypto-compare.test.ts rename to packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts diff --git a/packages/assets-controllers/src/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts similarity index 100% rename from packages/assets-controllers/src/crypto-compare.ts rename to packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts diff --git a/packages/assets-controllers/src/crypto-compare-service/index.ts b/packages/assets-controllers/src/crypto-compare-service/index.ts new file mode 100644 index 0000000000..aa13ad704e --- /dev/null +++ b/packages/assets-controllers/src/crypto-compare-service/index.ts @@ -0,0 +1,3 @@ +import { fetchExchangeRate } from './crypto-compare'; + +export { fetchExchangeRate }; From 4a029d83fb698a695d173f37ced4c84ef4155aaf Mon Sep 17 00:00:00 2001 From: gantunesr Date: Wed, 1 May 2024 18:41:14 -0400 Subject: [PATCH 02/69] feat: add non-evm rates controller --- .../BtcRateController/BtcRateController.ts | 200 ++++++++++++++++++ .../src/BtcRateController/index.ts | 8 + packages/assets-controllers/src/index.ts | 8 + 3 files changed, 216 insertions(+) create mode 100644 packages/assets-controllers/src/BtcRateController/BtcRateController.ts create mode 100644 packages/assets-controllers/src/BtcRateController/index.ts diff --git a/packages/assets-controllers/src/BtcRateController/BtcRateController.ts b/packages/assets-controllers/src/BtcRateController/BtcRateController.ts new file mode 100644 index 0000000000..d42556d7fb --- /dev/null +++ b/packages/assets-controllers/src/BtcRateController/BtcRateController.ts @@ -0,0 +1,200 @@ +import { BaseController } from '@metamask/base-controller'; +import type { + RestrictedControllerMessenger, + ControllerGetStateAction, + ControllerStateChangeEvent, +} from '@metamask/base-controller'; + +import { fetchExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; + +/** + * Represents the state structure for the BtcRateController. + */ +export type BtcRateState = { + currency: string; + rates: Record< + string, + { + conversionDate: number | null; + conversionRate: number | null; + usdConversionRate: number | null; + } + >; +}; + +const name = 'BtcRateController'; + +/** + * Type definition for BtcRateController state change events. + */ +export type BtcRateStateChange = ControllerStateChangeEvent< + typeof name, + BtcRateState +>; + +export type BtcRateControllerEvents = BtcRateStateChange; + +/** + * Type definition for getting the BtcRateController. + */ +export type GetBtcRateState = ControllerGetStateAction< + typeof name, + BtcRateState +>; + +export type BtcRateControllerActions = GetBtcRateState; + +type BtcRateMessenger = RestrictedControllerMessenger< + typeof name, + BtcRateControllerActions, + BtcRateControllerEvents, + never, + never +>; + +const metadata = { + currency: { persist: true, anonymous: true }, + rates: { persist: true, anonymous: true }, +}; + +const defaultState = { + currency: 'usd', + rates: { + btc: { + conversionDate: 0, + conversionRate: 0, + usdConversionRate: null, + }, + }, +}; + +const DEFAULT_CURRENCIES = { + btc: 'BTC', +}; + +export class BtcRateController extends BaseController< + typeof name, + BtcRateState, + BtcRateMessenger +> { + readonly #fetchExchangeRate; + + readonly #onStart; + + readonly #onStop; + + readonly #includeUsdRate; + + #intervalId: NodeJS.Timeout | undefined; + + #intervalLength: number | undefined = 1000; + + /** + * Creates a BtcRateController instance. + * + * @param options - Constructor options. + * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. + * @param options.interval - The polling interval, in milliseconds. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. + * @param options.onStart - Optional callback to be executed when the polling stops. + * @param options.onStop - Optional callback to be executed when the polling starts. + */ + constructor({ + interval = 180000, + messenger, + state, + includeUsdRate, + fetchExchangeRate = defaultFetchExchangeRate, + onStart, + onStop, + }: { + includeUsdRate?: boolean; + interval?: number; + messenger: BtcRateMessenger; + state?: Partial; + fetchExchangeRate?: typeof defaultFetchExchangeRate; + onStart?: () => Promise; + onStop?: () => Promise; + }) { + super({ + name, + metadata, + messenger, + state: { ...defaultState, ...state }, + }); + this.#includeUsdRate = includeUsdRate; + this.#fetchExchangeRate = fetchExchangeRate; + this.#onStart = onStart; + this.#onStop = onStop; + this.#setIntervalLength(interval); + } + + /** + * Sets the interval length for polling. + * + * @param intervalLength - The length of the interval in milliseconds. + */ + #setIntervalLength(intervalLength: number) { + this.#intervalLength = intervalLength; + } + + /** + * Updates the BTC rates by fetching new data. + */ + async #updateRates(): Promise { + const { currency } = this.state; + const response = await this.#fetchExchangeRate( + currency, + DEFAULT_CURRENCIES.btc, + ); + const { conversionRate, usdConversionRate } = response; + this.update(() => { + return { + rates: { + btc: { + conversionDate: Date.now() / 1000, + conversionRate, + usdConversionRate, + }, + }, + currency, + }; + }); + } + + /** + * Executes the polling operation to update rates. + */ + async #executePoll(): Promise { + await this.#updateRates(); + } + + /** + * Starts the polling process. + */ + async start(): Promise { + if (this.#intervalId) { + return; + } + + await this.#onStart?.(); + this.#intervalId = setInterval(() => { + this.#executePoll().catch(console.error); + }, this.#intervalLength); + } + + /** + * Stops the polling process. + */ + async stop(): Promise { + if (!this.#intervalId) { + return; + } + + clearInterval(this.#intervalId); + this.#intervalId = undefined; + await this.#onStop?.(); + } +} diff --git a/packages/assets-controllers/src/BtcRateController/index.ts b/packages/assets-controllers/src/BtcRateController/index.ts new file mode 100644 index 0000000000..04db2505b9 --- /dev/null +++ b/packages/assets-controllers/src/BtcRateController/index.ts @@ -0,0 +1,8 @@ +export { BtcRateController } from './BtcRateController'; +export type { + BtcRateState, + BtcRateStateChange, + BtcRateControllerEvents, + GetBtcRateState, + BtcRateControllerActions, +} from './BtcRateController'; diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 306280f429..aeb4d74e00 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -58,3 +58,11 @@ export { CodefiTokenPricesServiceV2, SUPPORTED_CHAIN_IDS, } from './token-prices-service'; +export { BtcRateController } from './BtcRateController'; +export type { + BtcRateState, + BtcRateStateChange, + BtcRateControllerEvents, + GetBtcRateState, + BtcRateControllerActions, +} from './BtcRateController'; From 272168d1038b881469e92a296ac996e08ed7f59e Mon Sep 17 00:00:00 2001 From: gantunesr Date: Wed, 1 May 2024 19:22:07 -0400 Subject: [PATCH 03/69] refactor: rename controller --- .../src/BtcRateController/index.ts | 8 ---- .../RatesController.ts} | 37 ++++++++++--------- .../src/RatesController/index.ts | 8 ++++ packages/assets-controllers/src/index.ts | 14 +++---- 4 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 packages/assets-controllers/src/BtcRateController/index.ts rename packages/assets-controllers/src/{BtcRateController/BtcRateController.ts => RatesController/RatesController.ts} (85%) create mode 100644 packages/assets-controllers/src/RatesController/index.ts diff --git a/packages/assets-controllers/src/BtcRateController/index.ts b/packages/assets-controllers/src/BtcRateController/index.ts deleted file mode 100644 index 04db2505b9..0000000000 --- a/packages/assets-controllers/src/BtcRateController/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { BtcRateController } from './BtcRateController'; -export type { - BtcRateState, - BtcRateStateChange, - BtcRateControllerEvents, - GetBtcRateState, - BtcRateControllerActions, -} from './BtcRateController'; diff --git a/packages/assets-controllers/src/BtcRateController/BtcRateController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts similarity index 85% rename from packages/assets-controllers/src/BtcRateController/BtcRateController.ts rename to packages/assets-controllers/src/RatesController/RatesController.ts index d42556d7fb..c000cfeb96 100644 --- a/packages/assets-controllers/src/BtcRateController/BtcRateController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -10,7 +10,7 @@ import { fetchExchangeRate as defaultFetchExchangeRate } from '../crypto-compare /** * Represents the state structure for the BtcRateController. */ -export type BtcRateState = { +export type RatesState = { currency: string; rates: Record< string, @@ -22,32 +22,29 @@ export type BtcRateState = { >; }; -const name = 'BtcRateController'; +const name = 'RatesController'; /** * Type definition for BtcRateController state change events. */ -export type BtcRateStateChange = ControllerStateChangeEvent< +export type RatesStateChange = ControllerStateChangeEvent< typeof name, - BtcRateState + RatesState >; -export type BtcRateControllerEvents = BtcRateStateChange; +export type RatesControllerEvents = RatesStateChange; /** * Type definition for getting the BtcRateController. */ -export type GetBtcRateState = ControllerGetStateAction< - typeof name, - BtcRateState ->; +export type GetRatesState = ControllerGetStateAction; -export type BtcRateControllerActions = GetBtcRateState; +export type RatesControllerActions = GetRatesState; -type BtcRateMessenger = RestrictedControllerMessenger< +type RatesMessenger = RestrictedControllerMessenger< typeof name, - BtcRateControllerActions, - BtcRateControllerEvents, + RatesControllerActions, + RatesControllerEvents, never, never >; @@ -70,12 +67,13 @@ const defaultState = { const DEFAULT_CURRENCIES = { btc: 'BTC', + sol: 'SOL', }; -export class BtcRateController extends BaseController< +export class RatesController extends BaseController< typeof name, - BtcRateState, - BtcRateMessenger + RatesState, + RatesMessenger > { readonly #fetchExchangeRate; @@ -112,8 +110,8 @@ export class BtcRateController extends BaseController< }: { includeUsdRate?: boolean; interval?: number; - messenger: BtcRateMessenger; - state?: Partial; + messenger: RatesMessenger; + state?: Partial; fetchExchangeRate?: typeof defaultFetchExchangeRate; onStart?: () => Promise; onStop?: () => Promise; @@ -145,9 +143,12 @@ export class BtcRateController extends BaseController< */ async #updateRates(): Promise { const { currency } = this.state; + // const cryptoCurrencies: string = + // Object.values(DEFAULT_CURRENCIES).join(','); const response = await this.#fetchExchangeRate( currency, DEFAULT_CURRENCIES.btc, + this.#includeUsdRate, ); const { conversionRate, usdConversionRate } = response; this.update(() => { diff --git a/packages/assets-controllers/src/RatesController/index.ts b/packages/assets-controllers/src/RatesController/index.ts new file mode 100644 index 0000000000..618b89fd32 --- /dev/null +++ b/packages/assets-controllers/src/RatesController/index.ts @@ -0,0 +1,8 @@ +export { RatesController } from './RatesController'; +export type { + RatesState, + RatesStateChange, + RatesControllerEvents, + GetRatesState, + RatesControllerActions, +} from './RatesController'; diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index aeb4d74e00..492a399637 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -58,11 +58,11 @@ export { CodefiTokenPricesServiceV2, SUPPORTED_CHAIN_IDS, } from './token-prices-service'; -export { BtcRateController } from './BtcRateController'; +export { RatesController } from './RatesController'; export type { - BtcRateState, - BtcRateStateChange, - BtcRateControllerEvents, - GetBtcRateState, - BtcRateControllerActions, -} from './BtcRateController'; + RatesState, + RatesStateChange, + RatesControllerEvents, + GetRatesState, + RatesControllerActions, +} from './RatesController'; From 77613e116855cb357514d25bddc4e87e0c308653 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Wed, 8 May 2024 09:55:46 -0400 Subject: [PATCH 04/69] feat: add support for multiprice --- .../crypto-compare.test.ts | 20 ++++- .../crypto-compare-service/crypto-compare.ts | 86 ++++++++++++++++--- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts index dc1958c895..0857f9636f 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts @@ -1,6 +1,6 @@ import nock from 'nock'; -import { fetchExchangeRate } from './crypto-compare'; +import { fetchExchangeRate, fetchMultiExchangeRate } from './crypto-compare'; const cryptoCompareHost = 'https://min-api.cryptocompare.com'; @@ -149,4 +149,22 @@ describe('CryptoCompare', () => { const { conversionRate } = await fetchExchangeRate('USD', 'MNT'); expect(conversionRate).toBe(123); }); + + it('should return CAD and USD conversion rate for BTC, ETH, and SOL', async () => { + nock(cryptoCompareHost) + .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=CAD,USD') + .reply(200, { + BTC: { CAD: 2000.42, USD: 1000.42 }, + ETH: { CAD: 3000.42, USD: 2000.42 }, + SOL: { CAD: 4000.42, USD: 3000.42 }, + }); + + const response = await fetchMultiExchangeRate('CAD', 'BTC,ETH,SOL', true); + + expect(response).toStrictEqual({ + btc: { conversionRate: 2000.42, usdConversionRate: 1000.42 }, + eth: { conversionRate: 3000.42, usdConversionRate: 2000.42 }, + sol: { conversionRate: 4000.42, usdConversionRate: 3000.42 }, + }); + }); }); diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index c6149dfa1e..544a4ae7b0 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -1,11 +1,15 @@ import { handleFetch } from '@metamask/controller-utils'; +import type { Rate, ConversionRates } from '../RatesController/types'; + /** * A map from native currency symbol to CryptoCompare identifier. * This is only needed when the values don't match. */ const nativeSymbolOverrides = new Map([['MNT', 'MANTLE']]); +const CRYPTO_COMPARE_DOMAIN = 'https://min-api.cryptocompare.com'; + /** * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to * the given currency. Optionally, the conversion rate from the native currency to USD can also be @@ -25,12 +29,47 @@ function getPricingURL( nativeCurrency = nativeCurrency.toUpperCase(); const fsym = nativeSymbolOverrides.get(nativeCurrency) ?? nativeCurrency; return ( - `https://min-api.cryptocompare.com/data/price?fsym=` + + `${CRYPTO_COMPARE_DOMAIN}/data/price?fsym=` + `${fsym}&tsyms=${currentCurrency.toUpperCase()}` + `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}` ); } +/** + * Get the CryptoCompare API URL for getting the conversion rate from a given array of native currencies + * to the given currency. Optionally, the conversion rate from the native currency to USD can also be + * included in the response. + * + * @param fsyms - The native currencies to get conversion rates for. + * @param tsyms - The currency to convert to. + * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be included. + * @returns The API URL for getting the conversion rates. + */ +function getMultiPricingURL( + fsyms: string, + tsyms: string, + includeUSDRate?: boolean, +) { + const updatedTsyms = + includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; + return `${CRYPTO_COMPARE_DOMAIN}/data/pricemulti?fsyms=${fsyms}&tsyms=${updatedTsyms}`; +} + +/** + * Handles an error response from the CryptoCompare API. + * Expected error response format + * { Response: "Error", Message: "...", HasWarning: false } + * + * @param json - The JSON response from the CryptoCompare API. + * @param json.Response - The response status. + * @param json.Message - The error message. + */ +function handleErrorResponse(json: { Response?: string; Message?: string }) { + if (json.Response === 'Error') { + throw new Error(json.Message); + } +} + /** * Fetches the exchange rate for a given currency. * @@ -51,18 +90,7 @@ export async function fetchExchangeRate( getPricingURL(currency, nativeCurrency, includeUSDRate), ); - /* - Example expected error response (if pair is not found) - { - Response: "Error", - Message: "cccagg_or_exchange market does not exist for this coin pair (ETH-)", - HasWarning: false, - } - */ - if (json.Response === 'Error') { - throw new Error(json.Message); - } - + handleErrorResponse(json); const conversionRate = Number(json[currency.toUpperCase()]); const usdConversionRate = Number(json.USD); @@ -83,3 +111,35 @@ export async function fetchExchangeRate( usdConversionRate, }; } + +/** + * Fetches the exchange rates for multiple currencies. + * + * @param currency - The currency of the rates (ISO 4217). + * @param cryptocurrencies - The cryptocurrencies to get conversion rates for. Min length: 1. Max length: 300. + * @param includeUSDRate - Whether to add the USD rate to the fetch. + * @returns Promise resolving to exchange rates for given currencies. + */ +export async function fetchMultiExchangeRate( + currency: string, + cryptocurrencies: string[], + includeUSDRate?: boolean, +): Promise { + const url = getMultiPricingURL( + Object.values(cryptocurrencies).join(','), + currency, + includeUSDRate, + ); + const response = await handleFetch(url); + handleErrorResponse(response); + + const rates: ConversionRates = {}; + for (const [key, value] of Object.entries(response) as [string, Rate][]) { + rates[key.toLowerCase()] = { + conversionRate: value[currency.toUpperCase()], + usdConversionRate: value.USD || null, + }; + } + + return rates; +} From 4c825fccd3850adecafec04d4cc6870313634146 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Wed, 8 May 2024 09:56:17 -0400 Subject: [PATCH 05/69] refactor: change endpoint to multiprice --- .../src/RatesController/RatesController.ts | 77 +++++++++++-------- .../src/RatesController/types.ts | 15 ++++ .../src/crypto-compare-service/index.ts | 4 +- 3 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 packages/assets-controllers/src/RatesController/types.ts diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index c000cfeb96..e146bd5228 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -5,21 +5,16 @@ import type { ControllerStateChangeEvent, } from '@metamask/base-controller'; -import { fetchExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; +import { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; +import type { ConversionRates } from './types'; /** * Represents the state structure for the BtcRateController. */ export type RatesState = { currency: string; - rates: Record< - string, - { - conversionDate: number | null; - conversionRate: number | null; - usdConversionRate: number | null; - } - >; + rates: ConversionRates; + cryptocurrencyList: string[]; }; const name = 'RatesController'; @@ -49,9 +44,14 @@ type RatesMessenger = RestrictedControllerMessenger< never >; +const DEFAULT_CURRENCIES = { + btc: 'btc', +}; + const metadata = { currency: { persist: true, anonymous: true }, rates: { persist: true, anonymous: true }, + cryptocurrencyList: { persist: true, anonymous: true }, }; const defaultState = { @@ -63,11 +63,7 @@ const defaultState = { usdConversionRate: null, }, }, -}; - -const DEFAULT_CURRENCIES = { - btc: 'BTC', - sol: 'SOL', + cryptocurrencyList: [DEFAULT_CURRENCIES.btc], }; export class RatesController extends BaseController< @@ -75,7 +71,7 @@ export class RatesController extends BaseController< RatesState, RatesMessenger > { - readonly #fetchExchangeRate; + readonly #fetchMultiExchangeRate; readonly #onStart; @@ -95,7 +91,7 @@ export class RatesController extends BaseController< * @param options.interval - The polling interval, in milliseconds. * @param options.messenger - A reference to the messaging system. * @param options.state - Initial state to set on this controller. - * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. + * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. * @param options.onStart - Optional callback to be executed when the polling stops. * @param options.onStop - Optional callback to be executed when the polling starts. */ @@ -104,7 +100,7 @@ export class RatesController extends BaseController< messenger, state, includeUsdRate, - fetchExchangeRate = defaultFetchExchangeRate, + fetchMultiExchangeRate = defaultFetchExchangeRate, onStart, onStop, }: { @@ -112,7 +108,7 @@ export class RatesController extends BaseController< interval?: number; messenger: RatesMessenger; state?: Partial; - fetchExchangeRate?: typeof defaultFetchExchangeRate; + fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; onStart?: () => Promise; onStop?: () => Promise; }) { @@ -123,7 +119,7 @@ export class RatesController extends BaseController< state: { ...defaultState, ...state }, }); this.#includeUsdRate = includeUsdRate; - this.#fetchExchangeRate = fetchExchangeRate; + this.#fetchMultiExchangeRate = fetchMultiExchangeRate; this.#onStart = onStart; this.#onStop = onStop; this.#setIntervalLength(interval); @@ -142,25 +138,26 @@ export class RatesController extends BaseController< * Updates the BTC rates by fetching new data. */ async #updateRates(): Promise { - const { currency } = this.state; - // const cryptoCurrencies: string = - // Object.values(DEFAULT_CURRENCIES).join(','); - const response = await this.#fetchExchangeRate( + const { currency, cryptocurrencyList } = this.state; + const response = await this.#fetchMultiExchangeRate( currency, - DEFAULT_CURRENCIES.btc, + cryptocurrencyList, this.#includeUsdRate, ); - const { conversionRate, usdConversionRate } = response; + + const updatedRates: ConversionRates = {}; + for (const [key, value] of Object.entries(response)) { + updatedRates[key] = { + conversionDate: Date.now() / 1000, + conversionRate: value.conversionRate, + usdconversionRate: value.usdconversionRate || null, + }; + } + this.update(() => { return { - rates: { - btc: { - conversionDate: Date.now() / 1000, - conversionRate, - usdConversionRate, - }, - }, - currency, + ...this.state, + rates: updatedRates, }; }); } @@ -198,4 +195,18 @@ export class RatesController extends BaseController< this.#intervalId = undefined; await this.#onStop?.(); } + + getCryptocurrencyList(): string[] { + const { cryptocurrencyList } = this.state; + return cryptocurrencyList; + } + + setCryptocurrencyList(list: string[]): void { + this.update(() => { + return { + ...this.state, + cryptocurrencyList: list, + }; + }); + } } diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts new file mode 100644 index 0000000000..fcf235013d --- /dev/null +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -0,0 +1,15 @@ +/** + * Represents the conversion rates from one currency to others. + * Each key is a string representing the cryptocurrency code (e.g., "BTC", "SOL"), + * and its value is either a number representing the conversion rate to that currency, + * or `null` if the conversion rate is not available. + */ +export type Rate = Record; + +/** + * Represents the conversion rates for multiple cryptocurrencies. + * Each key is a string representing the cryptocurrency symbol (e.g., "BTC", "SOL"), + * and its value is a `Rate` object containing conversion rates from that cryptocurrency + * to various fiat currencies or other cryptocurrencies. + */ +export type ConversionRates = Record; diff --git a/packages/assets-controllers/src/crypto-compare-service/index.ts b/packages/assets-controllers/src/crypto-compare-service/index.ts index aa13ad704e..3364674111 100644 --- a/packages/assets-controllers/src/crypto-compare-service/index.ts +++ b/packages/assets-controllers/src/crypto-compare-service/index.ts @@ -1,3 +1 @@ -import { fetchExchangeRate } from './crypto-compare'; - -export { fetchExchangeRate }; +export { fetchExchangeRate, fetchMultiExchangeRate } from './crypto-compare'; From b04949f60cd5720ba317b847e8fa4fa22063045c Mon Sep 17 00:00:00 2001 From: gantunesr Date: Thu, 9 May 2024 16:13:28 -0400 Subject: [PATCH 06/69] test: add unit tests to RatesController --- .../RatesController/RatesController.test.ts | 288 ++++++++++++++++++ .../src/RatesController/RatesController.ts | 128 ++++---- .../src/RatesController/types.ts | 53 ++++ 3 files changed, 396 insertions(+), 73 deletions(-) create mode 100644 packages/assets-controllers/src/RatesController/RatesController.test.ts diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts new file mode 100644 index 0000000000..47f645edf1 --- /dev/null +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -0,0 +1,288 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import { useFakeTimers } from 'sinon'; + +import { advanceTime } from '../../../../tests/helpers'; +import type { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; +import { + RatesController, + name as ratesControllerName, +} from './RatesController'; +import type { + RatesControllerActions, + RatesControllerEvents, + RatesMessenger, + RatesState, +} from './types'; + +const MOCK_TIMESTAMP = 1709983353; + +const getStubbedDate = () => { + return new Date(MOCK_TIMESTAMP * 1000).getTime(); +}; + +// eslint-disable-next-line jsdoc/require-jsdoc +function buildMessenger(): ControllerMessenger< + RatesControllerActions, + RatesControllerEvents +> { + return new ControllerMessenger< + RatesControllerActions, + RatesControllerEvents + >(); +} + +// eslint-disable-next-line jsdoc/require-jsdoc +function buildRatesControllerMessenger( + messenger: ControllerMessenger, +): RatesMessenger { + return messenger.getRestricted({ + name: ratesControllerName, + allowedEvents: [], + allowedActions: [], + }); +} + +// eslint-disable-next-line jsdoc/require-jsdoc +function setupRatesController({ + initialState, + messenger, + includeUsdRate, + fetchMultiExchangeRate, + onStart, + onStop, +}: { + initialState: Partial; + messenger: ControllerMessenger; + includeUsdRate?: boolean; + fetchMultiExchangeRate: typeof defaultFetchExchangeRate; + onStart?: () => Promise; + onStop?: () => Promise; +}) { + const ratesControllerMessenger = buildRatesControllerMessenger(messenger); + return new RatesController({ + interval: 150, + messenger: ratesControllerMessenger, + state: initialState, + includeUsdRate, + fetchMultiExchangeRate, + onStart, + onStop, + }); +} + +describe('RatesController', () => { + let clock: sinon.SinonFakeTimers; + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('contruct', () => { + it('constructs the RatesController with the correct values', () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + includeUsdRate: false, + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + const { currency, rates, cryptocurrencyList } = ratesController.state; + expect(ratesController).toBeDefined(); + expect(currency).toBe('usd'); + expect(Object.keys(rates)).toStrictEqual(['btc']); + expect(cryptocurrencyList).toStrictEqual(['btc']); + }); + }); + + describe('start', () => { + beforeEach(() => { + clock = useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + jest.restoreAllMocks(); + }); + + it('starts the polling process with default values', async () => { + jest.spyOn(global.Date, 'now').mockImplementation(() => getStubbedDate()); + const mockRateValue = 62235.48; + const fetchExchangeRateStub = jest.fn(() => { + return Promise.resolve({ + btc: { + usd: mockRateValue, + }, + }); + }); + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + + const ratesPreUpdate = ratesController.state.rates; + + expect(ratesPreUpdate).toStrictEqual({ + btc: { + conversionDate: 0, + conversionRate: 0, + usdConversionRate: null, + }, + }); + + await ratesController.start(); + + await advanceTime({ clock, duration: 200 }); + + const ratesPosUpdate = ratesController.state.rates; + + expect(fetchExchangeRateStub).toHaveBeenCalled(); + expect(ratesPosUpdate).toStrictEqual({ + btc: { + conversionDate: MOCK_TIMESTAMP, + conversionRate: mockRateValue, + usdConversionRate: mockRateValue, + }, + }); + }); + + it('starts the polling process with custom values', async () => { + jest.spyOn(global.Date, 'now').mockImplementation(() => getStubbedDate()); + const mockBtcUsdRateValue = 62235.48; + const mockSolUsdRateValue = 148.41; + const mockStrkUsdRateValue = 1.248; + const mockBtcEurRateValue = 57715.42; + const mockSolEurRateValue = 137.68; + const mockStrkEurRateValue = 1.157; + const fetchExchangeRateStub = jest.fn(() => { + return Promise.resolve({ + btc: { + usd: mockBtcUsdRateValue, + eur: mockBtcEurRateValue, + }, + sol: { + usd: mockSolUsdRateValue, + eur: mockSolEurRateValue, + }, + strk: { + usd: mockStrkUsdRateValue, + eur: mockStrkEurRateValue, + }, + }); + }); + const onStartStub = jest.fn().mockResolvedValue({}); + + const ratesController = setupRatesController({ + initialState: { + cryptocurrencyList: ['btc', 'sol', 'strk'], + currency: 'eur', + }, + messenger: buildMessenger(), + includeUsdRate: true, + fetchMultiExchangeRate: fetchExchangeRateStub, + onStart: onStartStub, + }); + + await ratesController.start(); + + await advanceTime({ clock, duration: 200 }); + + const { rates } = ratesController.state; + expect(fetchExchangeRateStub).toHaveBeenCalled(); + expect(onStartStub).toHaveBeenCalled(); + expect(rates).toStrictEqual({ + btc: { + conversionDate: MOCK_TIMESTAMP, + conversionRate: mockBtcEurRateValue, + usdConversionRate: mockBtcUsdRateValue, + }, + sol: { + conversionDate: MOCK_TIMESTAMP, + conversionRate: mockSolEurRateValue, + usdConversionRate: mockSolUsdRateValue, + }, + strk: { + conversionDate: MOCK_TIMESTAMP, + conversionRate: mockStrkEurRateValue, + usdConversionRate: mockStrkUsdRateValue, + }, + }); + }); + }); + + describe('stop', () => { + beforeEach(() => { + clock = useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + jest.restoreAllMocks(); + }); + + it('stops the polling process', async () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const onStopStub = jest.fn().mockResolvedValue({}); + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + onStop: onStopStub, + }); + + await ratesController.start(); + + await advanceTime({ clock, duration: 200 }); + + expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1); + + await ratesController.stop(); + + expect(onStopStub).toHaveBeenCalled(); + + await advanceTime({ clock, duration: 200 }); + + expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1); + }); + }); + + describe('getCryptocurrencyList', () => { + it('returns the current cryptocurrency list', () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const mockCryptocurrencyList = ['btc', 'sol', 'strk']; + const ratesController = setupRatesController({ + initialState: { + cryptocurrencyList: mockCryptocurrencyList, + }, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + + const cryptocurrencyList = ratesController.getCryptocurrencyList(); + expect(cryptocurrencyList).toStrictEqual(mockCryptocurrencyList); + }); + }); + + describe('setCryptocurrencyList', () => { + it('updates the cryptocurrency list', () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const mockCryptocurrencyList = ['btc', 'sol', 'strk']; + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + + const cryptocurrencyListPreUpdate = + ratesController.getCryptocurrencyList(); + expect(cryptocurrencyListPreUpdate).toStrictEqual(['btc']); + + ratesController.setCryptocurrencyList(mockCryptocurrencyList); + const cryptocurrencyListPostUpdate = + ratesController.getCryptocurrencyList(); + expect(cryptocurrencyListPostUpdate).toStrictEqual( + mockCryptocurrencyList, + ); + }); + }); +}); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index e146bd5228..8f305733fe 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -1,48 +1,15 @@ import { BaseController } from '@metamask/base-controller'; -import type { - RestrictedControllerMessenger, - ControllerGetStateAction, - ControllerStateChangeEvent, -} from '@metamask/base-controller'; +import { Mutex } from 'async-mutex'; import { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; -import type { ConversionRates } from './types'; - -/** - * Represents the state structure for the BtcRateController. - */ -export type RatesState = { - currency: string; - rates: ConversionRates; - cryptocurrencyList: string[]; -}; - -const name = 'RatesController'; - -/** - * Type definition for BtcRateController state change events. - */ -export type RatesStateChange = ControllerStateChangeEvent< - typeof name, - RatesState ->; - -export type RatesControllerEvents = RatesStateChange; - -/** - * Type definition for getting the BtcRateController. - */ -export type GetRatesState = ControllerGetStateAction; - -export type RatesControllerActions = GetRatesState; +import type { + ConversionRates, + RatesState, + RatesMessenger, + RatesControllerArgs, +} from './types'; -type RatesMessenger = RestrictedControllerMessenger< - typeof name, - RatesControllerActions, - RatesControllerEvents, - never, - never ->; +export const name = 'RatesController'; const DEFAULT_CURRENCIES = { btc: 'btc', @@ -71,6 +38,8 @@ export class RatesController extends BaseController< RatesState, RatesMessenger > { + readonly #mutex = new Mutex(); + readonly #fetchMultiExchangeRate; readonly #onStart; @@ -81,7 +50,7 @@ export class RatesController extends BaseController< #intervalId: NodeJS.Timeout | undefined; - #intervalLength: number | undefined = 1000; + #intervalLength: number | undefined; /** * Creates a BtcRateController instance. @@ -103,15 +72,7 @@ export class RatesController extends BaseController< fetchMultiExchangeRate = defaultFetchExchangeRate, onStart, onStop, - }: { - includeUsdRate?: boolean; - interval?: number; - messenger: RatesMessenger; - state?: Partial; - fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; - onStart?: () => Promise; - onStop?: () => Promise; - }) { + }: RatesControllerArgs) { super({ name, metadata, @@ -137,36 +98,42 @@ export class RatesController extends BaseController< /** * Updates the BTC rates by fetching new data. */ - async #updateRates(): Promise { + async updateRates(): Promise { + const releaseLock = await this.#mutex.acquire(); const { currency, cryptocurrencyList } = this.state; - const response = await this.#fetchMultiExchangeRate( - currency, - cryptocurrencyList, - this.#includeUsdRate, - ); - - const updatedRates: ConversionRates = {}; - for (const [key, value] of Object.entries(response)) { - updatedRates[key] = { - conversionDate: Date.now() / 1000, - conversionRate: value.conversionRate, - usdconversionRate: value.usdconversionRate || null, - }; - } - this.update(() => { - return { - ...this.state, - rates: updatedRates, - }; - }); + try { + const response = await this.#fetchMultiExchangeRate( + currency, + cryptocurrencyList, + this.#includeUsdRate, + ); + + const updatedRates: ConversionRates = {}; + for (const [key, value] of Object.entries(response)) { + updatedRates[key] = { + conversionDate: Date.now() / 1000, + conversionRate: value[currency], + usdConversionRate: value.usd || null, + }; + } + + this.update(() => { + return { + ...this.state, + rates: updatedRates, + }; + }); + } finally { + releaseLock(); + } } /** * Executes the polling operation to update rates. */ async #executePoll(): Promise { - await this.#updateRates(); + await this.updateRates(); } /** @@ -209,4 +176,19 @@ export class RatesController extends BaseController< }; }); } + + async setCurrentCurrency(currentCurrency: string) { + const releaseLock = await this.#mutex.acquire(); + try { + this.update(() => { + return { + ...defaultState, + currentCurrency, + }; + }); + } finally { + releaseLock(); + } + await this.updateRates(); + } } diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index fcf235013d..4bc3f29cbb 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -1,3 +1,12 @@ +import type { + RestrictedControllerMessenger, + ControllerGetStateAction, + ControllerStateChangeEvent, +} from '@metamask/base-controller'; + +import type { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; +import type { name as ratesControllerName } from './RatesController'; + /** * Represents the conversion rates from one currency to others. * Each key is a string representing the cryptocurrency code (e.g., "BTC", "SOL"), @@ -13,3 +22,47 @@ export type Rate = Record; * to various fiat currencies or other cryptocurrencies. */ export type ConversionRates = Record; + +/** + * Represents the state structure for the BtcRateController. + */ +export type RatesState = { + currency: string; + rates: ConversionRates; + cryptocurrencyList: string[]; +}; + +/** + * Type definition for BtcRateController state change events. + */ +export type RatesStateChange = ControllerStateChangeEvent< + typeof ratesControllerName, + RatesState +>; + +export type RatesControllerEvents = RatesStateChange; + +export type GetRatesState = ControllerGetStateAction< + typeof ratesControllerName, + RatesState +>; + +export type RatesControllerActions = GetRatesState; + +export type RatesMessenger = RestrictedControllerMessenger< + typeof ratesControllerName, + RatesControllerActions, + RatesControllerEvents, + never, + never +>; + +export type RatesControllerArgs = { + includeUsdRate?: boolean; + interval?: number; + messenger: RatesMessenger; + state?: Partial; + fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; + onStart?: () => Promise; + onStop?: () => Promise; +}; From 3a1254db75024ff6d9a5c09d85018d1dd2a5e123 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Thu, 9 May 2024 23:20:42 -0400 Subject: [PATCH 07/69] chore: throw an error if the currency is an empty string --- .../src/RatesController/RatesController.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 8f305733fe..6eafc69b7b 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -177,13 +177,16 @@ export class RatesController extends BaseController< }); } - async setCurrentCurrency(currentCurrency: string) { + async setCurrency(currency: string) { + if (currency === '') { + throw new Error("The currency can't be an empty string"); + } const releaseLock = await this.#mutex.acquire(); try { this.update(() => { return { ...defaultState, - currentCurrency, + currency, }; }); } finally { From adecee9337de90d3114be3a90259da92d4b9efd1 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Thu, 9 May 2024 23:21:01 -0400 Subject: [PATCH 08/69] test: add jsdoc to test helper methods --- .../RatesController/RatesController.test.ts | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 47f645edf1..b1f63bf810 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -16,11 +16,18 @@ import type { const MOCK_TIMESTAMP = 1709983353; +/** + * Returns a stubbed date based on a predefined timestamp. + * @returns The stubbed date in milliseconds. + */ const getStubbedDate = () => { return new Date(MOCK_TIMESTAMP * 1000).getTime(); }; -// eslint-disable-next-line jsdoc/require-jsdoc +/** + * Builds a new ControllerMessenger instance for RatesController. + * @returns A new ControllerMessenger instance. + */ function buildMessenger(): ControllerMessenger< RatesControllerActions, RatesControllerEvents @@ -31,7 +38,11 @@ function buildMessenger(): ControllerMessenger< >(); } -// eslint-disable-next-line jsdoc/require-jsdoc +/** + * Builds a restricted messenger for the RatesController. + * @param messenger - The base messenger instance. + * @returns A restricted messenger for the RatesController. + */ function buildRatesControllerMessenger( messenger: ControllerMessenger, ): RatesMessenger { @@ -42,7 +53,17 @@ function buildRatesControllerMessenger( }); } -// eslint-disable-next-line jsdoc/require-jsdoc +/** + * Sets up and returns a new instance of RatesController with the provided configuration. + * @param config - The configuration object for the RatesController. + * @param config.initialState - Initial state of the controller. + * @param config.messenger - ControllerMessenger instance. + * @param config.includeUsdRate - Indicates if the USD rate should be included. + * @param config.fetchMultiExchangeRate - Callback to fetch rates data. + * @param config.onStart - Optional method to be executed when the polling starts. + * @param config.onStop - Optional method to be executed when the polling stops. + * @returns A new instance of RatesController. + */ function setupRatesController({ initialState, messenger, @@ -285,4 +306,23 @@ describe('RatesController', () => { ); }); }); + + describe('setCurrentCurrency', () => { + it('sets the currency to a new value', async () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + + const currencyPreUpdate = ratesController.state.currency; + expect(currencyPreUpdate).toBe('usd'); + + await ratesController.setCurrency('eur'); + + const currencyPostUpdate = ratesController.state.currency; + expect(currencyPostUpdate).toBe('eur'); + }); + }); }); From 9f5c83007bd93c4426dd3766928fc4b62193b7b4 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Thu, 9 May 2024 23:26:10 -0400 Subject: [PATCH 09/69] docs: improve JSDocs --- .../src/RatesController/types.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 4bc3f29cbb..82fc8c39b6 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -24,7 +24,10 @@ export type Rate = Record; export type ConversionRates = Record; /** - * Represents the state structure for the BtcRateController. + * Represents the state structure for the RatesController. + * @property {string} currency - The base currency for conversion rates. + * @property {ConversionRates} rates - The conversion rates for multiple cryptocurrencies. + * @property {string[]} cryptocurrencyList - A list of supported cryptocurrency symbols. */ export type RatesState = { currency: string; @@ -40,6 +43,9 @@ export type RatesStateChange = ControllerStateChangeEvent< RatesState >; +/** + * Defines the events that the RatesController can emit. + */ export type RatesControllerEvents = RatesStateChange; export type GetRatesState = ControllerGetStateAction< @@ -47,8 +53,14 @@ export type GetRatesState = ControllerGetStateAction< RatesState >; +/** + * Defines the actions that can be performed to get the state of the RatesController. + */ export type RatesControllerActions = GetRatesState; +/** + * Defines the actions that the RatesController can perform. + */ export type RatesMessenger = RestrictedControllerMessenger< typeof ratesControllerName, RatesControllerActions, @@ -57,6 +69,16 @@ export type RatesMessenger = RestrictedControllerMessenger< never >; +/** + * The arguments required to initialize a RatesController. + * @property {boolean} [includeUsdRate] - Whether to include USD rates in the conversion rates. + * @property {number} [interval] - The polling interval in milliseconds. + * @property {RatesMessenger} messenger - The messenger instance for communication. + * @property {Partial} [state] - The initial state of the controller. + * @property {typeof defaultFetchExchangeRate} [fetchMultiExchangeRate] - The function to fetch exchange rates. + * @property {() => Promise} [onStart] - A function to execute when the controller starts. + * @property {() => Promise} [onStop] - A function to execute when the controller stops. + */ export type RatesControllerArgs = { includeUsdRate?: boolean; interval?: number; From 9bec6161357d11179fce8e72304cc48940996307 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Fri, 10 May 2024 11:53:01 -0400 Subject: [PATCH 10/69] test: update crypto-compare tests --- .../crypto-compare-service/crypto-compare.test.ts | 12 ++++++++---- .../src/crypto-compare-service/crypto-compare.ts | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts index 0857f9636f..f18e435ab5 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts @@ -159,12 +159,16 @@ describe('CryptoCompare', () => { SOL: { CAD: 4000.42, USD: 3000.42 }, }); - const response = await fetchMultiExchangeRate('CAD', 'BTC,ETH,SOL', true); + const response = await fetchMultiExchangeRate( + 'CAD', + ['BTC', 'ETH', 'SOL'], + true, + ); expect(response).toStrictEqual({ - btc: { conversionRate: 2000.42, usdConversionRate: 1000.42 }, - eth: { conversionRate: 3000.42, usdConversionRate: 2000.42 }, - sol: { conversionRate: 4000.42, usdConversionRate: 3000.42 }, + btc: { cad: 2000.42, usd: 1000.42 }, + eth: { cad: 3000.42, usd: 2000.42 }, + sol: { cad: 4000.42, usd: 3000.42 }, }); }); }); diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index 544a4ae7b0..ea4aa169f7 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -136,8 +136,8 @@ export async function fetchMultiExchangeRate( const rates: ConversionRates = {}; for (const [key, value] of Object.entries(response) as [string, Rate][]) { rates[key.toLowerCase()] = { - conversionRate: value[currency.toUpperCase()], - usdConversionRate: value.USD || null, + [currency.toLowerCase()]: value[currency.toUpperCase()], + usd: value.USD || null, }; } From f95b93ac1bae3564c254cce60e031fe216af4d59 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Fri, 10 May 2024 12:05:32 -0400 Subject: [PATCH 11/69] fix: update exports --- packages/assets-controllers/src/RatesController/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/index.ts b/packages/assets-controllers/src/RatesController/index.ts index 618b89fd32..70e7d00348 100644 --- a/packages/assets-controllers/src/RatesController/index.ts +++ b/packages/assets-controllers/src/RatesController/index.ts @@ -5,4 +5,4 @@ export type { RatesControllerEvents, GetRatesState, RatesControllerActions, -} from './RatesController'; +} from './types'; From 9639a50a896791847c1232371e79d1c33e00e65c Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Fri, 10 May 2024 19:12:07 -0400 Subject: [PATCH 12/69] chore: update RatesControllerOptions JSDocs Co-authored-by: Elliot Winkler --- .../src/RatesController/types.ts | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 82fc8c39b6..eac410ba28 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -70,21 +70,35 @@ export type RatesMessenger = RestrictedControllerMessenger< >; /** - * The arguments required to initialize a RatesController. - * @property {boolean} [includeUsdRate] - Whether to include USD rates in the conversion rates. - * @property {number} [interval] - The polling interval in milliseconds. - * @property {RatesMessenger} messenger - The messenger instance for communication. - * @property {Partial} [state] - The initial state of the controller. - * @property {typeof defaultFetchExchangeRate} [fetchMultiExchangeRate] - The function to fetch exchange rates. - * @property {() => Promise} [onStart] - A function to execute when the controller starts. - * @property {() => Promise} [onStop] - A function to execute when the controller stops. + * The options required to initialize a RatesController. */ -export type RatesControllerArgs = { +export type RatesControllerOptions = { + /** + * Whether to include USD rates in the conversion rates. + */ includeUsdRate?: boolean; + /** + * The polling interval in milliseconds. + */ interval?: number; + /** + * The messenger instance for communication. + */ messenger: RatesMessenger; + /** + * The initial state of the controller. + */ state?: Partial; + /** + * The function to fetch exchange rates. + */ fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; + /** + * A function to execute when the controller starts. + */ onStart?: () => Promise; + /** + * A function to execute when the controller stops. + */ onStop?: () => Promise; }; From e0a75ab4fb0e6f1abd40e1a5762efc971b4ee81a Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Fri, 10 May 2024 19:13:33 -0400 Subject: [PATCH 13/69] chore: update JSDocs Co-authored-by: Elliot Winkler --- packages/assets-controllers/src/RatesController/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index eac410ba28..3c98a615be 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -36,7 +36,7 @@ export type RatesState = { }; /** - * Type definition for BtcRateController state change events. + * Type definition for RatesController state change events. */ export type RatesStateChange = ControllerStateChangeEvent< typeof ratesControllerName, From e95d3b3d1f01a5bb96dc0d989104c02dc2a0cfa3 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Fri, 10 May 2024 19:14:07 -0400 Subject: [PATCH 14/69] chore: update JSDocs Co-authored-by: Elliot Winkler --- .../src/RatesController/types.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 3c98a615be..4f18efb64b 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -25,13 +25,19 @@ export type ConversionRates = Record; /** * Represents the state structure for the RatesController. - * @property {string} currency - The base currency for conversion rates. - * @property {ConversionRates} rates - The conversion rates for multiple cryptocurrencies. - * @property {string[]} cryptocurrencyList - A list of supported cryptocurrency symbols. */ -export type RatesState = { +export type RatesControllerState = { + /** + * The base currency for conversion rates. + */ currency: string; + /** + * The conversion rates for multiple cryptocurrencies. + */ rates: ConversionRates; + /** + * A list of supported cryptocurrency symbols. + */ cryptocurrencyList: string[]; }; From 8a6232ed357f890e1df657ccbd300d34b9383b36 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Fri, 10 May 2024 19:17:00 -0400 Subject: [PATCH 15/69] chore: update JSDocs Co-authored-by: Elliot Winkler --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 6eafc69b7b..dc2a066dd1 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -53,7 +53,7 @@ export class RatesController extends BaseController< #intervalLength: number | undefined; /** - * Creates a BtcRateController instance. + * Creates a RatesController instance. * * @param options - Constructor options. * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. From 7fbad35bdaf32dfb5488dddc18b9cbf504bef6ca Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 18:13:59 -0400 Subject: [PATCH 16/69] refactor: replace callbacks with events --- .../RatesController/RatesController.test.ts | 14 -------------- .../src/RatesController/RatesController.ts | 15 +++------------ .../src/RatesController/types.ts | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index b1f63bf810..eb6631707c 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -60,8 +60,6 @@ function buildRatesControllerMessenger( * @param config.messenger - ControllerMessenger instance. * @param config.includeUsdRate - Indicates if the USD rate should be included. * @param config.fetchMultiExchangeRate - Callback to fetch rates data. - * @param config.onStart - Optional method to be executed when the polling starts. - * @param config.onStop - Optional method to be executed when the polling stops. * @returns A new instance of RatesController. */ function setupRatesController({ @@ -69,15 +67,11 @@ function setupRatesController({ messenger, includeUsdRate, fetchMultiExchangeRate, - onStart, - onStop, }: { initialState: Partial; messenger: ControllerMessenger; includeUsdRate?: boolean; fetchMultiExchangeRate: typeof defaultFetchExchangeRate; - onStart?: () => Promise; - onStop?: () => Promise; }) { const ratesControllerMessenger = buildRatesControllerMessenger(messenger); return new RatesController({ @@ -86,8 +80,6 @@ function setupRatesController({ state: initialState, includeUsdRate, fetchMultiExchangeRate, - onStart, - onStop, }); } @@ -201,7 +193,6 @@ describe('RatesController', () => { messenger: buildMessenger(), includeUsdRate: true, fetchMultiExchangeRate: fetchExchangeRateStub, - onStart: onStartStub, }); await ratesController.start(); @@ -210,7 +201,6 @@ describe('RatesController', () => { const { rates } = ratesController.state; expect(fetchExchangeRateStub).toHaveBeenCalled(); - expect(onStartStub).toHaveBeenCalled(); expect(rates).toStrictEqual({ btc: { conversionDate: MOCK_TIMESTAMP, @@ -243,12 +233,10 @@ describe('RatesController', () => { it('stops the polling process', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); - const onStopStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, - onStop: onStopStub, }); await ratesController.start(); @@ -259,8 +247,6 @@ describe('RatesController', () => { await ratesController.stop(); - expect(onStopStub).toHaveBeenCalled(); - await advanceTime({ clock, duration: 200 }); expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 6eafc69b7b..e1e112f4b0 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -42,10 +42,6 @@ export class RatesController extends BaseController< readonly #fetchMultiExchangeRate; - readonly #onStart; - - readonly #onStop; - readonly #includeUsdRate; #intervalId: NodeJS.Timeout | undefined; @@ -61,8 +57,6 @@ export class RatesController extends BaseController< * @param options.messenger - A reference to the messaging system. * @param options.state - Initial state to set on this controller. * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. - * @param options.onStart - Optional callback to be executed when the polling stops. - * @param options.onStop - Optional callback to be executed when the polling starts. */ constructor({ interval = 180000, @@ -70,8 +64,6 @@ export class RatesController extends BaseController< state, includeUsdRate, fetchMultiExchangeRate = defaultFetchExchangeRate, - onStart, - onStop, }: RatesControllerArgs) { super({ name, @@ -81,8 +73,6 @@ export class RatesController extends BaseController< }); this.#includeUsdRate = includeUsdRate; this.#fetchMultiExchangeRate = fetchMultiExchangeRate; - this.#onStart = onStart; - this.#onStop = onStop; this.#setIntervalLength(interval); } @@ -144,7 +134,8 @@ export class RatesController extends BaseController< return; } - await this.#onStart?.(); + this.messagingSystem.publish(`${name}:startPolling`); + this.#intervalId = setInterval(() => { this.#executePoll().catch(console.error); }, this.#intervalLength); @@ -160,7 +151,7 @@ export class RatesController extends BaseController< clearInterval(this.#intervalId); this.#intervalId = undefined; - await this.#onStop?.(); + this.messagingSystem.publish(`${name}:stopPolling`); } getCryptocurrencyList(): string[] { diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 82fc8c39b6..06e12afdd8 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -38,15 +38,28 @@ export type RatesState = { /** * Type definition for BtcRateController state change events. */ -export type RatesStateChange = ControllerStateChangeEvent< +export type RatesControllerStateChange = ControllerStateChangeEvent< typeof ratesControllerName, RatesState >; +export type RatesControllerStartPolling = { + type: `${typeof ratesControllerName}:startPolling`; + payload: []; +}; + +export type RatesControllerStopPolling = { + type: `${typeof ratesControllerName}:stopPolling`; + payload: []; +}; + /** * Defines the events that the RatesController can emit. */ -export type RatesControllerEvents = RatesStateChange; +export type RatesControllerEvents = + | RatesControllerStateChange + | RatesControllerStartPolling + | RatesControllerStopPolling; export type GetRatesState = ControllerGetStateAction< typeof ratesControllerName, @@ -85,6 +98,4 @@ export type RatesControllerArgs = { messenger: RatesMessenger; state?: Partial; fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; - onStart?: () => Promise; - onStop?: () => Promise; }; From aa22c5d7cec9aa9938ab31d3cc3a419be50b6a8c Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 19:57:06 -0400 Subject: [PATCH 17/69] chore: add mutex to setCryptocurrencyList --- .../src/RatesController/RatesController.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index e1e112f4b0..44607b06c8 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -159,13 +159,18 @@ export class RatesController extends BaseController< return cryptocurrencyList; } - setCryptocurrencyList(list: string[]): void { - this.update(() => { - return { - ...this.state, - cryptocurrencyList: list, - }; - }); + async setCryptocurrencyList(list: string[]): Promise { + const releaseLock = await this.#mutex.acquire(); + try { + this.update(() => { + return { + ...this.state, + cryptocurrencyList: list, + }; + }); + } finally { + releaseLock(); + } } async setCurrency(currency: string) { From 1d3849cd8cafb038a1d65bc2d1525925fd3869e2 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 20:10:28 -0400 Subject: [PATCH 18/69] refactor: remove setIntervalLength method --- .../src/RatesController/RatesController.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 44607b06c8..f3a5c160b2 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -73,16 +73,7 @@ export class RatesController extends BaseController< }); this.#includeUsdRate = includeUsdRate; this.#fetchMultiExchangeRate = fetchMultiExchangeRate; - this.#setIntervalLength(interval); - } - - /** - * Sets the interval length for polling. - * - * @param intervalLength - The length of the interval in milliseconds. - */ - #setIntervalLength(intervalLength: number) { - this.#intervalLength = intervalLength; + this.#intervalLength = interval; } /** From e452e2fa0df28eb15d3234927516dacf98dd12ff Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 20:10:47 -0400 Subject: [PATCH 19/69] refactor: set DEFAULT_INTERVAL value --- .../assets-controllers/src/RatesController/RatesController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index f3a5c160b2..6e33b6ed5d 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -15,6 +15,8 @@ const DEFAULT_CURRENCIES = { btc: 'btc', }; +const DEFAULT_INTERVAL = 180000; + const metadata = { currency: { persist: true, anonymous: true }, rates: { persist: true, anonymous: true }, @@ -59,7 +61,7 @@ export class RatesController extends BaseController< * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. */ constructor({ - interval = 180000, + interval = DEFAULT_INTERVAL, messenger, state, includeUsdRate, From 9f0401ec3617471811919c578529e8e16f9b582e Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Mon, 13 May 2024 20:11:51 -0400 Subject: [PATCH 20/69] chore: update JSDocs Co-authored-by: Elliot Winkler --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index dc2a066dd1..84bda6ef2d 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -96,7 +96,7 @@ export class RatesController extends BaseController< } /** - * Updates the BTC rates by fetching new data. + * Updates the rates by fetching new data. */ async updateRates(): Promise { const releaseLock = await this.#mutex.acquire(); From 778a389c6afeab6a4c5f400250ed5ba33e930194 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 20:13:16 -0400 Subject: [PATCH 21/69] refactor: update event variable names --- .../assets-controllers/src/RatesController/types.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 06e12afdd8..9ad0bba6be 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -38,17 +38,17 @@ export type RatesState = { /** * Type definition for BtcRateController state change events. */ -export type RatesControllerStateChange = ControllerStateChangeEvent< +export type RatesControllerStateChangeEvent = ControllerStateChangeEvent< typeof ratesControllerName, RatesState >; -export type RatesControllerStartPolling = { +export type RatesControllerStartPollingEvent = { type: `${typeof ratesControllerName}:startPolling`; payload: []; }; -export type RatesControllerStopPolling = { +export type RatesControllerStopPollingEvent = { type: `${typeof ratesControllerName}:stopPolling`; payload: []; }; @@ -57,9 +57,9 @@ export type RatesControllerStopPolling = { * Defines the events that the RatesController can emit. */ export type RatesControllerEvents = - | RatesControllerStateChange - | RatesControllerStartPolling - | RatesControllerStopPolling; + | RatesControllerStateChangeEvent + | RatesControllerStartPollingEvent + | RatesControllerStopPollingEvent; export type GetRatesState = ControllerGetStateAction< typeof ratesControllerName, From 72ab7137d3621371903b48f67f5f9c504b21e696 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 20:14:04 -0400 Subject: [PATCH 22/69] refactor: rename action variable --- packages/assets-controllers/src/RatesController/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 9ad0bba6be..6f09cede16 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -61,7 +61,7 @@ export type RatesControllerEvents = | RatesControllerStartPollingEvent | RatesControllerStopPollingEvent; -export type GetRatesState = ControllerGetStateAction< +export type RatesControllerGetStateAction = ControllerGetStateAction< typeof ratesControllerName, RatesState >; @@ -69,7 +69,7 @@ export type GetRatesState = ControllerGetStateAction< /** * Defines the actions that can be performed to get the state of the RatesController. */ -export type RatesControllerActions = GetRatesState; +export type RatesControllerActions = RatesControllerGetStateAction; /** * Defines the actions that the RatesController can perform. From df2304e2113099035f3d760e1c897b5a0d246a0f Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 23:29:23 -0400 Subject: [PATCH 23/69] fix: data types --- .../src/RatesController/RatesController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index fc09ce52e7..6aee2b9906 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -3,10 +3,10 @@ import { Mutex } from 'async-mutex'; import { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; import type { - ConversionRates, - RatesState, RatesMessenger, - RatesControllerArgs, + ConversionRates, + RatesControllerState, + RatesControllerOptions, } from './types'; export const name = 'RatesController'; @@ -37,7 +37,7 @@ const defaultState = { export class RatesController extends BaseController< typeof name, - RatesState, + RatesControllerState, RatesMessenger > { readonly #mutex = new Mutex(); @@ -66,7 +66,7 @@ export class RatesController extends BaseController< state, includeUsdRate, fetchMultiExchangeRate = defaultFetchExchangeRate, - }: RatesControllerArgs) { + }: RatesControllerOptions) { super({ name, metadata, From 2b9d38ab136f7c0c9691459ffbdcbf28057bff78 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Mon, 13 May 2024 23:35:36 -0400 Subject: [PATCH 24/69] refactor: change name cryptocurrencyList to fromCurrencies --- .../src/RatesController/RatesController.ts | 14 +++++++------- .../src/RatesController/types.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 6aee2b9906..79a7030943 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -20,7 +20,7 @@ const DEFAULT_INTERVAL = 180000; const metadata = { currency: { persist: true, anonymous: true }, rates: { persist: true, anonymous: true }, - cryptocurrencyList: { persist: true, anonymous: true }, + fromCurrencies: { persist: true, anonymous: true }, }; const defaultState = { @@ -32,7 +32,7 @@ const defaultState = { usdConversionRate: null, }, }, - cryptocurrencyList: [DEFAULT_CURRENCIES.btc], + fromCurrencies: [DEFAULT_CURRENCIES.btc], }; export class RatesController extends BaseController< @@ -83,12 +83,12 @@ export class RatesController extends BaseController< */ async updateRates(): Promise { const releaseLock = await this.#mutex.acquire(); - const { currency, cryptocurrencyList } = this.state; + const { currency, fromCurrencies } = this.state; try { const response = await this.#fetchMultiExchangeRate( currency, - cryptocurrencyList, + fromCurrencies, this.#includeUsdRate, ); @@ -148,8 +148,8 @@ export class RatesController extends BaseController< } getCryptocurrencyList(): string[] { - const { cryptocurrencyList } = this.state; - return cryptocurrencyList; + const { fromCurrencies } = this.state; + return fromCurrencies; } async setCryptocurrencyList(list: string[]): Promise { @@ -158,7 +158,7 @@ export class RatesController extends BaseController< this.update(() => { return { ...this.state, - cryptocurrencyList: list, + fromCurrencies: list, }; }); } finally { diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index c941e098bf..a86f6c7b8a 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -38,7 +38,7 @@ export type RatesControllerState = { /** * A list of supported cryptocurrency symbols. */ - cryptocurrencyList: string[]; + fromCurrencies: string[]; }; /** From ae0af1bc3d2802d23dc36385511de7b4e4fd86ec Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 14 May 2024 11:03:48 -0400 Subject: [PATCH 25/69] fix: unit tests --- .../src/RatesController/RatesController.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index eb6631707c..b06933003d 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -11,7 +11,7 @@ import type { RatesControllerActions, RatesControllerEvents, RatesMessenger, - RatesState, + RatesControllerState, } from './types'; const MOCK_TIMESTAMP = 1709983353; @@ -68,7 +68,7 @@ function setupRatesController({ includeUsdRate, fetchMultiExchangeRate, }: { - initialState: Partial; + initialState: Partial; messenger: ControllerMessenger; includeUsdRate?: boolean; fetchMultiExchangeRate: typeof defaultFetchExchangeRate; @@ -99,11 +99,11 @@ describe('RatesController', () => { includeUsdRate: false, fetchMultiExchangeRate: fetchExchangeRateStub, }); - const { currency, rates, cryptocurrencyList } = ratesController.state; + const { currency, rates, fromCurrencies } = ratesController.state; expect(ratesController).toBeDefined(); expect(currency).toBe('usd'); expect(Object.keys(rates)).toStrictEqual(['btc']); - expect(cryptocurrencyList).toStrictEqual(['btc']); + expect(fromCurrencies).toStrictEqual(['btc']); }); }); @@ -183,11 +183,10 @@ describe('RatesController', () => { }, }); }); - const onStartStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ initialState: { - cryptocurrencyList: ['btc', 'sol', 'strk'], + fromCurrencies: ['btc', 'sol', 'strk'], currency: 'eur', }, messenger: buildMessenger(), @@ -259,7 +258,7 @@ describe('RatesController', () => { const mockCryptocurrencyList = ['btc', 'sol', 'strk']; const ratesController = setupRatesController({ initialState: { - cryptocurrencyList: mockCryptocurrencyList, + fromCurrencies: mockCryptocurrencyList, }, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, From e6ea9389f2ddafab7b8f4b206cd8484f8e56b1ec Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 14 May 2024 15:29:52 -0400 Subject: [PATCH 26/69] fix: unit tests --- .../src/RatesController/RatesController.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index b06933003d..3d720d688a 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -270,7 +270,7 @@ describe('RatesController', () => { }); describe('setCryptocurrencyList', () => { - it('updates the cryptocurrency list', () => { + it('updates the cryptocurrency list', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const mockCryptocurrencyList = ['btc', 'sol', 'strk']; const ratesController = setupRatesController({ @@ -283,7 +283,7 @@ describe('RatesController', () => { ratesController.getCryptocurrencyList(); expect(cryptocurrencyListPreUpdate).toStrictEqual(['btc']); - ratesController.setCryptocurrencyList(mockCryptocurrencyList); + await ratesController.setCryptocurrencyList(mockCryptocurrencyList); const cryptocurrencyListPostUpdate = ratesController.getCryptocurrencyList(); expect(cryptocurrencyListPostUpdate).toStrictEqual( From b23817d1cd0d4d89aa6bc41c8a14acb532fd701a Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 14 May 2024 15:50:44 -0400 Subject: [PATCH 27/69] chore: update exports --- packages/assets-controllers/src/RatesController/index.ts | 8 +++++--- packages/assets-controllers/src/index.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/index.ts b/packages/assets-controllers/src/RatesController/index.ts index 70e7d00348..e87400fe1e 100644 --- a/packages/assets-controllers/src/RatesController/index.ts +++ b/packages/assets-controllers/src/RatesController/index.ts @@ -1,8 +1,10 @@ export { RatesController } from './RatesController'; export type { - RatesState, - RatesStateChange, + RatesControllerState, RatesControllerEvents, - GetRatesState, RatesControllerActions, + RatesControllerGetStateAction, + RatesControllerStopPollingEvent, + RatesControllerStateChangeEvent, + RatesControllerStartPollingEvent, } from './types'; diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 492a399637..35c8e19e29 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -60,9 +60,11 @@ export { } from './token-prices-service'; export { RatesController } from './RatesController'; export type { - RatesState, - RatesStateChange, + RatesControllerState, RatesControllerEvents, - GetRatesState, RatesControllerActions, + RatesControllerGetStateAction, + RatesControllerStopPollingEvent, + RatesControllerStateChangeEvent, + RatesControllerStartPollingEvent, } from './RatesController'; From c44002d116d29bb06f27f5f899320d651b2ce0f5 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Tue, 14 May 2024 17:00:49 -0400 Subject: [PATCH 28/69] test: increase code coverage --- .../RatesController/RatesController.test.ts | 67 +++++++++++++++++-- .../src/RatesController/RatesController.ts | 2 +- .../crypto-compare.test.ts | 22 ++++++ 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 3d720d688a..37902ff757 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -118,18 +118,23 @@ describe('RatesController', () => { }); it('starts the polling process with default values', async () => { + const messenger = buildMessenger(); + const publishActionSpy = jest.spyOn(messenger, 'publish'); + jest.spyOn(global.Date, 'now').mockImplementation(() => getStubbedDate()); - const mockRateValue = 62235.48; + const mockRateValue = 57715.42; const fetchExchangeRateStub = jest.fn(() => { return Promise.resolve({ btc: { - usd: mockRateValue, + eur: mockRateValue, }, }); }); const ratesController = setupRatesController({ - initialState: {}, - messenger: buildMessenger(), + initialState: { + currency: 'eur', + }, + messenger, fetchMultiExchangeRate: fetchExchangeRateStub, }); @@ -145,18 +150,32 @@ describe('RatesController', () => { await ratesController.start(); + expect(publishActionSpy).toHaveBeenNthCalledWith( + 1, + `${ratesControllerName}:startPolling`, + ); + await advanceTime({ clock, duration: 200 }); const ratesPosUpdate = ratesController.state.rates; + // checks for the RatesController:stateChange event + expect(publishActionSpy).toHaveBeenCalledTimes(2); expect(fetchExchangeRateStub).toHaveBeenCalled(); expect(ratesPosUpdate).toStrictEqual({ btc: { conversionDate: MOCK_TIMESTAMP, conversionRate: mockRateValue, - usdConversionRate: mockRateValue, + usdConversionRate: null, }, }); + + await ratesController.start(); + + // since the polling has already started + // a second call to the start method should + // return immediately and no extra logic is executed + expect(publishActionSpy).not.toHaveBeenNthCalledWith(3); }); it('starts the polling process with custom values', async () => { @@ -231,24 +250,47 @@ describe('RatesController', () => { }); it('stops the polling process', async () => { + const messenger = buildMessenger(); + const publishActionSpy = jest.spyOn(messenger, 'publish'); const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ initialState: {}, - messenger: buildMessenger(), + messenger, fetchMultiExchangeRate: fetchExchangeRateStub, }); await ratesController.start(); + expect(publishActionSpy).toHaveBeenNthCalledWith( + 1, + `${ratesControllerName}:startPolling`, + ); + await advanceTime({ clock, duration: 200 }); expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1); await ratesController.stop(); + // check the 3rd call since the 2nd one is for the + // event stateChange + expect(publishActionSpy).toHaveBeenNthCalledWith( + 3, + `${ratesControllerName}:stopPolling`, + ); + await advanceTime({ clock, duration: 200 }); expect(fetchExchangeRateStub).toHaveBeenCalledTimes(1); + + await ratesController.stop(); + + // check if the stop method is called again, it returns early + // and no extra logic is executed + expect(publishActionSpy).not.toHaveBeenNthCalledWith( + 4, + `${ratesControllerName}:stopPolling`, + ); }); }); @@ -309,5 +351,18 @@ describe('RatesController', () => { const currencyPostUpdate = ratesController.state.currency; expect(currencyPostUpdate).toBe('eur'); }); + + it('throws if input is an empty string', async () => { + const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + const ratesController = setupRatesController({ + initialState: {}, + messenger: buildMessenger(), + fetchMultiExchangeRate: fetchExchangeRateStub, + }); + + await expect(ratesController.setCurrency('')).rejects.toThrow( + 'The currency can not be an empty string', + ); + }); }); }); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 79a7030943..1bc6347fbe 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -168,7 +168,7 @@ export class RatesController extends BaseController< async setCurrency(currency: string) { if (currency === '') { - throw new Error("The currency can't be an empty string"); + throw new Error('The currency can not be an empty string'); } const releaseLock = await this.#mutex.acquire(); try { diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts index f18e435ab5..d9817d7e34 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts @@ -171,4 +171,26 @@ describe('CryptoCompare', () => { sol: { cad: 4000.42, usd: 3000.42 }, }); }); + + it('should not return USD value if not requested', async () => { + nock(cryptoCompareHost) + .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=EUR') + .reply(200, { + BTC: { EUR: 1000 }, + ETH: { EUR: 2000 }, + SOL: { EUR: 3000 }, + }); + + const response = await fetchMultiExchangeRate( + 'EUR', + ['BTC', 'ETH', 'SOL'], + false, + ); + + expect(response).toStrictEqual({ + btc: { eur: 1000, usd: null }, + eth: { eur: 2000, usd: null }, + sol: { eur: 3000, usd: null }, + }); + }); }); From f51a478adf3b2766e0020b01dd1a7e0cdb22df66 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:22:18 -0400 Subject: [PATCH 29/69] refactor: change rate type from number to string --- .../src/RatesController/RatesController.ts | 4 ++-- packages/assets-controllers/src/RatesController/types.ts | 6 +++++- .../src/crypto-compare-service/crypto-compare.ts | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 1bc6347fbe..b4ff3be021 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -28,7 +28,7 @@ const defaultState = { rates: { btc: { conversionDate: 0, - conversionRate: 0, + conversionRate: '0', usdConversionRate: null, }, }, @@ -96,7 +96,7 @@ export class RatesController extends BaseController< for (const [key, value] of Object.entries(response)) { updatedRates[key] = { conversionDate: Date.now() / 1000, - conversionRate: value[currency], + conversionRate: value[currency] as string, usdConversionRate: value.usd || null, }; } diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index a86f6c7b8a..f573d038f3 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -13,7 +13,11 @@ import type { name as ratesControllerName } from './RatesController'; * and its value is either a number representing the conversion rate to that currency, * or `null` if the conversion rate is not available. */ -export type Rate = Record; +export type Rate = { + conversionDate: number; + conversionRate: string; + usdConversionRate: string | null; +}; /** * Represents the conversion rates for multiple cryptocurrencies. diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index ea4aa169f7..d9c8774786 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -124,7 +124,7 @@ export async function fetchMultiExchangeRate( currency: string, cryptocurrencies: string[], includeUSDRate?: boolean, -): Promise { +): Promise>> { const url = getMultiPricingURL( Object.values(cryptocurrencies).join(','), currency, @@ -133,8 +133,8 @@ export async function fetchMultiExchangeRate( const response = await handleFetch(url); handleErrorResponse(response); - const rates: ConversionRates = {}; - for (const [key, value] of Object.entries(response) as [string, Rate][]) { + const rates: Record> = {}; + for (const [key, value] of Object.entries(response) as [string, Record][]) { rates[key.toLowerCase()] = { [currency.toLowerCase()]: value[currency.toUpperCase()], usd: value.USD || null, From bd727dc774eb057fe6ef10e37991f8d50dcfe4bd Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:26:35 -0400 Subject: [PATCH 30/69] test: update unit tests data type --- .../RatesController/RatesController.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 37902ff757..4fd2f42750 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -20,7 +20,7 @@ const MOCK_TIMESTAMP = 1709983353; * Returns a stubbed date based on a predefined timestamp. * @returns The stubbed date in milliseconds. */ -const getStubbedDate = () => { +function getStubbedDate(): number { return new Date(MOCK_TIMESTAMP * 1000).getTime(); }; @@ -122,7 +122,7 @@ describe('RatesController', () => { const publishActionSpy = jest.spyOn(messenger, 'publish'); jest.spyOn(global.Date, 'now').mockImplementation(() => getStubbedDate()); - const mockRateValue = 57715.42; + const mockRateValue = '57715.42'; const fetchExchangeRateStub = jest.fn(() => { return Promise.resolve({ btc: { @@ -143,7 +143,7 @@ describe('RatesController', () => { expect(ratesPreUpdate).toStrictEqual({ btc: { conversionDate: 0, - conversionRate: 0, + conversionRate: '0', usdConversionRate: null, }, }); @@ -180,12 +180,12 @@ describe('RatesController', () => { it('starts the polling process with custom values', async () => { jest.spyOn(global.Date, 'now').mockImplementation(() => getStubbedDate()); - const mockBtcUsdRateValue = 62235.48; - const mockSolUsdRateValue = 148.41; - const mockStrkUsdRateValue = 1.248; - const mockBtcEurRateValue = 57715.42; - const mockSolEurRateValue = 137.68; - const mockStrkEurRateValue = 1.157; + const mockBtcUsdRateValue = '62235.48'; + const mockSolUsdRateValue = '148.41'; + const mockStrkUsdRateValue = '1.248'; + const mockBtcEurRateValue = '57715.42'; + const mockSolEurRateValue = '137.68'; + const mockStrkEurRateValue = '1.157'; const fetchExchangeRateStub = jest.fn(() => { return Promise.resolve({ btc: { From c60c4aa9e230f6635b07d84c2be84fde52ac08ee Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:40:54 -0400 Subject: [PATCH 31/69] refactor: make includeUsdRate not optional --- .../src/RatesController/RatesController.test.ts | 9 ++++++++- packages/assets-controllers/src/RatesController/types.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 4fd2f42750..d0a3ec0766 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -70,7 +70,7 @@ function setupRatesController({ }: { initialState: Partial; messenger: ControllerMessenger; - includeUsdRate?: boolean; + includeUsdRate: boolean; fetchMultiExchangeRate: typeof defaultFetchExchangeRate; }) { const ratesControllerMessenger = buildRatesControllerMessenger(messenger); @@ -136,6 +136,8 @@ describe('RatesController', () => { }, messenger, fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, + }); const ratesPreUpdate = ratesController.state.rates; @@ -257,6 +259,7 @@ describe('RatesController', () => { initialState: {}, messenger, fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, }); await ratesController.start(); @@ -304,6 +307,7 @@ describe('RatesController', () => { }, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, }); const cryptocurrencyList = ratesController.getCryptocurrencyList(); @@ -319,6 +323,7 @@ describe('RatesController', () => { initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, }); const cryptocurrencyListPreUpdate = @@ -341,6 +346,7 @@ describe('RatesController', () => { initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, }); const currencyPreUpdate = ratesController.state.currency; @@ -358,6 +364,7 @@ describe('RatesController', () => { initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, + includeUsdRate: false, }); await expect(ratesController.setCurrency('')).rejects.toThrow( diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index f573d038f3..86c13327b1 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -99,7 +99,7 @@ export type RatesControllerOptions = { /** * Whether to include USD rates in the conversion rates. */ - includeUsdRate?: boolean; + includeUsdRate: boolean; /** * The polling interval in milliseconds. */ From bc8e9bc501c5eb426e095ab21d0a9135c803a703 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:42:57 -0400 Subject: [PATCH 32/69] refactor: change DefaultCurrencies to enum --- .../src/RatesController/RatesController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index b4ff3be021..2f01293cf1 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -11,8 +11,8 @@ import type { export const name = 'RatesController'; -const DEFAULT_CURRENCIES = { - btc: 'btc', +enum DefaultCurrencies { + btc = 'btc', }; const DEFAULT_INTERVAL = 180000; @@ -32,7 +32,7 @@ const defaultState = { usdConversionRate: null, }, }, - fromCurrencies: [DEFAULT_CURRENCIES.btc], + fromCurrencies: [DefaultCurrencies.btc], }; export class RatesController extends BaseController< From ff06cc47fa58da48c6e41ca61620ef53e87ac46e Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:52:46 -0400 Subject: [PATCH 33/69] refactor: use DefaultCurrencies value in defaultState --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 2f01293cf1..31d54b3185 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -26,7 +26,7 @@ const metadata = { const defaultState = { currency: 'usd', rates: { - btc: { + [DefaultCurrencies.btc]: { conversionDate: 0, conversionRate: '0', usdConversionRate: null, From 898d455186e20f36ace35a8e1edb194a3586fefb Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 20:55:09 -0400 Subject: [PATCH 34/69] chore: add comment to explain ms convertion --- .../assets-controllers/src/RatesController/RatesController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 31d54b3185..35b7752610 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -95,6 +95,7 @@ export class RatesController extends BaseController< const updatedRates: ConversionRates = {}; for (const [key, value] of Object.entries(response)) { updatedRates[key] = { + // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, conversionRate: value[currency] as string, usdConversionRate: value.usd || null, From 58b2093e93bb79a195fc36f54d7e74a250c8dc2b Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 21:04:08 -0400 Subject: [PATCH 35/69] refactor: lock logic --- .../src/RatesController/RatesController.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 35b7752610..72d1a51ab0 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -82,10 +82,8 @@ export class RatesController extends BaseController< * Updates the rates by fetching new data. */ async updateRates(): Promise { - const releaseLock = await this.#mutex.acquire(); - const { currency, fromCurrencies } = this.state; - - try { + await this.#withLock(async () => { + const { currency, fromCurrencies } = this.state; const response = await this.#fetchMultiExchangeRate( currency, fromCurrencies, @@ -108,6 +106,13 @@ export class RatesController extends BaseController< rates: updatedRates, }; }); + }) + } + + async #withLock(f: () => R) { + const releaseLock = await this.#mutex.acquire(); + try { + return f(); } finally { releaseLock(); } @@ -154,34 +159,29 @@ export class RatesController extends BaseController< } async setCryptocurrencyList(list: string[]): Promise { - const releaseLock = await this.#mutex.acquire(); - try { + await this.#withLock(() => { this.update(() => { return { ...this.state, fromCurrencies: list, }; }); - } finally { - releaseLock(); - } + }) } async setCurrency(currency: string) { if (currency === '') { throw new Error('The currency can not be an empty string'); } - const releaseLock = await this.#mutex.acquire(); - try { + + await this.#withLock(() => { this.update(() => { return { ...defaultState, currency, }; }); - } finally { - releaseLock(); - } + }) await this.updateRates(); } } From 265f6d1e3d91b177eb851fd90d423c8648c1deb7 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 21:07:28 -0400 Subject: [PATCH 36/69] chore: refactor event names --- .../src/RatesController/RatesController.ts | 4 ++-- .../assets-controllers/src/RatesController/index.ts | 4 ++-- .../assets-controllers/src/RatesController/types.ts | 12 ++++++------ packages/assets-controllers/src/index.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 72d1a51ab0..201edfefe9 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -133,7 +133,7 @@ export class RatesController extends BaseController< return; } - this.messagingSystem.publish(`${name}:startPolling`); + this.messagingSystem.publish(`${name}:pollingStarted`); this.#intervalId = setInterval(() => { this.#executePoll().catch(console.error); @@ -150,7 +150,7 @@ export class RatesController extends BaseController< clearInterval(this.#intervalId); this.#intervalId = undefined; - this.messagingSystem.publish(`${name}:stopPolling`); + this.messagingSystem.publish(`${name}:pollingStopped`); } getCryptocurrencyList(): string[] { diff --git a/packages/assets-controllers/src/RatesController/index.ts b/packages/assets-controllers/src/RatesController/index.ts index e87400fe1e..51dcaaa0ad 100644 --- a/packages/assets-controllers/src/RatesController/index.ts +++ b/packages/assets-controllers/src/RatesController/index.ts @@ -4,7 +4,7 @@ export type { RatesControllerEvents, RatesControllerActions, RatesControllerGetStateAction, - RatesControllerStopPollingEvent, RatesControllerStateChangeEvent, - RatesControllerStartPollingEvent, + RatesControllerPollingStartedEvent, + RatesControllerPollingStoppedEvent, } from './types'; diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 86c13327b1..5e35d12efd 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -53,13 +53,13 @@ export type RatesControllerStateChangeEvent = ControllerStateChangeEvent< RatesControllerState >; -export type RatesControllerStartPollingEvent = { - type: `${typeof ratesControllerName}:startPolling`; +export type RatesControllerPollingStartedEvent = { + type: `${typeof ratesControllerName}:pollingStarted`; payload: []; }; -export type RatesControllerStopPollingEvent = { - type: `${typeof ratesControllerName}:stopPolling`; +export type RatesControllerPollingStoppedEvent = { + type: `${typeof ratesControllerName}:pollingStopped`; payload: []; }; @@ -68,8 +68,8 @@ export type RatesControllerStopPollingEvent = { */ export type RatesControllerEvents = | RatesControllerStateChangeEvent - | RatesControllerStartPollingEvent - | RatesControllerStopPollingEvent; + | RatesControllerPollingStartedEvent + | RatesControllerPollingStoppedEvent; export type RatesControllerGetStateAction = ControllerGetStateAction< typeof ratesControllerName, diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 35c8e19e29..72b9691d3f 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -64,7 +64,7 @@ export type { RatesControllerEvents, RatesControllerActions, RatesControllerGetStateAction, - RatesControllerStopPollingEvent, RatesControllerStateChangeEvent, - RatesControllerStartPollingEvent, + RatesControllerPollingStartedEvent, + RatesControllerPollingStoppedEvent, } from './RatesController'; From c64cf5a55da6546d3852fcd317d2ade577817fca Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 21:14:49 -0400 Subject: [PATCH 37/69] test: update RatesController unit test --- .../src/RatesController/RatesController.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index d0a3ec0766..e89d0b409e 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -154,7 +154,7 @@ describe('RatesController', () => { expect(publishActionSpy).toHaveBeenNthCalledWith( 1, - `${ratesControllerName}:startPolling`, + `${ratesControllerName}:pollingStarted`, ); await advanceTime({ clock, duration: 200 }); @@ -266,7 +266,7 @@ describe('RatesController', () => { expect(publishActionSpy).toHaveBeenNthCalledWith( 1, - `${ratesControllerName}:startPolling`, + `${ratesControllerName}:pollingStarted`, ); await advanceTime({ clock, duration: 200 }); @@ -279,7 +279,7 @@ describe('RatesController', () => { // event stateChange expect(publishActionSpy).toHaveBeenNthCalledWith( 3, - `${ratesControllerName}:stopPolling`, + `${ratesControllerName}:pollingStopped`, ); await advanceTime({ clock, duration: 200 }); @@ -292,7 +292,7 @@ describe('RatesController', () => { // and no extra logic is executed expect(publishActionSpy).not.toHaveBeenNthCalledWith( 4, - `${ratesControllerName}:stopPolling`, + `${ratesControllerName}:pollingStopped`, ); }); }); From 729008411e81d1d5458af6aabffc375c40733140 Mon Sep 17 00:00:00 2001 From: gantunesr Date: Sun, 19 May 2024 21:15:32 -0400 Subject: [PATCH 38/69] refactor: getMultiPricingURL method --- .../crypto-compare-service/crypto-compare.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index d9c8774786..2d1325c90b 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -50,9 +50,19 @@ function getMultiPricingURL( tsyms: string, includeUSDRate?: boolean, ) { - const updatedTsyms = - includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; - return `${CRYPTO_COMPARE_DOMAIN}/data/pricemulti?fsyms=${fsyms}&tsyms=${updatedTsyms}`; + // Ensure USD is included in tsyms if required + const updatedTsyms = includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; + + // Use URLSearchParams for safe parameter encoding + const params = new URLSearchParams(); + params.append('fsyms', fsyms); + params.append('tsyms', updatedTsyms); + + // Construct the URL safely + const url = new URL(`${CRYPTO_COMPARE_DOMAIN}/data/pricemulti`); + url.search = params.toString(); + + return url.toString(); } /** @@ -123,7 +133,7 @@ export async function fetchExchangeRate( export async function fetchMultiExchangeRate( currency: string, cryptocurrencies: string[], - includeUSDRate?: boolean, + includeUSDRate: boolean, ): Promise>> { const url = getMultiPricingURL( Object.values(cryptocurrencies).join(','), From ccca895c2aaa1d452f920494860f240d0ab26433 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Sun, 19 May 2024 21:16:08 -0400 Subject: [PATCH 39/69] refactor: remove comments --- .../src/crypto-compare-service/crypto-compare.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index 2d1325c90b..a620901696 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -50,15 +50,12 @@ function getMultiPricingURL( tsyms: string, includeUSDRate?: boolean, ) { - // Ensure USD is included in tsyms if required const updatedTsyms = includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; - // Use URLSearchParams for safe parameter encoding const params = new URLSearchParams(); params.append('fsyms', fsyms); params.append('tsyms', updatedTsyms); - // Construct the URL safely const url = new URL(`${CRYPTO_COMPARE_DOMAIN}/data/pricemulti`); url.search = params.toString(); From 5255f585762858d0e9f1af683dd8c03456e47013 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Sun, 19 May 2024 21:56:51 -0400 Subject: [PATCH 40/69] fix: lint --- .../src/RatesController/RatesController.test.ts | 3 +-- .../src/RatesController/RatesController.ts | 8 ++++---- .../src/crypto-compare-service/crypto-compare.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index e89d0b409e..2b9f5ff777 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -22,7 +22,7 @@ const MOCK_TIMESTAMP = 1709983353; */ function getStubbedDate(): number { return new Date(MOCK_TIMESTAMP * 1000).getTime(); -}; +} /** * Builds a new ControllerMessenger instance for RatesController. @@ -137,7 +137,6 @@ describe('RatesController', () => { messenger, fetchMultiExchangeRate: fetchExchangeRateStub, includeUsdRate: false, - }); const ratesPreUpdate = ratesController.state.rates; diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 201edfefe9..2a45502bdf 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -13,7 +13,7 @@ export const name = 'RatesController'; enum DefaultCurrencies { btc = 'btc', -}; +} const DEFAULT_INTERVAL = 180000; @@ -106,7 +106,7 @@ export class RatesController extends BaseController< rates: updatedRates, }; }); - }) + }); } async #withLock(f: () => R) { @@ -166,7 +166,7 @@ export class RatesController extends BaseController< fromCurrencies: list, }; }); - }) + }); } async setCurrency(currency: string) { @@ -181,7 +181,7 @@ export class RatesController extends BaseController< currency, }; }); - }) + }); await this.updateRates(); } } diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index a620901696..a969ba93bd 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -1,7 +1,5 @@ import { handleFetch } from '@metamask/controller-utils'; -import type { Rate, ConversionRates } from '../RatesController/types'; - /** * A map from native currency symbol to CryptoCompare identifier. * This is only needed when the values don't match. @@ -50,7 +48,8 @@ function getMultiPricingURL( tsyms: string, includeUSDRate?: boolean, ) { - const updatedTsyms = includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; + const updatedTsyms = + includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; const params = new URLSearchParams(); params.append('fsyms', fsyms); @@ -141,7 +140,10 @@ export async function fetchMultiExchangeRate( handleErrorResponse(response); const rates: Record> = {}; - for (const [key, value] of Object.entries(response) as [string, Record][]) { + for (const [key, value] of Object.entries(response) as [ + string, + Record, + ][]) { rates[key.toLowerCase()] = { [currency.toLowerCase()]: value[currency.toUpperCase()], usd: value.USD || null, From 1d547e54e0349b9fdf2faf010a53fae3dbf0afe8 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Sun, 19 May 2024 23:59:36 -0400 Subject: [PATCH 41/69] refactor: make includeUSDRate not optional --- .../src/crypto-compare-service/crypto-compare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index a969ba93bd..02833431f1 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -46,7 +46,7 @@ function getPricingURL( function getMultiPricingURL( fsyms: string, tsyms: string, - includeUSDRate?: boolean, + includeUSDRate = false, ) { const updatedTsyms = includeUSDRate && !tsyms.includes('USD') ? `${tsyms},USD` : tsyms; From c4118e0099ecbcb866e5c0cebd4be96656c1ef0c Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 09:07:07 -0400 Subject: [PATCH 42/69] fix: intervalLength data type --- .../assets-controllers/src/RatesController/RatesController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 2a45502bdf..5477ec4a98 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -46,9 +46,9 @@ export class RatesController extends BaseController< readonly #includeUsdRate; - #intervalId: NodeJS.Timeout | undefined; + #intervalLength: number; - #intervalLength: number | undefined; + #intervalId: NodeJS.Timeout | undefined; /** * Creates a RatesController instance. From f54c5989df13a647bcd424fda338155c45885cca Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 11:40:05 -0400 Subject: [PATCH 43/69] docs: add RatesController README docs --- packages/assets-controllers/README.md | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/assets-controllers/README.md b/packages/assets-controllers/README.md index 95dcb8b045..54ec4fdd62 100644 --- a/packages/assets-controllers/README.md +++ b/packages/assets-controllers/README.md @@ -19,12 +19,41 @@ This package features the following controllers: - [**CollectibleDetectionController**](src/CollectibleDetectionController.ts) keeps a periodically updated list of ERC-721 tokens assigned to the currently selected address. - [**CollectiblesController**](src/CollectiblesController.ts) tracks ERC-721 and ERC-1155 tokens assigned to the currently selected address, using OpenSea to retrieve token information. - [**CurrencyRateController**](src/CurrencyRateController.ts) keeps a periodically updated value of the exchange rate from the currently selected "native" currency to another (handling testnet tokens specially). +- [**RatesController**](src/RatesController/RatesController.ts) keeps a periodically updated value for the exchange rates for different cryptocurrencies. The difference between the `RatesController` and `CurrencyRateController` is that the second one is coupled to the `NetworksController` and is EVM specific, whilst the first one can handle different blockchain currencies like BTC and SOL. - [**TokenBalancesController**](src/TokenBalancesController.ts) keeps a periodically updated set of balances for the current set of ERC-20 tokens. - [**TokenDetectionController**](src/TokenDetectionController.ts) keeps a periodically updated list of ERC-20 tokens assigned to the currently selected address. - [**TokenListController**](src/TokenListController.ts) uses the MetaSwap API to keep a periodically updated list of known ERC-20 tokens along with their metadata. - [**TokenRatesController**](src/TokenRatesController.ts) keeps a periodically updated list of exchange rates for known ERC-20 tokens relative to the currently selected native currency. - [**TokensController**](src/TokensController.ts) stores the ERC-20 and ERC-721 tokens, along with their metadata, that are listed in the wallet under the currently selected address on the currently selected chain. +### `RatesController` + +The `RatesController` is responsible for managing the state related to cryptocurrency exchange rates and periodically updating these rates by fetching new data from an external API. + +```ts +// Initialize the RatesController +const ratesController = new RatesController({ + interval: 180000, + includeUsdRate: true, + state: { + currency: 'usd', + fromCurrencies: ['btc', 'eth', 'sol'], + }, +}); + +// Start the polling process +ratesController.start().then(() => { + console.log('Polling for exchange rates has started.'); +}); + +// Stop the polling process after some time +setTimeout(() => { + ratesController.stop().then(() => { + console.log('Polling for exchange rates has stopped.'); + }); +}, 300000); +``` + ## Contributing This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). From 26ae37342727290c61b69bd819214fc0a73e19a6 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 11:42:45 -0400 Subject: [PATCH 44/69] docs: update RatesController README docs --- packages/assets-controllers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/README.md b/packages/assets-controllers/README.md index 54ec4fdd62..33d65abc76 100644 --- a/packages/assets-controllers/README.md +++ b/packages/assets-controllers/README.md @@ -36,7 +36,7 @@ const ratesController = new RatesController({ interval: 180000, includeUsdRate: true, state: { - currency: 'usd', + currency: 'eur', fromCurrencies: ['btc', 'eth', 'sol'], }, }); From 66b8a930dd757090491eef0e59457588be07044f Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 14:26:39 -0400 Subject: [PATCH 45/69] docs: udpate Rate --- .../assets-controllers/src/RatesController/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 5e35d12efd..c7dfb40e00 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -8,15 +8,15 @@ import type { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../cryp import type { name as ratesControllerName } from './RatesController'; /** - * Represents the conversion rates from one currency to others. - * Each key is a string representing the cryptocurrency code (e.g., "BTC", "SOL"), - * and its value is either a number representing the conversion rate to that currency, - * or `null` if the conversion rate is not available. + * Represents the conversion rates from one currency to others, including the conversion date. + * The `conversionRate` field is a string that maps a cryptocurrency code (e.g., "BTC") to its conversion rate. + * The `usdConversionRate` provides the conversion rate to USD as a string, or `null` if the conversion rate to USD is not available. + * The `conversionDate` is a Unix timestamp (number) indicating when the conversion rate was last updated. */ export type Rate = { - conversionDate: number; conversionRate: string; usdConversionRate: string | null; + conversionDate: number; }; /** From 13f2b2ae9b621ad11e9cfa320f155fb0501ad3a6 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 15:14:42 -0400 Subject: [PATCH 46/69] refactor: use Cryptocurrency instead of string --- .../RatesController/RatesController.test.ts | 7 +-- .../src/RatesController/RatesController.ts | 44 +++++++++---------- .../src/RatesController/types.ts | 7 ++- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 2b9f5ff777..f40496039f 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -4,6 +4,7 @@ import { useFakeTimers } from 'sinon'; import { advanceTime } from '../../../../tests/helpers'; import type { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; import { + Cryptocurrency, RatesController, name as ratesControllerName, } from './RatesController'; @@ -206,7 +207,7 @@ describe('RatesController', () => { const ratesController = setupRatesController({ initialState: { - fromCurrencies: ['btc', 'sol', 'strk'], + fromCurrencies: [Cryptocurrency.btc], currency: 'eur', }, messenger: buildMessenger(), @@ -299,7 +300,7 @@ describe('RatesController', () => { describe('getCryptocurrencyList', () => { it('returns the current cryptocurrency list', () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); - const mockCryptocurrencyList = ['btc', 'sol', 'strk']; + const mockCryptocurrencyList = [Cryptocurrency.btc]; const ratesController = setupRatesController({ initialState: { fromCurrencies: mockCryptocurrencyList, @@ -317,7 +318,7 @@ describe('RatesController', () => { describe('setCryptocurrencyList', () => { it('updates the cryptocurrency list', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); - const mockCryptocurrencyList = ['btc', 'sol', 'strk']; + const mockCryptocurrencyList = [Cryptocurrency.btc]; const ratesController = setupRatesController({ initialState: {}, messenger: buildMessenger(), diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 5477ec4a98..08dc7f319b 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -11,7 +11,7 @@ import type { export const name = 'RatesController'; -enum DefaultCurrencies { +export enum Cryptocurrency { btc = 'btc', } @@ -26,13 +26,13 @@ const metadata = { const defaultState = { currency: 'usd', rates: { - [DefaultCurrencies.btc]: { + [Cryptocurrency.btc]: { conversionDate: 0, conversionRate: '0', usdConversionRate: null, }, }, - fromCurrencies: [DefaultCurrencies.btc], + fromCurrencies: [Cryptocurrency.btc], }; export class RatesController extends BaseController< @@ -78,6 +78,22 @@ export class RatesController extends BaseController< this.#intervalLength = interval; } + async #withLock(f: () => R) { + const releaseLock = await this.#mutex.acquire(); + try { + return f(); + } finally { + releaseLock(); + } + } + + /** + * Executes the polling operation to update rates. + */ + async #executePoll(): Promise { + await this.updateRates(); + } + /** * Updates the rates by fetching new data. */ @@ -95,7 +111,7 @@ export class RatesController extends BaseController< updatedRates[key] = { // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, - conversionRate: value[currency] as string, + conversionRate: value[currency] as Cryptocurrency, usdConversionRate: value.usd || null, }; } @@ -109,22 +125,6 @@ export class RatesController extends BaseController< }); } - async #withLock(f: () => R) { - const releaseLock = await this.#mutex.acquire(); - try { - return f(); - } finally { - releaseLock(); - } - } - - /** - * Executes the polling operation to update rates. - */ - async #executePoll(): Promise { - await this.updateRates(); - } - /** * Starts the polling process. */ @@ -153,12 +153,12 @@ export class RatesController extends BaseController< this.messagingSystem.publish(`${name}:pollingStopped`); } - getCryptocurrencyList(): string[] { + getCryptocurrencyList(): Cryptocurrency[] { const { fromCurrencies } = this.state; return fromCurrencies; } - async setCryptocurrencyList(list: string[]): Promise { + async setCryptocurrencyList(list: Cryptocurrency[]): Promise { await this.#withLock(() => { this.update(() => { return { diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index c7dfb40e00..5fb6a7a848 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -5,7 +5,10 @@ import type { } from '@metamask/base-controller'; import type { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; -import type { name as ratesControllerName } from './RatesController'; +import type { + name as ratesControllerName, + Cryptocurrency, +} from './RatesController'; /** * Represents the conversion rates from one currency to others, including the conversion date. @@ -42,7 +45,7 @@ export type RatesControllerState = { /** * A list of supported cryptocurrency symbols. */ - fromCurrencies: string[]; + fromCurrencies: Cryptocurrency[]; }; /** From 8d40f6ed507e556869aa262075d282553f80abc8 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 15:15:13 -0400 Subject: [PATCH 47/69] refactor: change iterable variable name --- .../src/crypto-compare-service/crypto-compare.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index 02833431f1..d015b67adf 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -140,13 +140,13 @@ export async function fetchMultiExchangeRate( handleErrorResponse(response); const rates: Record> = {}; - for (const [key, value] of Object.entries(response) as [ + for (const [cryptocurrency, values] of Object.entries(response) as [ string, Record, ][]) { - rates[key.toLowerCase()] = { - [currency.toLowerCase()]: value[currency.toUpperCase()], - usd: value.USD || null, + rates[cryptocurrency.toLowerCase()] = { + [currency.toLowerCase()]: values[currency.toUpperCase()], + usd: values.USD || null, }; } From cda3a208d26f50a5798425e9ee8409da28f7dba7 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 15:17:39 -0400 Subject: [PATCH 48/69] docs: update definition --- packages/assets-controllers/src/RatesController/types.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 5fb6a7a848..b79b89f1c6 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -12,8 +12,11 @@ import type { /** * Represents the conversion rates from one currency to others, including the conversion date. - * The `conversionRate` field is a string that maps a cryptocurrency code (e.g., "BTC") to its conversion rate. - * The `usdConversionRate` provides the conversion rate to USD as a string, or `null` if the conversion rate to USD is not available. + * The `conversionRate` field is a string that maps a cryptocurrency code (e.g., "BTC") to its + * conversion rate. For this field we use string as the data type to avoid potential rounding + * errors and precision loss + * The `usdConversionRate` provides the conversion rate to USD as a string, or `null` if the + * conversion rate to USD is not available. We also use string for the same reason as stated before. * The `conversionDate` is a Unix timestamp (number) indicating when the conversion rate was last updated. */ export type Rate = { From a79984e4d28341551629dd52bd9bc3a8307586fc Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 15:50:59 -0400 Subject: [PATCH 49/69] fix: Cryptocurrency enum --- .../src/RatesController/RatesController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 08dc7f319b..399ca66f32 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -12,7 +12,7 @@ import type { export const name = 'RatesController'; export enum Cryptocurrency { - btc = 'btc', + Btc = 'btc', } const DEFAULT_INTERVAL = 180000; @@ -26,13 +26,13 @@ const metadata = { const defaultState = { currency: 'usd', rates: { - [Cryptocurrency.btc]: { + [Cryptocurrency.Btc]: { conversionDate: 0, conversionRate: '0', usdConversionRate: null, }, }, - fromCurrencies: [Cryptocurrency.btc], + fromCurrencies: [Cryptocurrency.Btc], }; export class RatesController extends BaseController< From 0a19ee2a2439abdd59732c2aa4c0a5d194a3ce38 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 16:39:33 -0400 Subject: [PATCH 50/69] refactor: update iterable variable names --- .../src/RatesController/RatesController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 399ca66f32..01170b191e 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -107,12 +107,12 @@ export class RatesController extends BaseController< ); const updatedRates: ConversionRates = {}; - for (const [key, value] of Object.entries(response)) { - updatedRates[key] = { + for (const [cryptocurrency, values] of Object.entries(response)) { + updatedRates[cryptocurrency] = { // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, - conversionRate: value[currency] as Cryptocurrency, - usdConversionRate: value.usd || null, + conversionRate: values[currency] as Cryptocurrency, + usdConversionRate: values.usd || null, }; } From 8fb09ede7926f7affafb041ed66a755d8efa1ad1 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 16:46:54 -0400 Subject: [PATCH 51/69] test: fix --- .../src/RatesController/RatesController.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index f40496039f..7b3ebab62f 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -207,7 +207,7 @@ describe('RatesController', () => { const ratesController = setupRatesController({ initialState: { - fromCurrencies: [Cryptocurrency.btc], + fromCurrencies: [Cryptocurrency.Btc], currency: 'eur', }, messenger: buildMessenger(), @@ -300,7 +300,7 @@ describe('RatesController', () => { describe('getCryptocurrencyList', () => { it('returns the current cryptocurrency list', () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); - const mockCryptocurrencyList = [Cryptocurrency.btc]; + const mockCryptocurrencyList = [Cryptocurrency.Btc]; const ratesController = setupRatesController({ initialState: { fromCurrencies: mockCryptocurrencyList, @@ -318,7 +318,7 @@ describe('RatesController', () => { describe('setCryptocurrencyList', () => { it('updates the cryptocurrency list', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); - const mockCryptocurrencyList = [Cryptocurrency.btc]; + const mockCryptocurrencyList = [Cryptocurrency.Btc]; const ratesController = setupRatesController({ initialState: {}, messenger: buildMessenger(), From 9b7125a5551f691f73600ff8b4755cd210ee79a5 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 17:06:18 -0400 Subject: [PATCH 52/69] fix: type casting --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 01170b191e..da9a12aaff 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -111,7 +111,7 @@ export class RatesController extends BaseController< updatedRates[cryptocurrency] = { // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, - conversionRate: values[currency] as Cryptocurrency, + conversionRate: values[currency] as string, usdConversionRate: values.usd || null, }; } From a6086ea660009a7bb57b3207e49f1a1aa4ea3357 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 17:48:15 -0400 Subject: [PATCH 53/69] refactor: fetchMultiExchangeRate --- .../src/RatesController/RatesController.ts | 2 +- .../src/crypto-compare-service/crypto-compare.test.ts | 6 +++--- .../src/crypto-compare-service/crypto-compare.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index da9a12aaff..eb9c177fe6 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -111,7 +111,7 @@ export class RatesController extends BaseController< updatedRates[cryptocurrency] = { // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, - conversionRate: values[currency] as string, + conversionRate: values[currency], usdConversionRate: values.usd || null, }; } diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts index d9817d7e34..8f34e807ef 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts @@ -188,9 +188,9 @@ describe('CryptoCompare', () => { ); expect(response).toStrictEqual({ - btc: { eur: 1000, usd: null }, - eth: { eur: 2000, usd: null }, - sol: { eur: 3000, usd: null }, + btc: { eur: 1000 }, + eth: { eur: 2000 }, + sol: { eur: 3000 }, }); }); }); diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index d015b67adf..561ce7c4a4 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -130,7 +130,7 @@ export async function fetchMultiExchangeRate( currency: string, cryptocurrencies: string[], includeUSDRate: boolean, -): Promise>> { +): Promise>> { const url = getMultiPricingURL( Object.values(cryptocurrencies).join(','), currency, @@ -139,14 +139,14 @@ export async function fetchMultiExchangeRate( const response = await handleFetch(url); handleErrorResponse(response); - const rates: Record> = {}; + const rates: Record> = {}; for (const [cryptocurrency, values] of Object.entries(response) as [ string, Record, ][]) { rates[cryptocurrency.toLowerCase()] = { [currency.toLowerCase()]: values[currency.toUpperCase()], - usd: values.USD || null, + ...(includeUSDRate && { usd: values.USD }), }; } From af653f55103656079cb2c9d56b43f4e51131addf Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Mon, 20 May 2024 17:59:31 -0400 Subject: [PATCH 54/69] refactor: updateRates --- .../src/RatesController/RatesController.test.ts | 2 -- .../assets-controllers/src/RatesController/RatesController.ts | 3 +-- packages/assets-controllers/src/RatesController/types.ts | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 7b3ebab62f..823190d629 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -146,7 +146,6 @@ describe('RatesController', () => { btc: { conversionDate: 0, conversionRate: '0', - usdConversionRate: null, }, }); @@ -168,7 +167,6 @@ describe('RatesController', () => { btc: { conversionDate: MOCK_TIMESTAMP, conversionRate: mockRateValue, - usdConversionRate: null, }, }); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index eb9c177fe6..de644c34b2 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -29,7 +29,6 @@ const defaultState = { [Cryptocurrency.Btc]: { conversionDate: 0, conversionRate: '0', - usdConversionRate: null, }, }, fromCurrencies: [Cryptocurrency.Btc], @@ -112,7 +111,7 @@ export class RatesController extends BaseController< // Divided by 1000 to convert to ms conversionDate: Date.now() / 1000, conversionRate: values[currency], - usdConversionRate: values.usd || null, + ...(this.#includeUsdRate && { usdConversionRate: values.usd }), }; } diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index b79b89f1c6..d575676084 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -21,8 +21,8 @@ import type { */ export type Rate = { conversionRate: string; - usdConversionRate: string | null; conversionDate: number; + usdConversionRate?: string; }; /** From bf52ebe716e62ce5c5839503936256fc7bccdd00 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 13:59:47 -0400 Subject: [PATCH 55/69] chore: set conversionDate to ms --- .../src/RatesController/RatesController.test.ts | 2 +- .../assets-controllers/src/RatesController/RatesController.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 823190d629..df064a17f4 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -22,7 +22,7 @@ const MOCK_TIMESTAMP = 1709983353; * @returns The stubbed date in milliseconds. */ function getStubbedDate(): number { - return new Date(MOCK_TIMESTAMP * 1000).getTime(); + return new Date(MOCK_TIMESTAMP).getTime(); } /** diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index de644c34b2..1e9199d721 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -108,8 +108,7 @@ export class RatesController extends BaseController< const updatedRates: ConversionRates = {}; for (const [cryptocurrency, values] of Object.entries(response)) { updatedRates[cryptocurrency] = { - // Divided by 1000 to convert to ms - conversionDate: Date.now() / 1000, + conversionDate: Date.now(), conversionRate: values[currency], ...(this.#includeUsdRate && { usdConversionRate: values.usd }), }; From 75310174caad0538ac3187d64e1a13a6cdb9ec8c Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 14:02:33 -0400 Subject: [PATCH 56/69] chore: update variable name --- .../src/crypto-compare-service/crypto-compare.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts index 561ce7c4a4..6827e4e82f 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.ts @@ -121,19 +121,19 @@ export async function fetchExchangeRate( /** * Fetches the exchange rates for multiple currencies. * - * @param currency - The currency of the rates (ISO 4217). + * @param fiatCurrency - The currency of the rates (ISO 4217). * @param cryptocurrencies - The cryptocurrencies to get conversion rates for. Min length: 1. Max length: 300. * @param includeUSDRate - Whether to add the USD rate to the fetch. * @returns Promise resolving to exchange rates for given currencies. */ export async function fetchMultiExchangeRate( - currency: string, + fiatCurrency: string, cryptocurrencies: string[], includeUSDRate: boolean, ): Promise>> { const url = getMultiPricingURL( Object.values(cryptocurrencies).join(','), - currency, + fiatCurrency, includeUSDRate, ); const response = await handleFetch(url); @@ -145,7 +145,7 @@ export async function fetchMultiExchangeRate( Record, ][]) { rates[cryptocurrency.toLowerCase()] = { - [currency.toLowerCase()]: values[currency.toUpperCase()], + [fiatCurrency.toLowerCase()]: values[fiatCurrency.toUpperCase()], ...(includeUSDRate && { usd: values.USD }), }; } From 310603492ac928bf567b2acf0ed2baab9c652dd2 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 17:18:09 -0400 Subject: [PATCH 57/69] chore: update messenger type name to RatesControllerMessenger --- .../src/RatesController/RatesController.test.ts | 4 ++-- .../assets-controllers/src/RatesController/RatesController.ts | 4 ++-- packages/assets-controllers/src/RatesController/index.ts | 3 ++- packages/assets-controllers/src/RatesController/types.ts | 4 ++-- packages/assets-controllers/src/index.ts | 3 ++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index df064a17f4..198e4f93c1 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -11,7 +11,7 @@ import { import type { RatesControllerActions, RatesControllerEvents, - RatesMessenger, + RatesControllerMessenger, RatesControllerState, } from './types'; @@ -46,7 +46,7 @@ function buildMessenger(): ControllerMessenger< */ function buildRatesControllerMessenger( messenger: ControllerMessenger, -): RatesMessenger { +): RatesControllerMessenger { return messenger.getRestricted({ name: ratesControllerName, allowedEvents: [], diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 1e9199d721..95012c652e 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -3,10 +3,10 @@ import { Mutex } from 'async-mutex'; import { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service'; import type { - RatesMessenger, ConversionRates, RatesControllerState, RatesControllerOptions, + RatesControllerMessenger, } from './types'; export const name = 'RatesController'; @@ -37,7 +37,7 @@ const defaultState = { export class RatesController extends BaseController< typeof name, RatesControllerState, - RatesMessenger + RatesControllerMessenger > { readonly #mutex = new Mutex(); diff --git a/packages/assets-controllers/src/RatesController/index.ts b/packages/assets-controllers/src/RatesController/index.ts index 51dcaaa0ad..bf1a078eb2 100644 --- a/packages/assets-controllers/src/RatesController/index.ts +++ b/packages/assets-controllers/src/RatesController/index.ts @@ -1,8 +1,9 @@ -export { RatesController } from './RatesController'; +export { RatesController, Cryptocurrency } from './RatesController'; export type { RatesControllerState, RatesControllerEvents, RatesControllerActions, + RatesControllerMessenger, RatesControllerGetStateAction, RatesControllerStateChangeEvent, RatesControllerPollingStartedEvent, diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index d575676084..842e226c84 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -90,7 +90,7 @@ export type RatesControllerActions = RatesControllerGetStateAction; /** * Defines the actions that the RatesController can perform. */ -export type RatesMessenger = RestrictedControllerMessenger< +export type RatesControllerMessenger = RestrictedControllerMessenger< typeof ratesControllerName, RatesControllerActions, RatesControllerEvents, @@ -113,7 +113,7 @@ export type RatesControllerOptions = { /** * The messenger instance for communication. */ - messenger: RatesMessenger; + messenger: RatesControllerMessenger; /** * The initial state of the controller. */ diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 72b9691d3f..b276b2ccd3 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -58,11 +58,12 @@ export { CodefiTokenPricesServiceV2, SUPPORTED_CHAIN_IDS, } from './token-prices-service'; -export { RatesController } from './RatesController'; +export { RatesController, Cryptocurrency } from './RatesController'; export type { RatesControllerState, RatesControllerEvents, RatesControllerActions, + RatesControllerMessenger, RatesControllerGetStateAction, RatesControllerStateChangeEvent, RatesControllerPollingStartedEvent, From 81b5b4f8e5a1031cf1c53c025296676f62dffc6d Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 17:21:40 -0400 Subject: [PATCH 58/69] test: fix --- .../RatesController/RatesController.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 198e4f93c1..c1c3bbfe67 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -91,7 +91,7 @@ describe('RatesController', () => { jest.resetAllMocks(); }); - describe('contruct', () => { + describe('construct', () => { it('constructs the RatesController with the correct values', () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ @@ -100,11 +100,11 @@ describe('RatesController', () => { includeUsdRate: false, fetchMultiExchangeRate: fetchExchangeRateStub, }); - const { currency, rates, fromCurrencies } = ratesController.state; + const { fiatCurrency, rates, cryptocurrencies } = ratesController.state; expect(ratesController).toBeDefined(); - expect(currency).toBe('usd'); + expect(fiatCurrency).toBe('usd'); expect(Object.keys(rates)).toStrictEqual(['btc']); - expect(fromCurrencies).toStrictEqual(['btc']); + expect(cryptocurrencies).toStrictEqual(['btc']); }); }); @@ -133,7 +133,7 @@ describe('RatesController', () => { }); const ratesController = setupRatesController({ initialState: { - currency: 'eur', + fiatCurrency: 'eur', }, messenger, fetchMultiExchangeRate: fetchExchangeRateStub, @@ -205,8 +205,8 @@ describe('RatesController', () => { const ratesController = setupRatesController({ initialState: { - fromCurrencies: [Cryptocurrency.Btc], - currency: 'eur', + cryptocurrencies: [Cryptocurrency.Btc], + fiatCurrency: 'eur', }, messenger: buildMessenger(), includeUsdRate: true, @@ -301,7 +301,7 @@ describe('RatesController', () => { const mockCryptocurrencyList = [Cryptocurrency.Btc]; const ratesController = setupRatesController({ initialState: { - fromCurrencies: mockCryptocurrencyList, + cryptocurrencies: mockCryptocurrencyList, }, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, @@ -347,12 +347,12 @@ describe('RatesController', () => { includeUsdRate: false, }); - const currencyPreUpdate = ratesController.state.currency; + const currencyPreUpdate = ratesController.state.fiatCurrency; expect(currencyPreUpdate).toBe('usd'); await ratesController.setCurrency('eur'); - const currencyPostUpdate = ratesController.state.currency; + const currencyPostUpdate = ratesController.state.fiatCurrency; expect(currencyPostUpdate).toBe('eur'); }); From 3c22dd61c3e6063c96295134cb7be6140cc8cf12 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 17:30:21 -0400 Subject: [PATCH 59/69] chore: update variable names --- .../src/RatesController/RatesController.ts | 31 ++++++++++--------- .../src/RatesController/types.ts | 7 +++-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 95012c652e..17025b9526 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -18,20 +18,20 @@ export enum Cryptocurrency { const DEFAULT_INTERVAL = 180000; const metadata = { - currency: { persist: true, anonymous: true }, + fiatCurrency: { persist: true, anonymous: true }, rates: { persist: true, anonymous: true }, - fromCurrencies: { persist: true, anonymous: true }, + cryptocurrencies: { persist: true, anonymous: true }, }; const defaultState = { - currency: 'usd', + fiatCurrency: 'usd', rates: { [Cryptocurrency.Btc]: { conversionDate: 0, conversionRate: '0', }, }, - fromCurrencies: [Cryptocurrency.Btc], + cryptocurrencies: [Cryptocurrency.Btc], }; export class RatesController extends BaseController< @@ -98,10 +98,13 @@ export class RatesController extends BaseController< */ async updateRates(): Promise { await this.#withLock(async () => { - const { currency, fromCurrencies } = this.state; - const response = await this.#fetchMultiExchangeRate( - currency, - fromCurrencies, + const { fiatCurrency, cryptocurrencies } = this.state; + const response: Record< + Cryptocurrency, + Record + > = await this.#fetchMultiExchangeRate( + fiatCurrency, + cryptocurrencies, this.#includeUsdRate, ); @@ -109,7 +112,7 @@ export class RatesController extends BaseController< for (const [cryptocurrency, values] of Object.entries(response)) { updatedRates[cryptocurrency] = { conversionDate: Date.now(), - conversionRate: values[currency], + conversionRate: values[fiatCurrency], ...(this.#includeUsdRate && { usdConversionRate: values.usd }), }; } @@ -152,8 +155,8 @@ export class RatesController extends BaseController< } getCryptocurrencyList(): Cryptocurrency[] { - const { fromCurrencies } = this.state; - return fromCurrencies; + const { cryptocurrencies } = this.state; + return cryptocurrencies; } async setCryptocurrencyList(list: Cryptocurrency[]): Promise { @@ -167,8 +170,8 @@ export class RatesController extends BaseController< }); } - async setCurrency(currency: string) { - if (currency === '') { + async setCurrency(fiatCurrency: string) { + if (fiatCurrency === '') { throw new Error('The currency can not be an empty string'); } @@ -176,7 +179,7 @@ export class RatesController extends BaseController< this.update(() => { return { ...defaultState, - currency, + fiatCurrency, }; }); }); diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 842e226c84..822b695e91 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -38,9 +38,10 @@ export type ConversionRates = Record; */ export type RatesControllerState = { /** - * The base currency for conversion rates. + * The fiat currency in which conversion rates are expressed + * (i.e., the "from" currency). */ - currency: string; + fiatCurrency: string; /** * The conversion rates for multiple cryptocurrencies. */ @@ -48,7 +49,7 @@ export type RatesControllerState = { /** * A list of supported cryptocurrency symbols. */ - fromCurrencies: Cryptocurrency[]; + cryptocurrencies: Cryptocurrency[]; }; /** From e2b092a78cda8a83033c402c35ae01eca32f93d2 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 17:30:34 -0400 Subject: [PATCH 60/69] refactor: withLock --- .../src/RatesController/RatesController.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 17025b9526..a9fa4cd690 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -77,10 +77,25 @@ export class RatesController extends BaseController< this.#intervalLength = interval; } - async #withLock(f: () => R) { + /** + * Executes a function `callback` within a mutex lock to ensure that only one instance of `callback` runs at a time across all invocations of `#withLock`. + * This method is useful for synchronizing access to a resource or section of code that should not be executed concurrently. + * + * @template R - The return type of the function `callback`. + * @param callback - A callback to execute once the lock is acquired. This callback can be synchronous or asynchronous. + * @returns A promise that resolves to the result of the function `callback`. The promise is fulfilled once `callback` has completed execution. + * @example + * async function criticalLogic() { + * // Critical logic code goes here. + * } + * + * // Execute criticalLogic within a lock. + * const result = await this.#withLock(criticalLogic); + */ + async #withLock(callback: () => R) { const releaseLock = await this.#mutex.acquire(); try { - return f(); + return callback(); } finally { releaseLock(); } From b8c5bf2e6a851572e52331115b27db4198f8aa93 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 17:47:40 -0400 Subject: [PATCH 61/69] docs: update types --- packages/assets-controllers/src/RatesController/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 822b695e91..7a6201f92f 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -39,7 +39,7 @@ export type ConversionRates = Record; export type RatesControllerState = { /** * The fiat currency in which conversion rates are expressed - * (i.e., the "from" currency). + * (i.e., the "to" currency). */ fiatCurrency: string; /** @@ -48,6 +48,7 @@ export type RatesControllerState = { rates: ConversionRates; /** * A list of supported cryptocurrency symbols. + * (i.e., the "from" currencies). */ cryptocurrencies: Cryptocurrency[]; }; From c3db5cb35c10f8913ba14c30890c2a32f3446fd7 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 18:28:40 -0400 Subject: [PATCH 62/69] test: increase coverage --- .../RatesController/RatesController.test.ts | 18 +++-- .../crypto-compare.test.ts | 77 ++++++++++--------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index c1c3bbfe67..2bf76d4faa 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -57,6 +57,7 @@ function buildRatesControllerMessenger( /** * Sets up and returns a new instance of RatesController with the provided configuration. * @param config - The configuration object for the RatesController. + * @param config.interval - Polling interval. * @param config.initialState - Initial state of the controller. * @param config.messenger - ControllerMessenger instance. * @param config.includeUsdRate - Indicates if the USD rate should be included. @@ -64,19 +65,21 @@ function buildRatesControllerMessenger( * @returns A new instance of RatesController. */ function setupRatesController({ + interval, initialState, messenger, includeUsdRate, fetchMultiExchangeRate, }: { + interval?: number; initialState: Partial; messenger: ControllerMessenger; includeUsdRate: boolean; - fetchMultiExchangeRate: typeof defaultFetchExchangeRate; + fetchMultiExchangeRate?: typeof defaultFetchExchangeRate; }) { const ratesControllerMessenger = buildRatesControllerMessenger(messenger); return new RatesController({ - interval: 150, + interval, messenger: ratesControllerMessenger, state: initialState, includeUsdRate, @@ -92,13 +95,11 @@ describe('RatesController', () => { }); describe('construct', () => { - it('constructs the RatesController with the correct values', () => { - const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); + it('constructs the RatesController with default values', () => { const ratesController = setupRatesController({ initialState: {}, messenger: buildMessenger(), includeUsdRate: false, - fetchMultiExchangeRate: fetchExchangeRateStub, }); const { fiatCurrency, rates, cryptocurrencies } = ratesController.state; expect(ratesController).toBeDefined(); @@ -132,6 +133,7 @@ describe('RatesController', () => { }); }); const ratesController = setupRatesController({ + interval: 150, initialState: { fiatCurrency: 'eur', }, @@ -204,6 +206,7 @@ describe('RatesController', () => { }); const ratesController = setupRatesController({ + interval: 150, initialState: { cryptocurrencies: [Cryptocurrency.Btc], fiatCurrency: 'eur', @@ -254,6 +257,7 @@ describe('RatesController', () => { const publishActionSpy = jest.spyOn(messenger, 'publish'); const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ + interval: 150, initialState: {}, messenger, fetchMultiExchangeRate: fetchExchangeRateStub, @@ -300,6 +304,7 @@ describe('RatesController', () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const mockCryptocurrencyList = [Cryptocurrency.Btc]; const ratesController = setupRatesController({ + interval: 150, initialState: { cryptocurrencies: mockCryptocurrencyList, }, @@ -318,6 +323,7 @@ describe('RatesController', () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const mockCryptocurrencyList = [Cryptocurrency.Btc]; const ratesController = setupRatesController({ + interval: 150, initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, @@ -341,6 +347,7 @@ describe('RatesController', () => { it('sets the currency to a new value', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ + interval: 150, initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, @@ -359,6 +366,7 @@ describe('RatesController', () => { it('throws if input is an empty string', async () => { const fetchExchangeRateStub = jest.fn().mockResolvedValue({}); const ratesController = setupRatesController({ + interval: 150, initialState: {}, messenger: buildMessenger(), fetchMultiExchangeRate: fetchExchangeRateStub, diff --git a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts index 8f34e807ef..b7bdfb2664 100644 --- a/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts +++ b/packages/assets-controllers/src/crypto-compare-service/crypto-compare.test.ts @@ -150,47 +150,50 @@ describe('CryptoCompare', () => { expect(conversionRate).toBe(123); }); - it('should return CAD and USD conversion rate for BTC, ETH, and SOL', async () => { - nock(cryptoCompareHost) - .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=CAD,USD') - .reply(200, { - BTC: { CAD: 2000.42, USD: 1000.42 }, - ETH: { CAD: 3000.42, USD: 2000.42 }, - SOL: { CAD: 4000.42, USD: 3000.42 }, + describe('fetchMultiExchangeRate', () => { + it('should return CAD and USD conversion rate for BTC, ETH, and SOL', async () => { + nock(cryptoCompareHost) + .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=CAD,USD') + .reply(200, { + BTC: { CAD: 2000.42, USD: 1000.42 }, + ETH: { CAD: 3000.42, USD: 2000.42 }, + SOL: { CAD: 4000.42, USD: 3000.42 }, + }); + + const response = await fetchMultiExchangeRate( + 'CAD', + ['BTC', 'ETH', 'SOL'], + true, + ); + + expect(response).toStrictEqual({ + btc: { cad: 2000.42, usd: 1000.42 }, + eth: { cad: 3000.42, usd: 2000.42 }, + sol: { cad: 4000.42, usd: 3000.42 }, }); - - const response = await fetchMultiExchangeRate( - 'CAD', - ['BTC', 'ETH', 'SOL'], - true, - ); - - expect(response).toStrictEqual({ - btc: { cad: 2000.42, usd: 1000.42 }, - eth: { cad: 3000.42, usd: 2000.42 }, - sol: { cad: 4000.42, usd: 3000.42 }, }); - }); - it('should not return USD value if not requested', async () => { - nock(cryptoCompareHost) - .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=EUR') - .reply(200, { - BTC: { EUR: 1000 }, - ETH: { EUR: 2000 }, - SOL: { EUR: 3000 }, + it('should not return USD value if not requested', async () => { + nock(cryptoCompareHost) + .get('/data/pricemulti?fsyms=BTC,ETH,SOL&tsyms=EUR') + .reply(200, { + BTC: { EUR: 1000 }, + ETH: { EUR: 2000 }, + SOL: { EUR: 3000 }, + }); + + // @ts-expect-error Testing the case where the USD rate is not included + const response = await fetchMultiExchangeRate('EUR', [ + 'BTC', + 'ETH', + 'SOL', + ]); + + expect(response).toStrictEqual({ + btc: { eur: 1000 }, + eth: { eur: 2000 }, + sol: { eur: 3000 }, }); - - const response = await fetchMultiExchangeRate( - 'EUR', - ['BTC', 'ETH', 'SOL'], - false, - ); - - expect(response).toStrictEqual({ - btc: { eur: 1000 }, - eth: { eur: 2000 }, - sol: { eur: 3000 }, }); }); }); From a4953ae2e763f2432a4a1c5c0c0b13149bcb9592 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Tue, 21 May 2024 18:32:05 -0400 Subject: [PATCH 63/69] docs: update README --- packages/assets-controllers/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/README.md b/packages/assets-controllers/README.md index 33d65abc76..ea6618a219 100644 --- a/packages/assets-controllers/README.md +++ b/packages/assets-controllers/README.md @@ -36,8 +36,8 @@ const ratesController = new RatesController({ interval: 180000, includeUsdRate: true, state: { - currency: 'eur', - fromCurrencies: ['btc', 'eth', 'sol'], + fiatCurrency: 'eur', + cryptocurrencies: [Cryptocurrency.Btc], }, }); From aab6e053a2af86d5449a7a0cc6375fcdde963d65 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 09:34:35 -0400 Subject: [PATCH 64/69] chore: update commentary Co-authored-by: Charly Chevalier --- packages/assets-controllers/src/RatesController/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 7a6201f92f..7f32b3674d 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -14,7 +14,7 @@ import type { * Represents the conversion rates from one currency to others, including the conversion date. * The `conversionRate` field is a string that maps a cryptocurrency code (e.g., "BTC") to its * conversion rate. For this field we use string as the data type to avoid potential rounding - * errors and precision loss + * errors and precision loss. * The `usdConversionRate` provides the conversion rate to USD as a string, or `null` if the * conversion rate to USD is not available. We also use string for the same reason as stated before. * The `conversionDate` is a Unix timestamp (number) indicating when the conversion rate was last updated. From 0ea038f9ffbcb6cf5665633795e1a9c26e1beab8 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 10:15:34 -0400 Subject: [PATCH 65/69] chore: update commentary and method names --- .../RatesController/RatesController.test.ts | 8 ++++---- .../src/RatesController/RatesController.ts | 20 +++++++++++++++---- .../src/RatesController/types.ts | 8 +++++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index 2bf76d4faa..cd0350a97b 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -357,7 +357,7 @@ describe('RatesController', () => { const currencyPreUpdate = ratesController.state.fiatCurrency; expect(currencyPreUpdate).toBe('usd'); - await ratesController.setCurrency('eur'); + await ratesController.updateFiatCurrencyAndRates('eur'); const currencyPostUpdate = ratesController.state.fiatCurrency; expect(currencyPostUpdate).toBe('eur'); @@ -373,9 +373,9 @@ describe('RatesController', () => { includeUsdRate: false, }); - await expect(ratesController.setCurrency('')).rejects.toThrow( - 'The currency can not be an empty string', - ); + await expect( + ratesController.updateFiatCurrencyAndRates(''), + ).rejects.toThrow('The currency can not be an empty string'); }); }); }); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index a9fa4cd690..c7df2e50f3 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -105,13 +105,13 @@ export class RatesController extends BaseController< * Executes the polling operation to update rates. */ async #executePoll(): Promise { - await this.updateRates(); + await this.#updateRates(); } /** * Updates the rates by fetching new data. */ - async updateRates(): Promise { + async #updateRates(): Promise { await this.#withLock(async () => { const { fiatCurrency, cryptocurrencies } = this.state; const response: Record< @@ -169,11 +169,19 @@ export class RatesController extends BaseController< this.messagingSystem.publish(`${name}:pollingStopped`); } + /** + * Returns the current list of cryptocurrency. + * @returns The cryptocurrency list. + */ getCryptocurrencyList(): Cryptocurrency[] { const { cryptocurrencies } = this.state; return cryptocurrencies; } + /** + * Sets the list of supported cryptocurrencies. + * @param list - The list of supported cryptocurrencies. + */ async setCryptocurrencyList(list: Cryptocurrency[]): Promise { await this.#withLock(() => { this.update(() => { @@ -185,7 +193,11 @@ export class RatesController extends BaseController< }); } - async setCurrency(fiatCurrency: string) { + /** + * Returns the current fiat currency. + * @param fiatCurrency - The fiat currency. + */ + async updateFiatCurrencyAndRates(fiatCurrency: string) { if (fiatCurrency === '') { throw new Error('The currency can not be an empty string'); } @@ -198,6 +210,6 @@ export class RatesController extends BaseController< }; }); }); - await this.updateRates(); + await this.#updateRates(); } } diff --git a/packages/assets-controllers/src/RatesController/types.ts b/packages/assets-controllers/src/RatesController/types.ts index 7a6201f92f..fd4f229d5b 100644 --- a/packages/assets-controllers/src/RatesController/types.ts +++ b/packages/assets-controllers/src/RatesController/types.ts @@ -29,7 +29,7 @@ export type Rate = { * Represents the conversion rates for multiple cryptocurrencies. * Each key is a string representing the cryptocurrency symbol (e.g., "BTC", "SOL"), * and its value is a `Rate` object containing conversion rates from that cryptocurrency - * to various fiat currencies or other cryptocurrencies. + * to a fiat currencies and an optional USD rate. */ export type ConversionRates = Record; @@ -61,11 +61,17 @@ export type RatesControllerStateChangeEvent = ControllerStateChangeEvent< RatesControllerState >; +/** + * Type definition for the RatesController polling started event. + */ export type RatesControllerPollingStartedEvent = { type: `${typeof ratesControllerName}:pollingStarted`; payload: []; }; +/** + * Type definition for the RatesController polling stopped event. + */ export type RatesControllerPollingStoppedEvent = { type: `${typeof ratesControllerName}:pollingStopped`; payload: []; From 2c552d3612bfb22544a2629aa006efdd5a19e33b Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 11:11:49 -0400 Subject: [PATCH 66/69] chore: update method name --- .../src/RatesController/RatesController.test.ts | 8 ++++---- .../src/RatesController/RatesController.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.test.ts b/packages/assets-controllers/src/RatesController/RatesController.test.ts index cd0350a97b..aea62d27be 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.test.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.test.ts @@ -357,7 +357,7 @@ describe('RatesController', () => { const currencyPreUpdate = ratesController.state.fiatCurrency; expect(currencyPreUpdate).toBe('usd'); - await ratesController.updateFiatCurrencyAndRates('eur'); + await ratesController.setFiatCurrency('eur'); const currencyPostUpdate = ratesController.state.fiatCurrency; expect(currencyPostUpdate).toBe('eur'); @@ -373,9 +373,9 @@ describe('RatesController', () => { includeUsdRate: false, }); - await expect( - ratesController.updateFiatCurrencyAndRates(''), - ).rejects.toThrow('The currency can not be an empty string'); + await expect(ratesController.setFiatCurrency('')).rejects.toThrow( + 'The currency can not be an empty string', + ); }); }); }); diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index c7df2e50f3..54e3c7d67e 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -197,7 +197,7 @@ export class RatesController extends BaseController< * Returns the current fiat currency. * @param fiatCurrency - The fiat currency. */ - async updateFiatCurrencyAndRates(fiatCurrency: string) { + async setFiatCurrency(fiatCurrency: string) { if (fiatCurrency === '') { throw new Error('The currency can not be an empty string'); } From 3dc06e750ef35201d0d421da5f112cdfa3304a51 Mon Sep 17 00:00:00 2001 From: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 11:26:06 -0400 Subject: [PATCH 67/69] chore: update commentary Co-authored-by: Charly Chevalier --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 54e3c7d67e..09d6e8f9da 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -194,7 +194,7 @@ export class RatesController extends BaseController< } /** - * Returns the current fiat currency. + * Sets the internal fiat currency and update rates accordingly. * @param fiatCurrency - The fiat currency. */ async setFiatCurrency(fiatCurrency: string) { From 598acd534b29f71be2872bc6dc40de8e651b3821 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 11:39:34 -0400 Subject: [PATCH 68/69] fix: lint --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 09d6e8f9da..12c7ae4f6d 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -194,7 +194,7 @@ export class RatesController extends BaseController< } /** - * Sets the internal fiat currency and update rates accordingly. + * Sets the internal fiat currency and update rates accordingly. * @param fiatCurrency - The fiat currency. */ async setFiatCurrency(fiatCurrency: string) { From e11ba0faa42358c3c8d03fd7d085a0e16d38dfa2 Mon Sep 17 00:00:00 2001 From: gantunesr <17601467+gantunesr@users.noreply.github.com> Date: Wed, 22 May 2024 11:43:15 -0400 Subject: [PATCH 69/69] chore: add return type --- .../assets-controllers/src/RatesController/RatesController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/RatesController/RatesController.ts b/packages/assets-controllers/src/RatesController/RatesController.ts index 12c7ae4f6d..f5dcb89b24 100644 --- a/packages/assets-controllers/src/RatesController/RatesController.ts +++ b/packages/assets-controllers/src/RatesController/RatesController.ts @@ -197,7 +197,7 @@ export class RatesController extends BaseController< * Sets the internal fiat currency and update rates accordingly. * @param fiatCurrency - The fiat currency. */ - async setFiatCurrency(fiatCurrency: string) { + async setFiatCurrency(fiatCurrency: string): Promise { if (fiatCurrency === '') { throw new Error('The currency can not be an empty string'); }