Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased (develop)

- changed: Optimize exchange rate fetching and asset pair cache handling
- fixed: Fixed broken `logEvent` tracking calls by adding the needed `dispatch`.
- removed: Remove change quote tracking.

Expand Down
1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,6 @@ export default [
'src/util/CurrencyInfoHelpers.ts',
'src/util/CurrencyWalletHelpers.ts',

'src/util/exchangeRates.ts',
'src/util/fake/FakeProviders.tsx',
'src/util/FioAddressUtils.ts',
'src/util/getAccountUsername.ts',
Expand Down
203 changes: 110 additions & 93 deletions src/actions/ExchangeRateActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { ThunkAction } from '../types/reduxTypes'
import {
asCryptoAsset,
asRatesParams,
createRateKey,
type RatesParams
} from '../util/exchangeRates'
import { fetchRates } from '../util/network'
Expand Down Expand Up @@ -308,113 +309,129 @@ async function fetchExchangeRates(
}
}

const cryptoPairCache = new Map<string, CryptoFiatPair>()
const fiatPairCache = new Map<string, FiatFiatPair>()
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 promises = requests.map(async query => {
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
// Save crypto assets with rates to asset cache
cryptoPairCache.set(createRateKey(cryptoRate.asset, targetFiat), {
asset: cryptoRate.asset,
targetFiat,
isoDate: undefined,
expiration: pairExpiration
})
}
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
}
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
}

rateObj.expiration = rateExpiration
} else {
rateObj.current = rate
}
break

rateObj.expiration = rateExpiration

// Save fiat assets with rates to asset cache
fiatPairCache.set(createRateKey(fiatCode, targetFiat), {
fiatCode,
targetFiat,
isoDate: undefined,
expiration: pairExpiration
})
}
} catch (error: unknown) {
console.log(
`buildExchangeRates error querying rates server ${String(error)}`
)
}
} catch (error: unknown) {
console.log(
`buildExchangeRates error querying rates server ${String(error)}`
)
}
}
})
await Promise.allSettled(promises)

// Update the in-memory cache:
exchangeRateCache = {
rates,
cryptoPairs: Array.from(cryptoPairMap.values()),
fiatPairs: Array.from(fiatPairMap.values())
cryptoPairs: Array.from(cryptoPairCache.values()),
fiatPairs: Array.from(fiatPairCache.values())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cache clears failed rate pairs, causing missing data

The exchange rate cache now only persists asset pairs that successfully returned rate data, rather than all initially requested pairs. If a rate fetch fails, the asset pair is removed from the cache and won't be tracked or re-requested until its associated wallet is active again, potentially causing missing exchange rates.

Fix in Cursor Fix in Web

}

// Write the cache to disk:
Expand Down
8 changes: 4 additions & 4 deletions src/util/exchangeRates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const doQuery = async (doFetch?: EdgeFetchFunction): Promise<void> => {

clog(`${n} deleting ${key}`)
resolverMap.delete(key)
if (resolvers.length) {
if (resolvers.length > 0) {
resolvers.forEach((r, i) => {
r(rate)
})
Expand Down Expand Up @@ -163,7 +163,7 @@ const doQuery = async (doFetch?: EdgeFetchFunction): Promise<void> => {

clog(`${n} deleting ${key}`)
resolverMap.delete(key)
if (resolvers.length) {
if (resolvers.length > 0) {
resolvers.forEach((r, i) => {
r(rate)
})
Expand Down Expand Up @@ -205,7 +205,7 @@ const addToQueue = (
resolve: Function,
maxQuerySize: number,
doFetch?: EdgeFetchFunction
) => {
): void => {
const rateKeyResolver = resolverMap.get(rateKey)
if (rateKeyResolver == null) {
// Create a new entry in the map for this pair/date
Expand All @@ -229,7 +229,7 @@ const addToQueue = (
}
}

const createRateKey = (
export const createRateKey = (
asset: { pluginId: string; tokenId?: EdgeTokenId } | string,
targetFiat: string,
date?: string
Expand Down
Loading