88 asRatesParams ,
99 type RatesParams
1010} from '../util/exchangeRates'
11+ import { log } from '../util/logger'
1112import { fetchRates } from '../util/network'
1213import { datelog , fixFiatCurrencyCode , removeIsoPrefix } from '../util/utils'
1314
@@ -73,11 +74,22 @@ type ExchangeRateCacheFile = ReturnType<typeof asExchangeRateCacheFile>
7374
7475let exchangeRateCache : ExchangeRateCacheFile | undefined
7576
77+ /**
78+ * Returns the in-memory exchange-rate cache, including the subscribed
79+ * pair lists, for inclusion in support log output. The logged blob can be
80+ * turned back into rate-server query bodies with
81+ * `scripts/ratesCacheToQueries.ts`.
82+ */
83+ export function getExchangeRateCacheDump ( ) : ExchangeRateCacheFile | undefined {
84+ return exchangeRateCache
85+ }
86+
7687export function updateExchangeRates ( ) : ThunkAction < Promise < void > > {
7788 return async ( dispatch , getState ) => {
7889 const state = getState ( )
79- const { account } = state . core
90+ const { account, context } = state . core
8091 const { defaultIsoFiat } = state . ui . settings
92+ const verbose = context . logSettings ?. defaultLogLevel === 'info'
8193 const now = Date . now ( )
8294 const yesterday = getYesterdayDateRoundDownHour ( now ) . toISOString ( )
8395
@@ -112,7 +124,8 @@ export function updateExchangeRates(): ThunkAction<Promise<void>> {
112124 defaultIsoFiat ,
113125 exchangeRateCache ,
114126 now ,
115- yesterday
127+ yesterday ,
128+ verbose
116129 )
117130
118131 dispatch ( {
@@ -182,13 +195,16 @@ async function loadExchangeRateCache(): Promise<ExchangeRateCacheFile> {
182195
183196/**
184197 * Fetches exchange rates from the server, and writes them out to disk.
198+ * When `verbose` is set, requested pairs, resolved pairs, and errors are
199+ * written to the captured logs.
185200 */
186201async function fetchExchangeRates (
187202 account : EdgeAccount ,
188203 accountIsoFiat : string ,
189204 cache : ExchangeRateCacheFile ,
190205 now : number ,
191- yesterday : string
206+ yesterday : string ,
207+ verbose : boolean
192208) : Promise < void > {
193209 const { currencyWallets } = account
194210
@@ -318,11 +334,17 @@ async function fetchExchangeRates(
318334 }
319335
320336 const requests = convertToRatesParams ( cryptoPairMap , fiatPairMap )
321- const promises = requests . map ( async query => {
337+ const promises = requests . map ( async ( query , queryIndex ) => {
338+ // Log the exact request body so it can be replayed verbatim against the
339+ // rates server (e.g. `curl -X POST .../v3/rates -d <body>`):
340+ const body = JSON . stringify ( query )
341+ if ( verbose ) {
342+ log ( `rates query ${ queryIndex } request: ${ body } ` )
343+ }
322344 const options = {
323345 method : 'POST' ,
324346 headers : { 'Content-Type' : 'application/json' } ,
325- body : JSON . stringify ( query )
347+ body
326348 }
327349 try {
328350 const response = await fetchRates ( 'v3/rates' , options )
@@ -331,6 +353,30 @@ async function fetchExchangeRates(
331353 const cleanedRates = asRatesParams ( json )
332354 const targetFiat = fixFiatCurrencyCode ( cleanedRates . targetFiat )
333355
356+ if ( verbose ) {
357+ // The requested pairs are already logged above (request body), so
358+ // here we only summarize the outcome and name the pairs the server
359+ // returned without a rate:
360+ let resolvedCount = 0
361+ const noRateKeys : string [ ] = [ ]
362+ for ( const entry of cleanedRates . crypto ) {
363+ if ( entry . rate != null ) resolvedCount ++
364+ else
365+ noRateKeys . push ( cryptoRateLogKey ( entry , cleanedRates . targetFiat ) )
366+ }
367+ for ( const entry of cleanedRates . fiat ) {
368+ if ( entry . rate != null ) resolvedCount ++
369+ else noRateKeys . push ( fiatRateLogKey ( entry , cleanedRates . targetFiat ) )
370+ }
371+ log (
372+ `rates query ${ queryIndex } result: ${ resolvedCount } resolved, ${
373+ noRateKeys . length
374+ } no-rate${
375+ noRateKeys . length > 0 ? `: ${ noRateKeys . join ( ', ' ) } ` : ''
376+ } `
377+ )
378+ }
379+
334380 for ( const cryptoRate of cleanedRates . crypto ) {
335381 const { asset, isoDate, rate } = cryptoRate
336382 if ( rate == null ) continue
@@ -408,8 +454,18 @@ async function fetchExchangeRates(
408454
409455 rateObj . expiration = rateExpiration
410456 }
457+ } else if ( verbose ) {
458+ const text = await response . text ( )
459+ log (
460+ `rates query ${ queryIndex } failed: HTTP ${
461+ response . status
462+ } : ${ text . slice ( 0 , 200 ) } `
463+ )
411464 }
412465 } catch ( error : unknown ) {
466+ if ( verbose ) {
467+ log ( `rates query ${ queryIndex } error: ${ String ( error ) } ` )
468+ }
413469 console . log (
414470 `buildExchangeRates error querying rates server ${ String ( error ) } `
415471 )
@@ -494,6 +550,31 @@ const getYesterdayDateRoundDownHour = (now?: Date | number): Date => {
494550 return yesterday
495551}
496552
553+ /**
554+ * Compact log key for a crypto rate entry:
555+ * `pluginId[_tokenId]_targetFiat[@isoDate]`
556+ */
557+ function cryptoRateLogKey (
558+ entry : RatesParams [ 'crypto' ] [ number ] ,
559+ targetFiat : string
560+ ) : string {
561+ const { asset, isoDate } = entry
562+ const tokenIdStr = asset . tokenId != null ? `_${ asset . tokenId } ` : ''
563+ const dateStr = isoDate != null ? `@${ isoDate . toISOString ( ) } ` : ''
564+ return `${ asset . pluginId } ${ tokenIdStr } _${ targetFiat } ${ dateStr } `
565+ }
566+
567+ /**
568+ * Compact log key for a fiat rate entry: `fiatCode_targetFiat[@isoDate]`
569+ */
570+ function fiatRateLogKey (
571+ entry : RatesParams [ 'fiat' ] [ number ] ,
572+ targetFiat : string
573+ ) : string {
574+ const dateStr = entry . isoDate != null ? `@${ entry . isoDate . toISOString ( ) } ` : ''
575+ return `${ entry . fiatCode } _${ targetFiat } ${ dateStr } `
576+ }
577+
497578/**
498579 * Convert maps to an array of RatesParams objects grouped by targetFiat.
499580 */
0 commit comments