{"version":3,"file":"RatesController.mjs","sourceRoot":"","sources":["../../src/RatesController/RatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type { StateMetadata } from '@metamask/base-controller';\nimport { Mutex } from 'async-mutex';\nimport type { Draft } from 'immer';\n\nimport { fetchMultiExchangeRate as defaultFetchExchangeRate } from '../crypto-compare-service';\nimport type {\n  ConversionRates,\n  RatesControllerState,\n  RatesControllerOptions,\n  RatesControllerMessenger,\n} from './types';\n\nexport const name = 'RatesController';\n\n/**\n * Supported cryptocurrencies that can be used as a base currency. The value needs to be compatible\n * with CryptoCompare's API which is the default source for the rates.\n *\n * See: https://min-api.cryptocompare.com/documentation?key=Price&cat=multipleSymbolsPriceEndpoint\n */\nexport enum Cryptocurrency {\n  Btc = 'btc',\n  Solana = 'sol',\n}\n\nconst DEFAULT_INTERVAL = 180000;\n\nconst metadata: StateMetadata<RatesControllerState> = {\n  fiatCurrency: {\n    includeInStateLogs: true,\n    persist: true,\n    includeInDebugSnapshot: true,\n    usedInUi: true,\n  },\n  rates: {\n    includeInStateLogs: false,\n    persist: true,\n    includeInDebugSnapshot: true,\n    usedInUi: true,\n  },\n  cryptocurrencies: {\n    includeInStateLogs: true,\n    persist: true,\n    includeInDebugSnapshot: true,\n    usedInUi: false,\n  },\n};\n\nconst defaultState = {\n  fiatCurrency: 'usd',\n  rates: {\n    [Cryptocurrency.Btc]: {\n      conversionDate: 0,\n      conversionRate: 0,\n    },\n    [Cryptocurrency.Solana]: {\n      conversionDate: 0,\n      conversionRate: 0,\n    },\n  },\n  cryptocurrencies: [Cryptocurrency.Btc, Cryptocurrency.Solana],\n};\n\nexport class RatesController extends BaseController<\n  typeof name,\n  RatesControllerState,\n  RatesControllerMessenger\n> {\n  readonly #mutex = new Mutex();\n\n  readonly #fetchMultiExchangeRate;\n\n  readonly #includeUsdRate;\n\n  readonly #intervalLength: number;\n\n  #intervalId: NodeJS.Timeout | undefined;\n\n  /**\n   * Creates a RatesController instance.\n   *\n   * @param options - Constructor options.\n   * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n   * @param options.interval - The polling interval, in milliseconds.\n   * @param options.messenger - A reference to the messaging system.\n   * @param options.state - Initial state to set on this controller.\n   * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n   */\n  constructor({\n    interval = DEFAULT_INTERVAL,\n    messenger,\n    state,\n    includeUsdRate,\n    fetchMultiExchangeRate = defaultFetchExchangeRate,\n  }: RatesControllerOptions) {\n    super({\n      name,\n      metadata,\n      messenger,\n      state: { ...defaultState, ...state },\n    });\n    this.#includeUsdRate = includeUsdRate;\n    this.#fetchMultiExchangeRate = fetchMultiExchangeRate;\n    this.#intervalLength = interval;\n  }\n\n  /**\n   * 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`.\n   * This method is useful for synchronizing access to a resource or section of code that should not be executed concurrently.\n   *\n   * @template R - The return type of the function `callback`.\n   * @param callback - A callback to execute once the lock is acquired. This callback can be synchronous or asynchronous.\n   * @returns A promise that resolves to the result of the function `callback`. The promise is fulfilled once `callback` has completed execution.\n   * @example\n   * async function criticalLogic() {\n   *   // Critical logic code goes here.\n   * }\n   *\n   * // Execute criticalLogic within a lock.\n   * const result = await this.#withLock(criticalLogic);\n   */\n  async #withLock<R>(callback: () => R) {\n    const releaseLock = await this.#mutex.acquire();\n    try {\n      return callback();\n    } finally {\n      releaseLock();\n    }\n  }\n\n  /**\n   * Executes the polling operation to update rates.\n   */\n  async #executePoll(): Promise<void> {\n    await this.#updateRates();\n  }\n\n  /**\n   * Updates the rates by fetching new data.\n   */\n  async #updateRates(): Promise<void> {\n    await this.#withLock(async () => {\n      const { fiatCurrency, cryptocurrencies } = this.state;\n      const response: Record<\n        Cryptocurrency,\n        Record<string, number>\n      > = await this.#fetchMultiExchangeRate(\n        fiatCurrency,\n        cryptocurrencies,\n        this.#includeUsdRate,\n      );\n\n      const updatedRates: ConversionRates = {};\n      for (const [cryptocurrency, values] of Object.entries(response)) {\n        updatedRates[cryptocurrency] = {\n          conversionDate: Date.now(),\n          conversionRate: values[fiatCurrency],\n          ...(this.#includeUsdRate && { usdConversionRate: values.usd }),\n        };\n      }\n\n      this.update(\n        (state: Draft<RatesControllerState>): RatesControllerState => {\n          return {\n            ...state,\n            rates: updatedRates,\n          };\n        },\n      );\n    });\n  }\n\n  /**\n   * Starts the polling process.\n   */\n  async start(): Promise<void> {\n    if (this.#intervalId) {\n      return;\n    }\n\n    this.messenger.publish(`${name}:pollingStarted`);\n\n    await this.#updateRates();\n\n    this.#intervalId = setInterval(() => {\n      this.#executePoll().catch(console.error);\n    }, this.#intervalLength);\n  }\n\n  /**\n   * Stops the polling process.\n   */\n  async stop(): Promise<void> {\n    if (!this.#intervalId) {\n      return;\n    }\n\n    clearInterval(this.#intervalId);\n    this.#intervalId = undefined;\n    this.messenger.publish(`${name}:pollingStopped`);\n  }\n\n  /**\n   * Returns the current list of cryptocurrency.\n   *\n   * @returns The cryptocurrency list.\n   */\n  getCryptocurrencyList(): Cryptocurrency[] {\n    const { cryptocurrencies } = this.state;\n    return cryptocurrencies;\n  }\n\n  /**\n   * Sets the list of supported cryptocurrencies.\n   *\n   * @param cryptocurrencies - The list of supported cryptocurrencies.\n   */\n  async setCryptocurrencyList(\n    cryptocurrencies: Cryptocurrency[],\n  ): Promise<void> {\n    await this.#withLock(() => {\n      this.update(\n        (state: Draft<RatesControllerState>): RatesControllerState => {\n          return {\n            ...state,\n            cryptocurrencies,\n          };\n        },\n      );\n    });\n  }\n\n  /**\n   * Sets the internal fiat currency and update rates accordingly.\n   *\n   * @param fiatCurrency - The fiat currency.\n   */\n  async setFiatCurrency(fiatCurrency: string): Promise<void> {\n    if (fiatCurrency === '') {\n      throw new Error('The currency can not be an empty string');\n    }\n\n    await this.#withLock(() => {\n      this.update(\n        (state: Draft<RatesControllerState>): RatesControllerState => {\n          return {\n            ...state,\n            fiatCurrency,\n          };\n        },\n      );\n    });\n    await this.#updateRates();\n  }\n}\n"]}