diff --git a/package-lock.json b/package-lock.json index 4ec628cf..cfad9ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "data-service", - "version": "0.31.2", + "version": "0.31.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0d8dfa99..3ac4c441 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-service", - "version": "0.31.2", + "version": "0.31.4", "description": "Waves data service", "main": "src/index.js", "repository": "git@github.com:wavesplatform/data-service.git", diff --git a/src/index.ts b/src/index.ts index de27c2c8..1a74f0c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,8 @@ import * as notFoundHandler from './middleware/notFoundHandler'; import { loadConfig } from './loadConfig'; import router from './endpoints'; +export const WavesId: string = 'WAVES'; + const app = unsafeKoaQs(new Koa()); const options = loadConfig(); diff --git a/src/loadConfig.ts b/src/loadConfig.ts index be414a31..9a614b8a 100644 --- a/src/loadConfig.ts +++ b/src/loadConfig.ts @@ -26,12 +26,18 @@ export type MatcherConfig = { }; }; +export type RatesConfig = { + pairAcceptanceVolumeThreshold: number; + thresholdAssetId: string; +}; + export type DefaultConfig = PostgresConfig & ServerConfig & LoggerConfig; export type DataServiceConfig = PostgresConfig & ServerConfig & LoggerConfig & - MatcherConfig; + MatcherConfig & + RatesConfig; const commonEnvVariables = ['PGHOST', 'PGDATABASE', 'PGUSER', 'PGPASSWORD']; @@ -49,14 +55,22 @@ export const loadDefaultConfig = (): DefaultConfig => { postgresPoolSize: process.env.PGPOOLSIZE ? parseInt(process.env.PGPOOLSIZE) : 20, postgresStatementTimeout: isNil(process.env.PGSTATEMENTTIMEOUT) || - isNaN(parseInt(process.env.PGSTATEMENTTIMEOUT)) + isNaN(parseInt(process.env.PGSTATEMENTTIMEOUT)) ? false : parseInt(process.env.PGSTATEMENTTIMEOUT), logLevel: process.env.LOG_LEVEL || 'info', }; }; -const envVariables = ['DEFAULT_MATCHER']; +const envVariables = ['DEFAULT_MATCHER', 'RATE_PAIR_ACCEPTANCE_VOLUME_THRESHOLD', 'RATE_THRESHOLD_ASSET_ID']; + +const ensurePositiveNumber = (x: number, msg: string) => { + if (x > 0) { + return x; + } + + throw new Error(msg); +}; const load = (): DataServiceConfig => { // assert all necessary env vars are set @@ -75,9 +89,20 @@ const load = (): DataServiceConfig => { matcher.matcher.settingsURL = process.env.MATCHER_SETTINGS_URL; } + const volumeThreshold = ensurePositiveNumber( + parseInt(process.env.RATE_PAIR_ACCEPTANCE_VOLUME_THRESHOLD as string), + 'RATE_PAIR_ACCEPTANCE_VOLUME_THRESHOLD environment variable should be a positive integer' + ); + + const rate: RatesConfig = { + pairAcceptanceVolumeThreshold: volumeThreshold, + thresholdAssetId: process.env.RATE_THRESHOLD_ASSET_ID as string + }; + return { ...loadDefaultConfig(), ...matcher, + ...rate, }; }; diff --git a/src/services/index.ts b/src/services/index.ts index 6c93fdb8..c4f17a2a 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -41,6 +41,7 @@ import createTransferTxsService, { TransferTxsService } from './transactions/tra import createUpdateAssetInfoTxsService, { UpdateAssetInfoTxsService } from './transactions/updateAssetInfo'; import { DataServiceConfig } from '../loadConfig'; import createRateService, { RateCacheImpl } from './rates'; +import { IThresholdAssetRateService, ThresholdAssetRateService } from './rates/ThresholdAssetRateService'; import { PairOrderingServiceImpl } from './PairOrderingService'; @@ -60,6 +61,9 @@ export type CommonServiceDependencies = { export type RateSerivceCreatorDependencies = CommonServiceDependencies & { cache: RateCache; + pairs: PairsService; + pairAcceptanceVolumeThreshold: number, + thresholdAssetRateService: IThresholdAssetRateService }; export type ServiceMesh = { @@ -130,6 +134,19 @@ export default ({ cache: assetsCache, }); + const pairsNoAsyncValidation = createPairsService({ + ...commonDeps, + cache: pairsCache, + validatePairs: () => taskOf(undefined), + }); + const pairsWithAsyncValidation = createPairsService({ + ...commonDeps, + cache: pairsCache, + validatePairs: validatePairs(assets, pairOrderingService), + }); + + const thresholdAssetRateService = new ThresholdAssetRateService(options.thresholdAssetId, options.matcher.defaultMatcherAddress, pairsNoAsyncValidation); + const aliasTxs = createAliasTxsService(commonDeps); const burnTxs = createBurnTxsService(commonDeps); const dataTxs = createDataTxsService(commonDeps); @@ -150,17 +167,9 @@ export default ({ const rates = createRateService({ ...commonDeps, cache: ratesCache, - }); - - const pairsNoAsyncValidation = createPairsService({ - ...commonDeps, - cache: pairsCache, - validatePairs: () => taskOf(undefined), - }); - const pairsWithAsyncValidation = createPairsService({ - ...commonDeps, - cache: pairsCache, - validatePairs: validatePairs(assets, pairOrderingService), + pairs: pairsNoAsyncValidation, + pairAcceptanceVolumeThreshold: options.pairAcceptanceVolumeThreshold, + thresholdAssetRateService: thresholdAssetRateService, }); const candlesNoAsyncValidation = createCandlesService({ diff --git a/src/services/rates/RateEstimator.ts b/src/services/rates/RateEstimator.ts index 6dc91393..0787fd23 100644 --- a/src/services/rates/RateEstimator.ts +++ b/src/services/rates/RateEstimator.ts @@ -1,37 +1,41 @@ import { BigNumber } from '@waves/data-entities'; import { Task } from 'folktale/concurrency/task'; import { Maybe } from 'folktale/maybe'; +import { isNil } from 'ramda'; -import { tap } from '../../utils/tap'; -import { AssetIdsPair, RateMgetParams } from '../../types'; import { AppError, DbError, Timeout } from '../../errorHandling'; +import { AssetIdsPair, Pair, RateMgetParams } from '../../types'; +import { tap } from '../../utils/tap'; +import { isEmpty } from '../../utils/fp/maybeOps'; -import { partitionByPreCount, AsyncMget, RateCache } from './repo'; +import { PairsService } from '../pairs'; +import { RateWithPairIds } from '../rates'; +import { IThresholdAssetRateService } from './ThresholdAssetRateService'; +import { partitionByPreComputed, AsyncMget, RateCache } from './repo'; import { RateCacheKey } from './repo/impl/RateCache'; import RateInfoLookup from './repo/impl/RateInfoLookup'; -import { isEmpty } from '../../utils/fp/maybeOps'; -import { RateWithPairIds } from '../rates'; type ReqAndRes = { req: TReq; res: Maybe; }; +export type VolumeAwareRateInfo = RateWithPairIds & { volumeWaves: BigNumber | null }; + export default class RateEstimator implements - AsyncMget< - RateMgetParams, - ReqAndRes, - AppError - > { + AsyncMget, AppError> { constructor( private readonly cache: RateCache, private readonly remoteGet: AsyncMget< RateMgetParams, RateWithPairIds, DbError | Timeout - > - ) {} + >, + private readonly pairs: PairsService, + private readonly pairAcceptanceVolumeThreshold: number, + private readonly thresholdAssetRateService: IThresholdAssetRateService + ) { } mget( request: RateMgetParams @@ -45,18 +49,18 @@ export default class RateEstimator matcher, }); - const cacheUnlessCached = (item: AssetIdsPair, rate: BigNumber) => { + const cacheUnlessCached = (item: VolumeAwareRateInfo) => { const key = getCacheKey(item); if (!this.cache.has(key)) { - this.cache.set(key, rate); + this.cache.set(key, item); } }; - const cacheAll = (items: Array) => - items.forEach(it => cacheUnlessCached(it, it.rate)); + const cacheAll = (items: Array) => + items.forEach((it) => cacheUnlessCached(it)); - const { preCount, toBeRequested } = partitionByPreCount( + const { preComputed, toBeRequested } = partitionByPreComputed( this.cache, pairs, getCacheKey, @@ -65,29 +69,63 @@ export default class RateEstimator return this.remoteGet .mget({ pairs: toBeRequested, matcher, timestamp }) - .map(results => { - if (shouldCache) cacheAll(results); - return results; - }) - .map(data => new RateInfoLookup(data.concat(preCount))) - .map(lookup => - pairs.map(idsPair => ({ - req: idsPair, - res: lookup.get(idsPair), - })) - ) - .map( - tap(data => - data.forEach(reqAndRes => - reqAndRes.res.map( - tap(res => { - if (shouldCache) { - cacheUnlessCached(reqAndRes.req, res.rate); - } - }) + .chain((pairsWithRates) => + this.pairs + .mget({ + pairs: pairsWithRates.map((pairWithRate) => ({ + amountAsset: pairWithRate.amountAsset, + priceAsset: pairWithRate.priceAsset, + })), + matcher: request.matcher, + }) + .map((foundPairs) => + foundPairs.data.map((pair: Pair, idx: number) => { + if (isNil(pair.data)) { + return { + ...pairsWithRates[idx], + volumeWaves: new BigNumber(0), + }; + } else { + return { + amountAsset: pair.amountAsset as string, + priceAsset: pair.priceAsset as string, + volumeWaves: pair.data.volumeWaves, + rate: pairsWithRates[idx].rate, + }; + } + }) + ) + .map( + tap((results) => { + if (shouldCache) cacheAll(results); + }) + ) + .chain( + (data: Array<{ amountAsset: string, priceAsset: string, volumeWaves: BigNumber | null, rate: Maybe }>) => + this.thresholdAssetRateService.get().map(thresholdAssetRate => new RateInfoLookup( + data.concat(preComputed), + new BigNumber(this.pairAcceptanceVolumeThreshold).dividedBy(thresholdAssetRate), + )) + ) + .map((lookup) => + pairs.map((idsPair) => ({ + req: idsPair, + res: lookup.get(idsPair), + })) + ) + .map( + tap((data) => + data.forEach((reqAndRes) => + reqAndRes.res.map( + tap((res) => { + if (shouldCache) { + cacheUnlessCached(res); + } + }) + ) + ) ) ) - ) ); } } diff --git a/src/services/rates/ThresholdAssetRateService.ts b/src/services/rates/ThresholdAssetRateService.ts new file mode 100644 index 00000000..c3843f6d --- /dev/null +++ b/src/services/rates/ThresholdAssetRateService.ts @@ -0,0 +1,47 @@ +import * as LRU from 'lru-cache'; +import { BigNumber } from "@waves/data-entities"; +import { Task, of as taskOf, rejected } from 'folktale/concurrency/task'; + +import { AppError } from "../../errorHandling"; +import { WavesId } from "../.."; +import { PairsService } from "../pairs"; + +export interface IThresholdAssetRateService { + get(): Task +}; + +export class ThresholdAssetRateService implements IThresholdAssetRateService { + private cache: LRU; + + constructor(private readonly thresholdAssetId: string, private readonly matcherAddress: string, private readonly pairsService: PairsService) { + this.cache = new LRU({ maxAge: 60000 }); + } + + get(): Task { + let rate = this.cache.get(this.thresholdAssetId); + if (rate === undefined) { + // rate was not set or is stale + return this.pairsService.get({ + pair: { + amountAsset: WavesId, + priceAsset: this.thresholdAssetId, + }, matcher: this.matcherAddress + }).chain(m => { + return m.matchWith({ + Just: ({ value }) => { + if (value.data === null) { + return rejected(AppError.Resolver(`Rate for pair WAVES/${this.thresholdAssetId} not found`)); + } + this.cache.set(this.thresholdAssetId, value.data.weightedAveragePrice); + return taskOf(value.data.weightedAveragePrice); + }, + Nothing: () => { + return rejected(AppError.Resolver(`Pair WAVES/${this.thresholdAssetId} not found`)); + } + }) + }); + } else { + return taskOf(rate); + } + } +} \ No newline at end of file diff --git a/src/services/rates/data.ts b/src/services/rates/data.ts index ff79ecec..f92c182e 100644 --- a/src/services/rates/data.ts +++ b/src/services/rates/data.ts @@ -39,5 +39,5 @@ export function generatePossibleRequestItems( priceAsset: WavesId, }; - return [wavesL, flip(wavesL), wavesR, flip(wavesR)]; + return [wavesL, flip(wavesL), wavesR, flip(wavesR), pair, flip(pair)]; } diff --git a/src/services/rates/index.ts b/src/services/rates/index.ts index db4702a2..19390634 100644 --- a/src/services/rates/index.ts +++ b/src/services/rates/index.ts @@ -1,26 +1,28 @@ import { BigNumber } from '@waves/data-entities'; -import { - ServiceMget, - Rate, - RateMgetParams, - list, - rate, - RateInfo, - AssetIdsPair, -} from '../../types'; +import { Maybe } from 'folktale/maybe'; + +import { ServiceMget, Rate, RateMgetParams, list, rate, AssetIdsPair } from '../../types'; import { RateSerivceCreatorDependencies } from '../../services'; import RateEstimator from './RateEstimator'; import RemoteRateRepo from './repo/impl/RemoteRateRepo'; - export { default as RateCacheImpl } from './repo/impl/RateCache'; -export type RateWithPairIds = RateInfo & AssetIdsPair; +export type RateWithPairIds = { rate: Maybe } & AssetIdsPair; export default function ({ drivers, cache, + pairs, + pairAcceptanceVolumeThreshold, + thresholdAssetRateService }: RateSerivceCreatorDependencies): ServiceMget { - const estimator = new RateEstimator(cache, new RemoteRateRepo(drivers.pg)); + const estimator = new RateEstimator( + cache, + new RemoteRateRepo(drivers.pg), + pairs, + pairAcceptanceVolumeThreshold, + thresholdAssetRateService + ); return { mget(request: RateMgetParams) { @@ -32,7 +34,11 @@ export default function ({ { rate: item.res.fold( () => new BigNumber(0), - (it) => it.rate + (it) => + it.rate.matchWith({ + Just: ({ value }) => value, + Nothing: () => new BigNumber(0), + }) ), }, { diff --git a/src/services/rates/repo/impl/RateCache.ts b/src/services/rates/repo/impl/RateCache.ts index 6b556ee5..b50d1fe2 100644 --- a/src/services/rates/repo/impl/RateCache.ts +++ b/src/services/rates/repo/impl/RateCache.ts @@ -1,11 +1,10 @@ -import { BigNumber } from '@waves/data-entities'; import { fromNullable } from 'folktale/maybe'; import * as LRU from 'lru-cache'; import { AssetIdsPair } from '../../../../types'; -import { inv } from '../../util'; import { flip } from '../../data'; import { RateCache } from '../../repo'; +import { VolumeAwareRateInfo } from '../../RateEstimator'; export type RateCacheKey = { pair: AssetIdsPair; @@ -17,7 +16,7 @@ const keyFn = (matcher: string) => (pair: AssetIdsPair): string => { }; export default class RateCacheImpl implements RateCache { - private readonly lru: LRU; + private readonly lru: LRU; constructor(size: number, maxAgeMillis: number) { this.lru = new LRU({ max: size, maxAge: maxAgeMillis }); @@ -30,15 +29,15 @@ export default class RateCacheImpl implements RateCache { ); } - set(key: RateCacheKey, rate: BigNumber) { - this.lru.set(keyFn(key.matcher)(key.pair), rate); + set(key: RateCacheKey, data: VolumeAwareRateInfo) { + this.lru.set(keyFn(key.matcher)(key.pair), data); } get(key: RateCacheKey) { const getKey = keyFn(key.matcher); return fromNullable(this.lru.get(getKey(key.pair))).orElse(() => - fromNullable(this.lru.get(getKey(flip(key.pair)))).chain(inv) + fromNullable(this.lru.get(getKey(flip(key.pair)))) ); } } diff --git a/src/services/rates/repo/impl/RateInfoLookup.ts b/src/services/rates/repo/impl/RateInfoLookup.ts index fba241cf..546d10b7 100644 --- a/src/services/rates/repo/impl/RateInfoLookup.ts +++ b/src/services/rates/repo/impl/RateInfoLookup.ts @@ -1,17 +1,18 @@ import { BigNumber } from '@waves/data-entities'; -import { Maybe, of as maybeOf, fromNullable } from 'folktale/maybe'; -import { path, complement } from 'ramda'; +import { Maybe, fromNullable, of as maybeOf } from 'folktale/maybe'; +import { path } from 'ramda'; import { AssetIdsPair, CacheSync } from '../../../../types'; import { WavesId, flip, pairHasWaves } from '../../data'; import { inv, safeDivide } from '../../util'; -import { isDefined, map2 } from '../../../../utils/fp/maybeOps'; +import { isDefined } from '../../../../utils/fp/maybeOps'; -import { RateWithPairIds } from '../../../rates' +import { RateWithPairIds } from '../../../rates'; +import { VolumeAwareRateInfo } from '../../../rates/RateEstimator'; type RateLookupTable = { [amountAsset: string]: { - [priceAsset: string]: BigNumber; + [priceAsset: string]: VolumeAwareRateInfo; }; }; @@ -26,7 +27,10 @@ export default class RateInfoLookup implements Omit, 'set'> { private readonly lookupTable: RateLookupTable; - constructor(data: Array) { + constructor( + data: Array, + private readonly pairAcceptanceVolumeThreshold: BigNumber + ) { this.lookupTable = this.toLookupTable(data); } @@ -34,26 +38,39 @@ export default class RateInfoLookup return isDefined(this.get(pair)); } - get(pair: AssetIdsPair): Maybe { + get(pair: AssetIdsPair): Maybe { const lookup = (pair: AssetIdsPair, flipped: boolean) => this.getFromLookupTable(pair, flipped); + if (pairHasWaves(pair)) { + return lookup(pair, false).orElse(() => lookup(pair, true)); + } + + let wavesPaired = this.lookupThroughWaves(pair); + return lookup(pair, false) .orElse(() => lookup(pair, true)) - .orElse(() => - maybeOf(pair) - .filter(complement(pairHasWaves)) - .chain(pair => this.lookupThroughWaves(pair)) - ); + .filter( + (val) => (val.volumeWaves !== null && val.volumeWaves.gte(this.pairAcceptanceVolumeThreshold)) || + wavesPaired.matchWith({ + Just: ({ value }) => + value.rate.matchWith({ + Just: () => false, + Nothing: () => true, + }), + Nothing: () => true, + }) + ) + .orElse(() => wavesPaired); } - private toLookupTable(data: Array): RateLookupTable { + private toLookupTable(data: Array): RateLookupTable { return data.reduce((acc, item) => { if (!(item.amountAsset in acc)) { acc[item.amountAsset] = {}; } - acc[item.amountAsset][item.priceAsset] = item.rate; + acc[item.amountAsset][item.priceAsset] = item; return acc; }, {}); @@ -62,36 +79,43 @@ export default class RateInfoLookup private getFromLookupTable( pair: AssetIdsPair, flipped: boolean - ): Maybe { + ): Maybe { const lookupData = flipped ? flip(pair) : pair; - return fromNullable( + let foundValue = fromNullable( path([lookupData.amountAsset, lookupData.priceAsset], this.lookupTable) - ) - .map((rate: BigNumber) => - flipped ? inv(rate).getOrElse(new BigNumber(0)) : rate - ) - .map((rate: BigNumber) => ({ - rate, - ...lookupData, - })); + ); + + return foundValue.map((data) => { + if (flipped) { + let flippedData = { ...data }; + flippedData.rate = flippedData.rate.chain((rate) => inv(rate)); + return flippedData; + } else { + return data; + } + }); } - private lookupThroughWaves(pair: AssetIdsPair): Maybe { - return map2( - (info1, info2) => safeDivide(info1.rate, info2.rate), - this.get({ - amountAsset: pair.amountAsset, - priceAsset: WavesId, - }), + private lookupThroughWaves(pair: AssetIdsPair): Maybe { + return this.get({ + amountAsset: pair.amountAsset, + priceAsset: WavesId, + }).chain((info1) => this.get({ amountAsset: pair.priceAsset, priceAsset: WavesId, - }) - ).map(rate => ({ - amountAsset: pair.amountAsset, - priceAsset: pair.priceAsset, - rate: rate.getOrElse(new BigNumber(0)), - })); + }).chain((info2) => + info1.rate.chain((rate1) => + info2.rate.chain((rate2) => + maybeOf({ + ...pair, + rate: safeDivide(rate1, rate2), + volumeWaves: BigNumber.max(info1.volumeWaves || 0, info2.volumeWaves || 0), + }) + ) + ) + ) + ); } } diff --git a/src/services/rates/repo/impl/RemoteRateRepo.ts b/src/services/rates/repo/impl/RemoteRateRepo.ts index 9cb56028..2876f62d 100644 --- a/src/services/rates/repo/impl/RemoteRateRepo.ts +++ b/src/services/rates/repo/impl/RemoteRateRepo.ts @@ -1,5 +1,6 @@ import * as knex from 'knex'; -import { chain, map } from 'ramda'; +import { chain } from 'ramda'; +import { fromNullable } from 'folktale/maybe'; import { Task, of as taskOf } from 'folktale/concurrency/task'; import { DbError, Timeout } from '../../../../errorHandling'; @@ -15,13 +16,8 @@ export default class RemoteRateRepo implements AsyncMget { constructor(private readonly dbDriver: PgDriver) {} - mget( - request: RateMgetParams - ): Task> { - const pairsSqlParams = chain( - it => [it.amountAsset, it.priceAsset], - request.pairs - ); + mget(request: RateMgetParams): Task> { + const pairsSqlParams = chain((it) => [it.amountAsset, it.priceAsset], request.pairs); const sql = pg.raw(makeSql(request.pairs.length), [ request.timestamp.getOrElse(new Date()), @@ -30,19 +26,19 @@ export default class RemoteRateRepo ]); const dbTask: Task = - request.pairs.length === 0 - ? taskOf([]) - : this.dbDriver.any(sql.toString()); + request.pairs.length === 0 ? taskOf([]) : this.dbDriver.any(sql.toString()); return dbTask.map( (result: any[]): Array => - map((it: any): RateWithPairIds => { - return { - amountAsset: it.amount_asset_id, - priceAsset: it.price_asset_id, - rate: it.weighted_average_price, - }; - }, result) + result.map( + (it: any): RateWithPairIds => { + return { + amountAsset: it.amount_asset_id, + priceAsset: it.price_asset_id, + rate: fromNullable(it.weighted_average_price), + }; + } + ) ); } } diff --git a/src/services/rates/repo/index.ts b/src/services/rates/repo/index.ts index 93c20a69..fbf1de64 100644 --- a/src/services/rates/repo/index.ts +++ b/src/services/rates/repo/index.ts @@ -1,27 +1,25 @@ import { partition, chain, uniqWith } from 'ramda'; -import { AssetIdsPair, CacheSync } from '../../../types'; import { BigNumber } from '@waves/data-entities'; -import { - pairIsSymmetric, - pairsEq, - generatePossibleRequestItems, -} from '../data'; -import { RateCacheKey } from './impl/RateCache'; +import { of as maybeOf } from 'folktale/maybe'; import { Task } from 'folktale/concurrency/task'; -import { RateWithPairIds } from '../../rates'; -export type RateCache = CacheSync; +import { AssetIdsPair, CacheSync } from '../../../types'; +import { pairIsSymmetric, pairsEq, generatePossibleRequestItems } from '../data'; +import { RateCacheKey } from './impl/RateCache'; +import { VolumeAwareRateInfo } from '../RateEstimator'; + +export type RateCache = CacheSync; export type AsyncMget = { mget(req: Req): Task; }; export type PairsForRequest = { - preCount: RateWithPairIds[]; + preComputed: VolumeAwareRateInfo[]; toBeRequested: AssetIdsPair[]; }; -export const partitionByPreCount = ( +export const partitionByPreComputed = ( cache: RateCache, pairs: AssetIdsPair[], getCacheKey: (pair: AssetIdsPair) => RateCacheKey, @@ -29,35 +27,32 @@ export const partitionByPreCount = ( ): PairsForRequest => { const [eq, uneq] = partition(pairIsSymmetric, pairs); - const eqRates: Array = eq.map(pair => ({ - rate: new BigNumber(1), + const eqRates: Array = eq.map((pair) => ({ + rate: maybeOf(new BigNumber(1)), + volumeWaves: new BigNumber(0), ...pair, })); const allPairsToRequest = uniqWith( pairsEq, - chain(it => generatePossibleRequestItems(it), uneq) + chain((it) => generatePossibleRequestItems(it), uneq) ); if (shouldCache) { const [cached, uncached] = partition( - it => cache.has(getCacheKey(it)), + (it) => cache.has(getCacheKey(it)), allPairsToRequest ); - const cachedRates: Array = cached.map(pair => ({ - amountAsset: pair.amountAsset, - priceAsset: pair.priceAsset, - rate: cache.get(getCacheKey(pair)).getOrElse(new BigNumber(0)), - })); + const cachedRates = cached.map((pair) => cache.get(getCacheKey(pair)).unsafeGet()); return { - preCount: cachedRates.concat(eqRates), + preComputed: cachedRates.concat(eqRates), toBeRequested: uncached, }; } else { return { - preCount: eqRates, + preComputed: eqRates, toBeRequested: allPairsToRequest, }; }