@@ -12,6 +12,8 @@ import {
1212 asGetRatesParams ,
1313 asIncomingGetRatesParams ,
1414 type CrossChainMapping ,
15+ type CryptoRate ,
16+ type FiatRate ,
1517 type GetRatesParams ,
1618 type IncomingGetRatesParams
1719} from './types'
@@ -32,9 +34,6 @@ const fixIncomingGetRatesParams = (
3234 if ( params . fiat . length > FIAT_LIMIT ) {
3335 throw new Error ( `fiat array must be less than ${ FIAT_LIMIT } ` )
3436 }
35- if ( params . targetFiat !== 'USD' ) {
36- throw new Error ( 'targetFiat must be USD' )
37- }
3837
3938 params . crypto . forEach ( crypto => {
4039 // Sanity check that the tokenId doesn't include _
@@ -114,6 +113,83 @@ automatedCrossChainSyncDoc.onChange(automatedMappings => {
114113 }
115114} )
116115
116+ const toDatedFiatKey = ( asset : FiatRate ) : string => {
117+ return `${ asset . isoDate . toISOString ( ) } _${ asset . fiatCode } `
118+ }
119+ const toDatedCryptoKey = ( asset : CryptoRate ) : string => {
120+ return `${ asset . isoDate . toISOString ( ) } _${ toCryptoKey ( asset . asset ) } `
121+ }
122+ /**
123+ * Break up a non-USD request into two queries. The first finds all the
124+ * USD rates and the second finds all of the fiat/USD rates on across all
125+ * the dates requested.
126+ */
127+ const getNonUsdRates = async (
128+ initialParams : GetRatesParams ,
129+ rightNow : Date
130+ ) : Promise < GetRatesParams > => {
131+ // Get requested rates in USD
132+ const usdParams = {
133+ ...initialParams ,
134+ targetFiat : 'USD'
135+ }
136+ const usdResult = await getRates ( usdParams , rightNow )
137+
138+ // Loop over the USD rates and store them in a map. At the same time,
139+ // save the date strings we'll need to query the original requested fiat for
140+ const usdCryptoRatesMap = new Map < string , number | undefined > ( )
141+ const dateSet = new Set < string > ( )
142+ for ( const crypto of usdResult . crypto ) {
143+ usdCryptoRatesMap . set ( toDatedCryptoKey ( crypto ) , crypto . rate )
144+ dateSet . add ( crypto . isoDate . toISOString ( ) )
145+ }
146+ const usdFiatRatesMap = new Map < string , number | undefined > ( )
147+ for ( const fiat of usdResult . fiat ) {
148+ usdFiatRatesMap . set ( toDatedFiatKey ( fiat ) , fiat . rate )
149+ dateSet . add ( fiat . isoDate . toISOString ( ) )
150+ }
151+
152+ // Get fiat/USD rates
153+ // The number of unique dates across crypto and fiat could exceed 256 so we need to chunk them
154+ const dateArray = Array . from ( dateSet )
155+ const chunkSize = FIAT_LIMIT
156+ const fiatUsdDateExchangeRateMap = new Map < string , number | undefined > ( )
157+ for ( let i = 0 ; i < dateArray . length ; i += chunkSize ) {
158+ const chunk = dateArray . slice ( i , i + chunkSize )
159+ const fiatParams = {
160+ targetFiat : 'USD' ,
161+ crypto : [ ] ,
162+ fiat : chunk . map ( date => ( {
163+ isoDate : new Date ( date ) ,
164+ fiatCode : initialParams . targetFiat ,
165+ rate : undefined
166+ } ) )
167+ }
168+ const fiatResult = await getRates ( fiatParams , rightNow )
169+ for ( const fiat of fiatResult . fiat ) {
170+ fiatUsdDateExchangeRateMap . set ( fiat . isoDate . toISOString ( ) , fiat . rate )
171+ }
172+ }
173+
174+ // Loop over the initial request and bridge the rates
175+ for ( const crypto of initialParams . crypto ) {
176+ const usdCryptoRate = usdCryptoRatesMap . get ( toDatedCryptoKey ( crypto ) )
177+ const fiatRate = fiatUsdDateExchangeRateMap . get (
178+ crypto . isoDate . toISOString ( )
179+ )
180+ if ( usdCryptoRate == null || fiatRate == null ) continue
181+ crypto . rate = usdCryptoRate / fiatRate
182+ }
183+ for ( const fiat of initialParams . fiat ) {
184+ const usdFiatRate = usdFiatRatesMap . get ( toDatedFiatKey ( fiat ) )
185+ const fiatRate = fiatUsdDateExchangeRateMap . get ( fiat . isoDate . toISOString ( ) )
186+ if ( usdFiatRate == null || fiatRate == null ) continue
187+ fiat . rate = usdFiatRate / fiatRate
188+ }
189+
190+ return initialParams
191+ }
192+
117193export const ratesV3 = async (
118194 request : ExpressRequest
119195) : Promise < HttpResponse > => {
@@ -124,7 +200,11 @@ export const ratesV3 = async (
124200 // Map all incoming crypto assets to their canonical versions
125201 const { mappedParams, originalToCanonicalKey } =
126202 applyCrossChainMappings ( params )
127- const result = await getRates ( mappedParams , rightNow )
203+
204+ const result =
205+ mappedParams . targetFiat === 'USD'
206+ ? await getRates ( mappedParams , rightNow )
207+ : await getNonUsdRates ( mappedParams , rightNow )
128208
129209 // Build a quick lookup from canonical key + isoDate -> rate
130210 const canonicalLookup = new Map < string , number > ( )
0 commit comments