@@ -12,6 +12,7 @@ import { StaticIntervalPollingController } from '@metamask/polling-controller';
1212import { Mutex } from 'async-mutex' ;
1313
1414import { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from './crypto-compare-service' ;
15+ import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service' ;
1516
1617/**
1718 * @type CurrencyRateState
@@ -107,6 +108,8 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
107108
108109 private readonly useExternalServices : ( ) => boolean ;
109110
111+ readonly #tokenPricesService: AbstractTokenPricesService ;
112+
110113 /**
111114 * Creates a CurrencyRateController instance.
112115 *
@@ -117,6 +120,7 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
117120 * @param options.state - Initial state to set on this controller.
118121 * @param options.useExternalServices - Feature Switch for using external services (default: true)
119122 * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.
123+ * @param options.tokenPricesService - An object in charge of retrieving token prices
120124 */
121125 constructor ( {
122126 includeUsdRate = false ,
@@ -125,13 +129,15 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
125129 messenger,
126130 state,
127131 fetchMultiExchangeRate = defaultFetchMultiExchangeRate ,
132+ tokenPricesService,
128133 } : {
129134 includeUsdRate ?: boolean ;
130135 interval ?: number ;
131136 messenger : CurrencyRateMessenger ;
132137 state ?: Partial < CurrencyRateState > ;
133138 useExternalServices ?: ( ) => boolean ;
134139 fetchMultiExchangeRate ?: typeof defaultFetchMultiExchangeRate ;
140+ tokenPricesService : AbstractTokenPricesService ;
135141 } ) {
136142 super ( {
137143 name,
@@ -143,6 +149,7 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
143149 this . useExternalServices = useExternalServices ;
144150 this . setIntervalLength ( interval ) ;
145151 this . fetchMultiExchangeRate = fetchMultiExchangeRate ;
152+ this . #tokenPricesService = tokenPricesService ;
146153 }
147154
148155 /**
@@ -168,6 +175,73 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
168175 this . updateExchangeRate ( nativeCurrencies ) ;
169176 }
170177
178+ async #fetchExchangeRatesWithFallback(
179+ nativeCurrenciesToFetch : Record < string , string > ,
180+ ) : Promise < CurrencyRateState [ 'currencyRates' ] > {
181+ const { currentCurrency } = this . state ;
182+
183+ try {
184+ const priceApiExchangeRatesResponse =
185+ await this . #tokenPricesService. fetchExchangeRates ( {
186+ baseCurrency : currentCurrency ,
187+ includeUsdRate : this . includeUsdRate ,
188+ cryptocurrencies : [
189+ ...new Set ( Object . values ( nativeCurrenciesToFetch ) ) ,
190+ ] ,
191+ } ) ;
192+
193+ const ratesPriceApi = Object . entries ( nativeCurrenciesToFetch ) . reduce (
194+ ( acc , [ nativeCurrency , fetchedCurrency ] ) => {
195+ const rate =
196+ priceApiExchangeRatesResponse [ fetchedCurrency . toLowerCase ( ) ] ;
197+
198+ acc [ nativeCurrency ] = {
199+ conversionDate : rate !== undefined ? Date . now ( ) / 1000 : null ,
200+ conversionRate : rate ?. value
201+ ? Number ( ( 1 / rate ?. value ) . toFixed ( 2 ) )
202+ : null ,
203+ usdConversionRate : rate ?. usd
204+ ? Number ( ( 1 / rate ?. usd ) . toFixed ( 2 ) )
205+ : null ,
206+ } ;
207+ return acc ;
208+ } ,
209+ { } as CurrencyRateState [ 'currencyRates' ] ,
210+ ) ;
211+ return ratesPriceApi ;
212+ } catch ( error ) {
213+ console . error ( 'Failed to fetch exchange rates.' , error ) ;
214+ }
215+
216+ // fallback to crypto compare
217+
218+ try {
219+ const fetchExchangeRateResponse = await this . fetchMultiExchangeRate (
220+ currentCurrency ,
221+ [ ...new Set ( Object . values ( nativeCurrenciesToFetch ) ) ] ,
222+ this . includeUsdRate ,
223+ ) ;
224+
225+ const rates = Object . entries ( nativeCurrenciesToFetch ) . reduce (
226+ ( acc , [ nativeCurrency , fetchedCurrency ] ) => {
227+ const rate = fetchExchangeRateResponse [ fetchedCurrency . toLowerCase ( ) ] ;
228+ acc [ nativeCurrency ] = {
229+ conversionDate : rate !== undefined ? Date . now ( ) / 1000 : null ,
230+ conversionRate : rate ?. [ currentCurrency . toLowerCase ( ) ] ?? null ,
231+ usdConversionRate : rate ?. usd ?? null ,
232+ } ;
233+ return acc ;
234+ } ,
235+ { } as CurrencyRateState [ 'currencyRates' ] ,
236+ ) ;
237+
238+ return rates ;
239+ } catch ( error ) {
240+ console . error ( 'Failed to fetch exchange rates.' , error ) ;
241+ throw error ;
242+ }
243+ }
244+
171245 /**
172246 * Updates the exchange rate for the current currency and native currency pairs.
173247 *
@@ -182,8 +256,6 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
182256
183257 const releaseLock = await this . mutex . acquire ( ) ;
184258 try {
185- const { currentCurrency } = this . state ;
186-
187259 // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.
188260 // Map each native currency to the symbol we want to fetch for it.
189261 const testnetSymbols = Object . values ( TESTNET_TICKER_SYMBOLS ) ;
@@ -201,23 +273,8 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
201273 { } as Record < string , string > ,
202274 ) ;
203275
204- const fetchExchangeRateResponse = await this . fetchMultiExchangeRate (
205- currentCurrency ,
206- [ ...new Set ( Object . values ( nativeCurrenciesToFetch ) ) ] ,
207- this . includeUsdRate ,
208- ) ;
209-
210- const rates = Object . entries ( nativeCurrenciesToFetch ) . reduce (
211- ( acc , [ nativeCurrency , fetchedCurrency ] ) => {
212- const rate = fetchExchangeRateResponse [ fetchedCurrency . toLowerCase ( ) ] ;
213- acc [ nativeCurrency ] = {
214- conversionDate : rate !== undefined ? Date . now ( ) / 1000 : null ,
215- conversionRate : rate ?. [ currentCurrency . toLowerCase ( ) ] ?? null ,
216- usdConversionRate : rate ?. usd ?? null ,
217- } ;
218- return acc ;
219- } ,
220- { } as CurrencyRateState [ 'currencyRates' ] ,
276+ const rates = await this . #fetchExchangeRatesWithFallback(
277+ nativeCurrenciesToFetch ,
221278 ) ;
222279
223280 this . update ( ( state ) => {
0 commit comments