From 617d2bd616ba0c1e1a62df9becf9ba69adef4b66 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 Nov 2025 15:23:03 -0800 Subject: [PATCH 1/3] Remove exchange rate retry With the cache, and periodic looping, this isn't really necessary and can actually slow down rate updates --- src/actions/ExchangeRateActions.ts | 176 ++++++++++++++--------------- 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/src/actions/ExchangeRateActions.ts b/src/actions/ExchangeRateActions.ts index b426d957e08..f861028143f 100644 --- a/src/actions/ExchangeRateActions.ts +++ b/src/actions/ExchangeRateActions.ts @@ -310,103 +310,101 @@ async function fetchExchangeRates( const requests = convertToRatesParams(cryptoPairMap, fiatPairMap) for (const query of requests) { - for (let attempt = 0; attempt < 5; ++attempt) { - const options = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(query) - } - try { - const response = await fetchRates('v3/rates', options) - if (response.ok) { - const json = await response.json() - const cleanedRates = asRatesParams(json) - const targetFiat = fixFiatCurrencyCode(cleanedRates.targetFiat) - - for (const cryptoRate of cleanedRates.crypto) { - const { asset, isoDate, rate } = cryptoRate - if (rate == null) continue - - const { pluginId, tokenId } = asset - const safeTokenId = tokenId ?? '' - - rates.crypto[pluginId] ??= {} - rates.crypto[pluginId][safeTokenId] ??= {} - rates.crypto[pluginId][safeTokenId][targetFiat] ??= { - current: 0, - yesterday: 0, - yesterdayTimestamp: 0, - expiration: 0 - } + const options = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(query) + } + try { + const response = await fetchRates('v3/rates', options) + if (response.ok) { + const json = await response.json() + const cleanedRates = asRatesParams(json) + const targetFiat = fixFiatCurrencyCode(cleanedRates.targetFiat) + + for (const cryptoRate of cleanedRates.crypto) { + const { asset, isoDate, rate } = cryptoRate + if (rate == null) continue + + const { pluginId, tokenId } = asset + const safeTokenId = tokenId ?? '' + + rates.crypto[pluginId] ??= {} + rates.crypto[pluginId][safeTokenId] ??= {} + rates.crypto[pluginId][safeTokenId][targetFiat] ??= { + current: 0, + yesterday: 0, + yesterdayTimestamp: 0, + expiration: 0 + } - const rateObj = rates.crypto[pluginId][safeTokenId][targetFiat] - - const isHistorical = - isoDate != null && isoDate.getTime() < now - ONE_HOUR - if (isHistorical) { - const dateTimestamp = isoDate.getTime() - const yesterdayTargetTimestamp = Date.parse(yesterday) - const yesterdayRateTimestamp = rateObj.yesterdayTimestamp - - // update yesterday rate if we find one closer than we have - if ( - Math.abs(yesterdayTargetTimestamp - dateTimestamp) < - Math.abs(yesterdayTargetTimestamp - yesterdayRateTimestamp) - ) { - rates.crypto[pluginId][safeTokenId][ - targetFiat - ].yesterdayTimestamp = yesterdayTimestamp - rateObj.yesterday = rate - } - } else { - rateObj.current = rate + const rateObj = rates.crypto[pluginId][safeTokenId][targetFiat] + + const isHistorical = + isoDate != null && isoDate.getTime() < now - ONE_HOUR + if (isHistorical) { + const dateTimestamp = isoDate.getTime() + const yesterdayTargetTimestamp = Date.parse(yesterday) + const yesterdayRateTimestamp = rateObj.yesterdayTimestamp + + // update yesterday rate if we find one closer than we have + if ( + Math.abs(yesterdayTargetTimestamp - dateTimestamp) < + Math.abs(yesterdayTargetTimestamp - yesterdayRateTimestamp) + ) { + rates.crypto[pluginId][safeTokenId][ + targetFiat + ].yesterdayTimestamp = yesterdayTimestamp + rateObj.yesterday = rate } + } else { + rateObj.current = rate + } - rateObj.expiration = rateExpiration + rateObj.expiration = rateExpiration + } + for (const fiatRate of cleanedRates.fiat) { + const { isoDate, rate } = fiatRate + const fiatCode = fixFiatCurrencyCode(fiatRate.fiatCode) + if (rate == null) continue + + rates.fiat[fiatCode] ??= {} + rates.fiat[fiatCode][targetFiat] ??= { + current: 0, + yesterday: 0, + yesterdayTimestamp: 0, + expiration: 0 } - for (const fiatRate of cleanedRates.fiat) { - const { isoDate, rate } = fiatRate - const fiatCode = fixFiatCurrencyCode(fiatRate.fiatCode) - if (rate == null) continue - - rates.fiat[fiatCode] ??= {} - rates.fiat[fiatCode][targetFiat] ??= { - current: 0, - yesterday: 0, - yesterdayTimestamp: 0, - expiration: 0 - } - const rateObj = rates.fiat[fiatCode][targetFiat] - - const isHistorical = - isoDate != null && isoDate.getTime() < now - ONE_HOUR - if (isHistorical) { - const dateTimestamp = isoDate.getTime() - const yesterdayTargetTimestamp = Date.parse(yesterday) - const yesterdayRateTimestamp = rateObj.yesterdayTimestamp - - // update yesterday rate if we find one closer than we have - if ( - Math.abs(yesterdayTargetTimestamp - dateTimestamp) < - Math.abs(yesterdayTargetTimestamp - yesterdayRateTimestamp) - ) { - rates.fiat[fiatCode][targetFiat].yesterdayTimestamp = - yesterdayTimestamp - rateObj.yesterday = rate - } - } else { - rateObj.current = rate + const rateObj = rates.fiat[fiatCode][targetFiat] + + const isHistorical = + isoDate != null && isoDate.getTime() < now - ONE_HOUR + if (isHistorical) { + const dateTimestamp = isoDate.getTime() + const yesterdayTargetTimestamp = Date.parse(yesterday) + const yesterdayRateTimestamp = rateObj.yesterdayTimestamp + + // update yesterday rate if we find one closer than we have + if ( + Math.abs(yesterdayTargetTimestamp - dateTimestamp) < + Math.abs(yesterdayTargetTimestamp - yesterdayRateTimestamp) + ) { + rates.fiat[fiatCode][targetFiat].yesterdayTimestamp = + yesterdayTimestamp + rateObj.yesterday = rate } - - rateObj.expiration = rateExpiration + } else { + rateObj.current = rate } - break + + rateObj.expiration = rateExpiration } - } catch (error: unknown) { - console.log( - `buildExchangeRates error querying rates server ${String(error)}` - ) + break } + } catch (error: unknown) { + console.log( + `buildExchangeRates error querying rates server ${String(error)}` + ) } } From 488a4e03d03ebf066792ecf4c4906976d38f5090 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 Nov 2025 15:25:38 -0800 Subject: [PATCH 2/3] Parallelize the rates queries --- src/actions/ExchangeRateActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/actions/ExchangeRateActions.ts b/src/actions/ExchangeRateActions.ts index f861028143f..b08f85fce31 100644 --- a/src/actions/ExchangeRateActions.ts +++ b/src/actions/ExchangeRateActions.ts @@ -309,7 +309,7 @@ async function fetchExchangeRates( } const requests = convertToRatesParams(cryptoPairMap, fiatPairMap) - for (const query of requests) { + const promises = requests.map(async query => { const options = { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -399,14 +399,14 @@ async function fetchExchangeRates( rateObj.expiration = rateExpiration } - break } } catch (error: unknown) { console.log( `buildExchangeRates error querying rates server ${String(error)}` ) } - } + }) + await Promise.allSettled(promises) // Update the in-memory cache: exchangeRateCache = { From d35d3b73457d2fa2ef4e5e237393fd25fbf8c09f Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 17 Nov 2025 16:15:45 -0800 Subject: [PATCH 3/3] Only add or update cached pairs if they successfully return a rate --- src/actions/ExchangeRateActions.ts | 70 ++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/actions/ExchangeRateActions.ts b/src/actions/ExchangeRateActions.ts index b08f85fce31..ebb6a79247d 100644 --- a/src/actions/ExchangeRateActions.ts +++ b/src/actions/ExchangeRateActions.ts @@ -140,14 +140,23 @@ async function loadExchangeRateCache(): Promise { const { cryptoPairs, fiatPairs, rates } = asExchangeRateCacheFile(json) // Keep un-expired asset pairs: + const cryptoPairsMap = new Map() for (const pair of cryptoPairs) { if (pair.expiration < now) continue - out.cryptoPairs.push(pair) + const tokenIdStr = + pair.asset.tokenId != null ? `_${pair.asset.tokenId}` : '' + const key = `${pair.asset.pluginId}${tokenIdStr}_${pair.targetFiat}` + cryptoPairsMap.set(key, { ...pair, isoDate: undefined }) } + out.cryptoPairs = Array.from(cryptoPairsMap.values()) + + const fiatPairsMap = new Map() for (const pair of fiatPairs) { if (pair.expiration < now) continue - out.fiatPairs.push(pair) + const key = `${pair.fiatCode}_${pair.targetFiat}` + fiatPairsMap.set(key, { ...pair, isoDate: undefined }) } + out.fiatPairs = Array.from(fiatPairsMap.values()) // Keep un-expired rates: for (const [pluginId, tokenObj] of Object.entries(rates.crypto)) { @@ -408,11 +417,64 @@ async function fetchExchangeRates( }) await Promise.allSettled(promises) + // Merge successful rate responses into the pair cache + const cryptoPairCache = [...(exchangeRateCache?.cryptoPairs ?? [])] + const fiatPairCache = [...(exchangeRateCache?.fiatPairs ?? [])] + for (const [pluginId, tokenObj] of Object.entries(rates.crypto)) { + for (const [tokenId, rateObj] of Object.entries(tokenObj)) { + for (const targetFiat of Object.keys(rateObj)) { + const edgeTokenId = tokenId === '' ? null : tokenId + const cryptoPairIndex = cryptoPairCache.findIndex( + pair => + pair.asset.pluginId === pluginId && + pair.asset.tokenId === edgeTokenId && + pair.targetFiat === targetFiat + ) + if (cryptoPairIndex === -1) { + cryptoPairCache.push({ + asset: { pluginId, tokenId: edgeTokenId }, + targetFiat, + isoDate: undefined, + expiration: pairExpiration + }) + } else { + cryptoPairCache[cryptoPairIndex] = { + asset: { pluginId, tokenId: edgeTokenId }, + targetFiat, + isoDate: undefined, + expiration: pairExpiration + } + } + } + } + } + for (const [fiatCode, fiatObj] of Object.entries(rates.fiat)) { + for (const targetFiat of Object.keys(fiatObj)) { + const fiatPairIndex = fiatPairCache.findIndex( + pair => pair.fiatCode === fiatCode && pair.targetFiat === targetFiat + ) + if (fiatPairIndex === -1) { + fiatPairCache.push({ + fiatCode, + targetFiat, + isoDate: undefined, + expiration: pairExpiration + }) + } else { + fiatPairCache[fiatPairIndex] = { + fiatCode, + targetFiat, + isoDate: undefined, + expiration: pairExpiration + } + } + } + } // Update the in-memory cache: exchangeRateCache = { rates, - cryptoPairs: Array.from(cryptoPairMap.values()), - fiatPairs: Array.from(fiatPairMap.values()) + cryptoPairs: cryptoPairCache, + fiatPairs: fiatPairCache } // Write the cache to disk: