diff --git a/src/modules/auto-router/specs/auto-router.service.spec.ts b/src/modules/auto-router/specs/auto-router.service.spec.ts index db1c9463f..97fcc1a38 100644 --- a/src/modules/auto-router/specs/auto-router.service.spec.ts +++ b/src/modules/auto-router/specs/auto-router.service.spec.ts @@ -31,8 +31,8 @@ import winston from 'winston'; import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; import { ComposableTasksTransactionService } from 'src/modules/composable-tasks/services/composable.tasks.transaction'; import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; -import { PairFilteringService } from 'src/modules/pair/services/pair.filtering.service'; import { gasConfig, scAddress } from 'src/config'; +import { PairsMetadataBuilder } from 'src/modules/pair/services/pair.metadata.builder'; describe('AutoRouterService', () => { let service: AutoRouterService; @@ -85,7 +85,7 @@ describe('AutoRouterService', () => { ComposableTasksTransactionService, ApiConfigService, MXApiServiceProvider, - PairFilteringService, + PairsMetadataBuilder, ], exports: [], }).compile(); diff --git a/src/modules/pair/mocks/pair.service.mock.ts b/src/modules/pair/mocks/pair.service.mock.ts index 5ee2562be..75281acbc 100644 --- a/src/modules/pair/mocks/pair.service.mock.ts +++ b/src/modules/pair/mocks/pair.service.mock.ts @@ -7,6 +7,9 @@ export class PairServiceMock { (address) => PairsData(address).liquidityPoolToken.identifier, ); } + async getAllStates(pairAddresses: string[]): Promise { + return pairAddresses.map((address) => PairsData(address).state); + } async getAllFeeStates(pairAddresses: string[]): Promise { return pairAddresses.map((address) => PairsData(address).feeState); } diff --git a/src/modules/pair/pair.module.ts b/src/modules/pair/pair.module.ts index 219aba335..9da8561ce 100644 --- a/src/modules/pair/pair.module.ts +++ b/src/modules/pair/pair.module.ts @@ -21,12 +21,12 @@ import { ComposableTasksModule } from '../composable-tasks/composable.tasks.modu import { RemoteConfigModule } from '../remote-config/remote-config.module'; import { StakingProxyModule } from '../staking-proxy/staking.proxy.module'; import { FarmModuleV2 } from '../farm/v2/farm.v2.module'; -import { PairFilteringService } from './services/pair.filtering.service'; import { StakingModule } from '../staking/staking.module'; import { EnergyModule } from '../energy/energy.module'; import { PairAbiLoader } from './services/pair.abi.loader'; import { PairComputeLoader } from './services/pair.compute.loader'; import { ElasticSearchModule } from 'src/services/elastic-search/elastic.search.module'; +import { PairsMetadataBuilder } from './services/pair.metadata.builder'; @Module({ imports: [ CommonAppModule, @@ -51,20 +51,19 @@ import { ElasticSearchModule } from 'src/services/elastic-search/elastic.search. PairComputeService, PairAbiService, PairTransactionService, - PairFilteringService, PairAbiLoader, PairComputeLoader, PairResolver, - PairFilteringService, PairCompoundedAPRResolver, PairRewardTokensResolver, + PairsMetadataBuilder, ], exports: [ PairService, PairSetterService, PairComputeService, PairAbiService, - PairFilteringService, + PairsMetadataBuilder, ], }) export class PairModule {} diff --git a/src/modules/pair/services/pair.filtering.service.ts b/src/modules/pair/services/pair.filtering.service.ts index 5471f84aa..d53e9eafe 100644 --- a/src/modules/pair/services/pair.filtering.service.ts +++ b/src/modules/pair/services/pair.filtering.service.ts @@ -1,334 +1,256 @@ -import { Injectable } from '@nestjs/common'; -import { PairAbiService } from './pair.abi.service'; -import { PairComputeService } from './pair.compute.service'; -import { PairMetadata } from 'src/modules/router/models/pair.metadata.model'; import { PairFilterArgs, PairsFilter, } from 'src/modules/router/models/filter.args'; import BigNumber from 'bignumber.js'; -import { PairService } from './pair.service'; +import { PairModel } from '../models/pair.model'; -@Injectable() export class PairFilteringService { - constructor( - private readonly pairAbi: PairAbiService, - private readonly pairCompute: PairComputeService, - private readonly pairService: PairService, - ) {} - - async pairsByIssuedLpToken( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.issuedLpToken) { - return pairsMetadata; + static pairsByIssuedLpToken( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (!filters.issuedLpToken) { + return pairs; } - const lpTokensIDs = await this.pairService.getAllLpTokensIds( - pairsMetadata.map((pairMetadata) => pairMetadata.address), - ); + return pairs.filter((pair) => pair.liquidityPoolToken !== undefined); + } - const filteredPairsMetadata = []; - for (let index = 0; index < lpTokensIDs.length; index++) { - if ( - lpTokensIDs[index] === undefined || - lpTokensIDs[index] === 'undefined' || - lpTokensIDs[index] === '' - ) { - continue; - } - filteredPairsMetadata.push(pairsMetadata[index]); + static pairsByAddress( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (!filters.addresses || filters.addresses.length === 0) { + return pairs; } - return filteredPairsMetadata; + return pairs.filter((pair) => filters.addresses.includes(pair.address)); } - async pairsByAddress( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (pairFilter.addresses) { - pairsMetadata = pairsMetadata.filter((pair) => - pairFilter.addresses.includes(pair.address), - ); + static pairsByTokens( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (filters instanceof PairsFilter) { + pairs = PairFilteringService.pairsByWildcardToken(filters, pairs); } - return await Promise.resolve(pairsMetadata); - } - async pairsByTokens( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (pairFilter.firstTokenID) { - if (pairFilter.secondTokenID) { - pairsMetadata = pairsMetadata.filter( + if (filters.firstTokenID) { + if (filters.secondTokenID) { + pairs = pairs.filter( (pair) => - (pairFilter.firstTokenID === pair.firstTokenID && - pairFilter.secondTokenID === pair.secondTokenID) || - (pairFilter.firstTokenID === pair.secondTokenID && - pairFilter.secondTokenID === pair.firstTokenID), + (pair.firstToken.identifier === filters.firstTokenID && + pair.secondToken.identifier === + filters.secondTokenID) || + (pair.firstToken.identifier === filters.secondTokenID && + pair.secondToken.identifier === + filters.firstTokenID), ); } else { - pairsMetadata = pairsMetadata.filter( - (pair) => pairFilter.firstTokenID === pair.firstTokenID, + pairs = pairs.filter( + (pair) => + pair.firstToken.identifier === filters.firstTokenID, ); } - } else if (pairFilter.secondTokenID) { - pairsMetadata = pairsMetadata.filter( - (pair) => pairFilter.secondTokenID === pair.secondTokenID, + } else if (filters.secondTokenID) { + pairs = pairs.filter( + (pair) => pair.secondToken.identifier === filters.secondTokenID, ); } - return await Promise.resolve(pairsMetadata); + + return pairs; } - async pairsByWildcardToken( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { + static pairsByWildcardToken( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { if ( - !pairFilter.searchToken || - pairFilter.searchToken.trim().length < 3 + filters.searchToken === undefined || + filters.searchToken.trim().length < 1 ) { - return pairsMetadata; + return pairs; } - const searchTerm = pairFilter.searchToken.toUpperCase().trim(); - const pairsAddresses = pairsMetadata.map( - (pairMetadata) => pairMetadata.address, - ); - - const pairsFirstToken = await this.pairService.getAllFirstTokens( - pairsAddresses, + const searchTerm = filters.searchToken.toUpperCase().trim(); + + return pairs.filter( + (pair) => + pair.firstToken.name.toUpperCase().includes(searchTerm) || + pair.firstToken.identifier.toUpperCase().includes(searchTerm) || + pair.firstToken.ticker.toUpperCase().includes(searchTerm) || + pair.secondToken.name.toUpperCase().includes(searchTerm) || + pair.secondToken.identifier + .toUpperCase() + .includes(searchTerm) || + pair.secondToken.ticker.toUpperCase().includes(searchTerm), ); - const pairsSecondToken = await this.pairService.getAllSecondTokens( - pairsAddresses, - ); - - const filteredPairs: PairMetadata[] = []; - for (const [index, pair] of pairsMetadata.entries()) { - const firstToken = pairsFirstToken[index]; - const secondToken = pairsSecondToken[index]; - - if ( - firstToken.name.toUpperCase().includes(searchTerm) || - firstToken.identifier.toUpperCase().includes(searchTerm) || - firstToken.ticker.toUpperCase().includes(searchTerm) || - secondToken.name.toUpperCase().includes(searchTerm) || - secondToken.identifier.toUpperCase().includes(searchTerm) || - secondToken.ticker.toUpperCase().includes(searchTerm) - ) { - filteredPairs.push(pair); - } - } - - return filteredPairs; } - async pairsByLpTokenIds( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.lpTokenIds || pairFilter.lpTokenIds.length === 0) { - return pairsMetadata; + static pairsByLpTokenIds( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if ( + filters.lpTokenIds === undefined || + filters.lpTokenIds.length === 0 + ) { + return pairs; } - const lpTokensIDs = await this.pairService.getAllLpTokensIds( - pairsMetadata.map((pairMetadata) => pairMetadata.address), - ); - - return pairsMetadata.filter((_, index) => - pairFilter.lpTokenIds.includes(lpTokensIDs[index]), + return pairs.filter( + (pair) => + pair.liquidityPoolToken !== undefined && + filters.lpTokenIds.includes(pair.liquidityPoolToken.identifier), ); } - async pairsByFarmTokens( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.farmTokens || pairFilter.farmTokens.length === 0) { - return pairsMetadata; + static pairsByFarmTokens( + filters: PairsFilter, + pairs: PairModel[], + farmTokens: string[], + ): PairModel[] { + if ( + filters.farmTokens === undefined || + filters.farmTokens.length === 0 + ) { + return pairs; } - const farmTokens = await Promise.all( - pairsMetadata.map((pairMetadata) => - this.pairCompute.getPairFarmToken(pairMetadata.address), - ), - ); - - return pairsMetadata.filter( + return pairs.filter( (_, index) => farmTokens[index] && - pairFilter.farmTokens.includes(farmTokens[index]), + filters.farmTokens.includes(farmTokens[index]), ); } - async pairsByState( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.state || pairFilter.state.length === 0) { - return pairsMetadata; + static pairsByState( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if ( + filters.state === undefined || + (Array.isArray(filters.state) && filters.state.length === 0) + ) { + return pairs; } - const pairsStates = await this.pairService.getAllStates( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter((_, index) => { - if (!Array.isArray(pairFilter.state)) { - return pairsStates[index] === pairFilter.state; + return pairs.filter((pair) => { + if (!Array.isArray(filters.state)) { + return pair.state === filters.state; } - return pairFilter.state.includes(pairsStates[index]); + return filters.state.includes(pair.state); }); } - async pairsByFeeState( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { + static pairsByFeeState( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { if ( - typeof pairFilter.feeState === 'undefined' || - pairFilter.feeState === null + typeof filters.feeState === 'undefined' || + filters.feeState === null ) { - return pairsMetadata; + return pairs; } - const pairsFeeStates = await this.pairService.getAllFeeStates( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsFeeStates[index] === pairFilter.feeState, - ); + return pairs.filter((pair) => pair.feeState === filters.feeState); } - async pairsByVolume( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minVolume) { - return pairsMetadata; + static pairsByVolume( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (filters.minVolume === undefined) { + return pairs; } - const pairsVolumes = await this.pairCompute.getAllVolumeUSD( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter((_, index) => { - const volume = new BigNumber(pairsVolumes[index]); - return volume.gte(pairFilter.minVolume); + return pairs.filter((pair) => { + return new BigNumber(pair.volumeUSD24h).gte(filters.minVolume); }); } - async pairsByLockedValueUSD( - pairFilter: PairFilterArgs | PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minLockedValueUSD) { - return pairsMetadata; + static pairsByLockedValueUSD( + filters: PairFilterArgs | PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (filters.minLockedValueUSD === undefined) { + return pairs; } - const pairsLiquidityUSD = await this.pairService.getAllLockedValueUSD( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter((_, index) => { - const lockedValueUSD = new BigNumber(pairsLiquidityUSD[index]); - return lockedValueUSD.gte(pairFilter.minLockedValueUSD); + return pairs.filter((pair) => { + return new BigNumber(pair.lockedValueUSD).gte( + filters.minLockedValueUSD, + ); }); } - async pairsByTradesCount( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minTradesCount) { - return pairsMetadata; + static pairsByTradesCount( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (filters.minTradesCount === undefined) { + return pairs; } - const pairsTradesCount = await this.pairService.getAllTradesCount( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsTradesCount[index] >= pairFilter.minTradesCount, + return pairs.filter( + (pair) => pair.tradesCount >= filters.minTradesCount, ); } - async pairsByTradesCount24h( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minTradesCount24h) { - return pairsMetadata; + static pairsByTradesCount24h( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (filters.minTradesCount24h === undefined) { + return pairs; } - const pairsTradesCount24h = await this.pairCompute.getAllTradesCount24h( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => - pairsTradesCount24h[index] >= pairFilter.minTradesCount24h, + return pairs.filter( + (pair) => pair.tradesCount24h >= filters.minTradesCount24h, ); } - async pairsByHasFarms( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { + static pairsByHasFarms( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { if ( - typeof pairFilter.hasFarms === 'undefined' || - pairFilter.hasFarms === null + typeof filters.hasFarms === 'undefined' || + filters.hasFarms === null ) { - return pairsMetadata; + return pairs; } - const pairsHasFarms = await this.pairService.getAllHasFarms( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsHasFarms[index] === pairFilter.hasFarms, - ); + return pairs.filter((pair) => pair.hasFarms === filters.hasFarms); } - async pairsByHasDualFarms( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { + static pairsByHasDualFarms( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { if ( - typeof pairFilter.hasDualFarms === 'undefined' || - pairFilter.hasDualFarms === null + typeof filters.hasDualFarms === 'undefined' || + filters.hasDualFarms === null ) { - return pairsMetadata; + return pairs; } - const pairsHasDualFarms = await this.pairService.getAllHasDualFarms( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsHasDualFarms[index] === pairFilter.hasDualFarms, + return pairs.filter( + (pair) => pair.hasDualFarms === filters.hasDualFarms, ); } - async pairsByDeployedAt( - pairFilter: PairsFilter, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minDeployedAt) { - return pairsMetadata; + static pairsByDeployedAt( + filters: PairsFilter, + pairs: PairModel[], + ): PairModel[] { + if (!filters.minDeployedAt) { + return pairs; } - const pairsDeployedAt = await this.pairService.getAllDeployedAt( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsDeployedAt[index] >= pairFilter.minDeployedAt, - ); + return pairs.filter((pair) => pair.deployedAt >= filters.minDeployedAt); } } diff --git a/src/modules/pair/services/pair.metadata.builder.ts b/src/modules/pair/services/pair.metadata.builder.ts index 026d24e2a..04cc8909b 100644 --- a/src/modules/pair/services/pair.metadata.builder.ts +++ b/src/modules/pair/services/pair.metadata.builder.ts @@ -1,21 +1,21 @@ -import { PairsFilter } from 'src/modules/router/models/filter.args'; +import { + PairFilterArgs, + PairsFilter, +} from 'src/modules/router/models/filter.args'; import { PairMetadata } from 'src/modules/router/models/pair.metadata.model'; +import { PairModel } from '../models/pair.model'; +import { PairService } from './pair.service'; +import { PairComputeService } from './pair.compute.service'; +import { EsdtToken } from 'src/modules/tokens/models/esdtToken.model'; import { PairFilteringService } from './pair.filtering.service'; +import { Injectable } from '@nestjs/common'; +@Injectable() export class PairsMetadataBuilder { - private pairsMetadata: PairMetadata[]; - private filters: PairsFilter; - private filteringService: PairFilteringService; - constructor( - pairsMetadata: PairMetadata[], - filters: PairsFilter, - filteringService: PairFilteringService, - ) { - this.pairsMetadata = pairsMetadata; - this.filters = filters; - this.filteringService = filteringService; - } + private readonly pairService: PairService, + private readonly pairCompute: PairComputeService, + ) {} static get availableFilters() { return Object.getOwnPropertyNames( @@ -27,135 +27,329 @@ export class PairsMetadataBuilder { ); } - async applyAllFilters(): Promise { + async applyAllFilters( + pairsMetadata: PairMetadata[], + filters: PairsFilter | PairFilterArgs, + ): Promise { + let pairs = pairsMetadata.map( + (pairMetadata) => + new PairModel({ + address: pairMetadata.address, + firstToken: new EsdtToken({ + identifier: pairMetadata.firstTokenID, + }), + secondToken: new EsdtToken({ + identifier: pairMetadata.secondTokenID, + }), + }), + ); + for (const filterFunction of PairsMetadataBuilder.availableFilters) { - await this[filterFunction](); + pairs = await this[filterFunction](filters, pairs); } - return this; + return pairs; } - async filterByIssuedLpToken(): Promise { - this.pairsMetadata = await this.filteringService.pairsByIssuedLpToken( - this.filters, - this.pairsMetadata, + async filterByIssuedLpToken( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if (!filters.issuedLpToken) { + return pairs; + } + + const lpTokensIDs = await this.pairService.getAllLpTokensIds( + pairs.map((pair) => pair.address), ); - return this; - } - async filterByAddress(): Promise { - this.pairsMetadata = await this.filteringService.pairsByAddress( - this.filters, - this.pairsMetadata, + pairs.forEach( + (pair, index) => + (pair.liquidityPoolToken = + lpTokensIDs[index] !== undefined + ? new EsdtToken({ + identifier: lpTokensIDs[index], + }) + : undefined), ); - return this; + + return PairFilteringService.pairsByIssuedLpToken(filters, pairs); + } + + async filterByAddress( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + return PairFilteringService.pairsByAddress(filters, pairs); } - async filterByTokens(): Promise { - if (this.filters.searchToken) { - this.pairsMetadata = - await this.filteringService.pairsByWildcardToken( - this.filters, - this.pairsMetadata, - ); + async filterByTokens( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if (filters instanceof PairsFilter) { + if ( + filters.searchToken === undefined || + filters.searchToken.trim().length < 1 + ) { + return pairs; + } + + const pairsFirstToken = await this.pairService.getAllFirstTokens( + pairs.map((pair) => pair.address), + ); + const pairsSecondToken = await this.pairService.getAllSecondTokens( + pairs.map((pair) => pair.address), + ); + + pairs.forEach((pair, index) => { + pair.firstToken = pairsFirstToken[index]; + pair.secondToken = pairsSecondToken[index]; + }); } - this.pairsMetadata = await this.filteringService.pairsByTokens( - this.filters, - this.pairsMetadata, - ); - return this; + return PairFilteringService.pairsByTokens(filters, pairs); } - async filterByLpTokens(): Promise { - this.pairsMetadata = await this.filteringService.pairsByLpTokenIds( - this.filters, - this.pairsMetadata, + async filterByLpTokens( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + filters.lpTokenIds === undefined || + filters.lpTokenIds.length === 0 + ) { + return pairs; + } + + const lpTokensIDs = await this.pairService.getAllLpTokensIds( + pairs.map((pair) => pair.address), ); - return this; - } - async filterByFarmTokens(): Promise { - this.pairsMetadata = await this.filteringService.pairsByFarmTokens( - this.filters, - this.pairsMetadata, + pairs.forEach( + (pair, index) => + (pair.liquidityPoolToken = + lpTokensIDs[index] !== undefined + ? new EsdtToken({ + identifier: lpTokensIDs[index], + }) + : undefined), ); - return this; + + return PairFilteringService.pairsByLpTokenIds(filters, pairs); } - async filterByState(): Promise { - this.pairsMetadata = await this.filteringService.pairsByState( - this.filters, - this.pairsMetadata, + async filterByFarmTokens( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + filters.farmTokens === undefined || + filters.farmTokens.length === 0 + ) { + return pairs; + } + + const farmTokens = await Promise.all( + pairs.map((pairMetadata) => + this.pairCompute.getPairFarmToken(pairMetadata.address), + ), + ); + + return PairFilteringService.pairsByFarmTokens( + filters, + pairs, + farmTokens, ); - return this; } - async filterByFeeState(): Promise { - this.pairsMetadata = await this.filteringService.pairsByFeeState( - this.filters, - this.pairsMetadata, + async filterByState( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + !filters.state || + (Array.isArray(filters.state) && filters.state.length === 0) + ) { + return pairs; + } + + const pairsStates = await this.pairService.getAllStates( + pairs.map((pair) => pair.address), ); - return this; + + pairs.forEach((pair, index) => (pair.state = pairsStates[index])); + + return PairFilteringService.pairsByState(filters, pairs); } - async filterByVolume(): Promise { - this.pairsMetadata = await this.filteringService.pairsByVolume( - this.filters, - this.pairsMetadata, + async filterByFeeState( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + typeof filters.feeState === 'undefined' || + filters.feeState === null + ) { + return pairs; + } + + const pairsFeeStates = await this.pairService.getAllFeeStates( + pairs.map((pair) => pair.address), ); - return this; + + pairs.forEach((pair, index) => (pair.feeState = pairsFeeStates[index])); + + return PairFilteringService.pairsByFeeState(filters, pairs); } - async filterByLockedValueUSD(): Promise { - this.pairsMetadata = await this.filteringService.pairsByLockedValueUSD( - this.filters, - this.pairsMetadata, + async filterByVolume( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if (filters.minVolume === undefined) { + return pairs; + } + + const pairsVolumes = await this.pairCompute.getAllVolumeUSD( + pairs.map((pair) => pair.address), ); - return this; + + pairs.forEach( + (pair, index) => (pair.volumeUSD24h = pairsVolumes[index]), + ); + + return PairFilteringService.pairsByVolume(filters, pairs); } - async filterByTradesCount(): Promise { - this.pairsMetadata = await this.filteringService.pairsByTradesCount( - this.filters, - this.pairsMetadata, + async filterByLockedValueUSD( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if (filters.minLockedValueUSD === undefined) { + return pairs; + } + + const pairsLiquidityUSD = await this.pairService.getAllLockedValueUSD( + pairs.map((pair) => pair.address), ); - return this; + + pairs.forEach( + (pair, index) => (pair.lockedValueUSD = pairsLiquidityUSD[index]), + ); + + return PairFilteringService.pairsByLockedValueUSD(filters, pairs); } - async filterByTradesCount24h(): Promise { - this.pairsMetadata = await this.filteringService.pairsByTradesCount24h( - this.filters, - this.pairsMetadata, + async filterByTradesCount( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + filters.minTradesCount === undefined + ) { + return pairs; + } + + const pairsTradesCount = await this.pairService.getAllTradesCount( + pairs.map((pair) => pair.address), + ); + + pairs.forEach( + (pair, index) => (pair.tradesCount = pairsTradesCount[index]), ); - return this; + + return PairFilteringService.pairsByTradesCount(filters, pairs); } - async filterByHasFarms(): Promise { - this.pairsMetadata = await this.filteringService.pairsByHasFarms( - this.filters, - this.pairsMetadata, + async filterByTradesCount24h( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + filters.minTradesCount24h === undefined + ) { + return pairs; + } + + const pairsTradesCount24h = await this.pairCompute.getAllTradesCount24h( + pairs.map((pair) => pair.address), + ); + + pairs.forEach( + (pair, index) => (pair.tradesCount24h = pairsTradesCount24h[index]), ); - return this; + + return PairFilteringService.pairsByTradesCount24h(filters, pairs); } - async filterByHasDualFarms(): Promise { - this.pairsMetadata = await this.filteringService.pairsByHasDualFarms( - this.filters, - this.pairsMetadata, + async filterByHasFarms( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + typeof filters.hasFarms === 'undefined' || + filters.hasFarms === null + ) { + return pairs; + } + + const pairsHasFarms = await this.pairService.getAllHasFarms( + pairs.map((pair) => pair.address), ); - return this; + + pairs.forEach((pair, index) => (pair.hasFarms = pairsHasFarms[index])); + + return PairFilteringService.pairsByHasFarms(filters, pairs); } - async filterByDeployedAt(): Promise { - this.pairsMetadata = await this.filteringService.pairsByDeployedAt( - this.filters, - this.pairsMetadata, + async filterByHasDualFarms( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + typeof filters.hasDualFarms === 'undefined' || + filters.hasDualFarms === null + ) { + return pairs; + } + + const pairsHasDualFarms = await this.pairService.getAllHasDualFarms( + pairs.map((pair) => pair.address), + ); + + pairs.forEach( + (pair, index) => (pair.hasDualFarms = pairsHasDualFarms[index]), ); - return this; + + return PairFilteringService.pairsByHasDualFarms(filters, pairs); } - async build(): Promise { - return this.pairsMetadata; + async filterByDeployedAt( + filters: PairsFilter | PairFilterArgs, + pairs: PairModel[], + ): Promise { + if ( + filters instanceof PairFilterArgs || + filters.minDeployedAt === undefined + ) { + return pairs; + } + + const pairsDeployedAt = await this.pairService.getAllDeployedAt( + pairs.map((pair) => pair.address), + ); + + pairs.forEach( + (pair, index) => (pair.deployedAt = pairsDeployedAt[index]), + ); + + return PairFilteringService.pairsByDeployedAt(filters, pairs); } } diff --git a/src/modules/position-creator/specs/position.creator.transaction.spec.ts b/src/modules/position-creator/specs/position.creator.transaction.spec.ts index 1d5fa17b7..c19720af6 100644 --- a/src/modules/position-creator/specs/position.creator.transaction.spec.ts +++ b/src/modules/position-creator/specs/position.creator.transaction.spec.ts @@ -36,8 +36,8 @@ import { constantsConfig, gasConfig, scAddress } from 'src/config'; import { StakingAbiService } from 'src/modules/staking/services/staking.abi.service'; import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; import { SwapRouteModel } from 'src/modules/auto-router/models/auto-route.model'; -import { PairFilteringService } from 'src/modules/pair/services/pair.filtering.service'; import { FarmVersion } from 'src/modules/farm/models/farm.model'; +import { PairsMetadataBuilder } from 'src/modules/pair/services/pair.metadata.builder'; describe('PositionCreatorTransaction', () => { let module: TestingModule; @@ -61,7 +61,6 @@ describe('PositionCreatorTransaction', () => { PairService, PairComputeServiceProvider, PairTransactionService, - PairFilteringService, WrapService, WrapAbiServiceProvider, WrapTransactionsService, @@ -83,6 +82,7 @@ describe('PositionCreatorTransaction', () => { ApiConfigService, ContextGetterServiceProvider, MXApiServiceProvider, + PairsMetadataBuilder, ], }).compile(); }); diff --git a/src/modules/router/services/router.service.ts b/src/modules/router/services/router.service.ts index 0ce7ce4b0..64287a308 100644 --- a/src/modules/router/services/router.service.ts +++ b/src/modules/router/services/router.service.ts @@ -9,13 +9,11 @@ import { PairSortingArgs, PairsFilter, } from '../models/filter.args'; -import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; import { RouterAbiService } from './router.abi.service'; import { PairComputeService } from 'src/modules/pair/services/pair.compute.service'; import BigNumber from 'bignumber.js'; import { CollectionType } from 'src/modules/common/collection.type'; import { PairsMetadataBuilder } from 'src/modules/pair/services/pair.metadata.builder'; -import { PairFilteringService } from 'src/modules/pair/services/pair.filtering.service'; import { SortingOrder } from 'src/modules/common/page.data'; import { CacheService } from '@multiversx/sdk-nestjs-cache'; import { PairService } from 'src/modules/pair/services/pair.service'; @@ -23,12 +21,11 @@ import { PairService } from 'src/modules/pair/services/pair.service'; @Injectable() export class RouterService { constructor( - private readonly pairAbi: PairAbiService, private readonly routerAbi: RouterAbiService, private readonly pairCompute: PairComputeService, - private readonly pairFilteringService: PairFilteringService, private readonly cacheService: CacheService, private readonly pairService: PairService, + private readonly pairMetadataBuilder: PairsMetadataBuilder, ) {} async getFactory(): Promise { @@ -48,33 +45,21 @@ export class RouterService { filters: PairsFilter, sorting: PairSortingArgs, ): Promise> { - let pairsMetadata = await this.routerAbi.pairsMetadata(); + const pairsMetadata = await this.routerAbi.pairsMetadata(); - const builder = new PairsMetadataBuilder( + let pairs = await this.pairMetadataBuilder.applyAllFilters( pairsMetadata, filters, - this.pairFilteringService, ); - await builder.applyAllFilters(); - - pairsMetadata = await builder.build(); - if (sorting) { - pairsMetadata = await this.sortPairs( - pairsMetadata, + pairs = await this.sortPairs( + pairs, sorting.sortField, sorting.sortOrder, ); } - const pairs = pairsMetadata.map( - (pairMetadata) => - new PairModel({ - address: pairMetadata.address, - }), - ); - return new CollectionType({ count: pairs.length, items: pairs.slice(offset, offset + limit), @@ -86,182 +71,14 @@ export class RouterService { limit: number, pairFilter: PairFilterArgs, ): Promise { - let pairsMetadata = await this.routerAbi.pairsMetadata(); - if (pairFilter.issuedLpToken) { - pairsMetadata = await this.pairsByIssuedLpToken(pairsMetadata); - } + const pairsMetadata = await this.routerAbi.pairsMetadata(); - pairsMetadata = this.filterPairsByAddress(pairFilter, pairsMetadata); - pairsMetadata = this.filterPairsByTokens(pairFilter, pairsMetadata); - pairsMetadata = await this.filterPairsByState( - pairFilter, + const pairs = await this.pairMetadataBuilder.applyAllFilters( pairsMetadata, - ); - pairsMetadata = await this.filterPairsByFeeState( - pairFilter, - pairsMetadata, - ); - pairsMetadata = await this.filterPairsByVolume( pairFilter, - pairsMetadata, - ); - pairsMetadata = await this.filterPairsByLockedValueUSD( - pairFilter, - pairsMetadata, - ); - - return pairsMetadata - .map( - (pairMetadata) => - new PairModel({ - address: pairMetadata.address, - }), - ) - .slice(offset, offset + limit); - } - - private filterPairsByAddress( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): PairMetadata[] { - if (pairFilter.addresses) { - pairsMetadata = pairsMetadata.filter((pair) => - pairFilter.addresses.includes(pair.address), - ); - } - return pairsMetadata; - } - - private filterPairsByTokens( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): PairMetadata[] { - if (pairFilter.firstTokenID) { - if (pairFilter.secondTokenID) { - pairsMetadata = pairsMetadata.filter( - (pair) => - (pairFilter.firstTokenID === pair.firstTokenID && - pairFilter.secondTokenID === pair.secondTokenID) || - (pairFilter.firstTokenID === pair.secondTokenID && - pairFilter.secondTokenID === pair.firstTokenID), - ); - } else { - pairsMetadata = pairsMetadata.filter( - (pair) => pairFilter.firstTokenID === pair.firstTokenID, - ); - } - } else if (pairFilter.secondTokenID) { - pairsMetadata = pairsMetadata.filter( - (pair) => pairFilter.secondTokenID === pair.secondTokenID, - ); - } - return pairsMetadata; - } - - private async pairsByIssuedLpToken( - pairsMetadata: PairMetadata[], - ): Promise { - return await this.filterPairsByIssuedLpTokenRaw(pairsMetadata); - } - - private async filterPairsByIssuedLpTokenRaw( - pairsMetadata: PairMetadata[], - ): Promise { - const lpTokensIDs = await this.pairService.getAllLpTokensIds( - pairsMetadata.map((pair) => pair.address), ); - const filteredPairsMetadata = []; - for (let index = 0; index < lpTokensIDs.length; index++) { - if ( - lpTokensIDs[index] === undefined || - lpTokensIDs[index] === 'undefined' || - lpTokensIDs[index] === '' - ) { - continue; - } - filteredPairsMetadata.push(pairsMetadata[index]); - } - - return filteredPairsMetadata; - } - - private async filterPairsByState( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.state) { - return pairsMetadata; - } - - const pairsStates = await this.pairService.getAllStates( - pairsMetadata.map((pair) => pair.address), - ); - - const filteredPairsMetadata = []; - for (let index = 0; index < pairsStates.length; index++) { - if (pairsStates[index] === pairFilter.state) { - filteredPairsMetadata.push(pairsMetadata[index]); - } - } - - return filteredPairsMetadata; - } - - private async filterPairsByFeeState( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): Promise { - if ( - typeof pairFilter.feeState === 'undefined' || - pairFilter.feeState === null - ) { - return pairsMetadata; - } - - const pairsFeeStates = await this.pairService.getAllFeeStates( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter( - (_, index) => pairsFeeStates[index] === pairFilter.feeState, - ); - } - - private async filterPairsByVolume( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minVolume) { - return pairsMetadata; - } - - const pairsVolumes = await this.pairCompute.getAllVolumeUSD( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter((_, index) => { - const volume = new BigNumber(pairsVolumes[index]); - return volume.gte(pairFilter.minVolume); - }); - } - - private async filterPairsByLockedValueUSD( - pairFilter: PairFilterArgs, - pairsMetadata: PairMetadata[], - ): Promise { - if (!pairFilter.minLockedValueUSD) { - return pairsMetadata; - } - - const pairsLiquidityUSD = await this.pairService.getAllLockedValueUSD( - pairsMetadata.map((pair) => pair.address), - ); - - return pairsMetadata.filter((_, index) => { - const lockedValueUSD = new BigNumber(pairsLiquidityUSD[index]); - return lockedValueUSD.gte(pairFilter.minLockedValueUSD); - }); + return pairs.slice(offset, offset + limit); } async requireOwner(sender: string) { @@ -270,10 +87,10 @@ export class RouterService { } private async sortPairs( - pairsMetadata: PairMetadata[], + pairsMetadata: PairModel[], sortField: string, sortOrder: string, - ): Promise { + ): Promise { let sortFieldData = []; if (!sortField) { diff --git a/src/modules/router/specs/router.service.spec.ts b/src/modules/router/specs/router.service.spec.ts index a40ac1309..bebb98ffd 100644 --- a/src/modules/router/specs/router.service.spec.ts +++ b/src/modules/router/specs/router.service.spec.ts @@ -11,12 +11,20 @@ import { ApiConfigService } from 'src/helpers/api.config.service'; import winston from 'winston'; import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; import { PairComputeServiceProvider } from 'src/modules/pair/mocks/pair.compute.service.mock'; -import { PairFilteringService } from 'src/modules/pair/services/pair.filtering.service'; import { PairServiceProvider } from 'src/modules/pair/mocks/pair.service.mock'; import { WrapAbiServiceProvider } from 'src/modules/wrapping/mocks/wrap.abi.service.mock'; import { TokenServiceProvider } from 'src/modules/tokens/mocks/token.service.mock'; import { ContextGetterServiceProvider } from 'src/services/context/mocks/context.getter.service.mock'; import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; +import { PairsMetadataBuilder } from 'src/modules/pair/services/pair.metadata.builder'; + +const createPairFilterArgs = (fields: Record): PairFilterArgs => { + const filters = new PairFilterArgs(); + for (const key in fields) { + filters[key] = fields[key]; + } + return filters; +}; describe('RouterService', () => { let module: TestingModule; @@ -36,12 +44,12 @@ describe('RouterService', () => { PairComputeServiceProvider, RouterService, ApiConfigService, - PairFilteringService, PairServiceProvider, WrapAbiServiceProvider, TokenServiceProvider, ContextGetterServiceProvider, MXApiServiceProvider, + PairsMetadataBuilder, ], }).compile(); }); @@ -59,7 +67,9 @@ describe('RouterService', () => { Number.MAX_VALUE, new PairFilterArgs(), ); - expect(allPairs).toEqual([ + expect( + allPairs.map((pair) => new PairModel({ address: pair.address })), + ).toEqual([ new PairModel({ address: Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000012', @@ -106,17 +116,19 @@ describe('RouterService', () => { it('should get filtered pairs', async () => { const service = module.get(RouterService); - const filteredPairs = await service.getAllPairs(0, Number.MAX_VALUE, { - firstTokenID: 'WEGLD-123456', - issuedLpToken: true, - addresses: null, - secondTokenID: null, - state: null, - feeState: null, - minVolume: null, - minLockedValueUSD: null, - }); - expect(filteredPairs).toEqual([ + const filteredPairs = await service.getAllPairs( + 0, + Number.MAX_VALUE, + createPairFilterArgs({ + firstTokenID: 'WEGLD-123456', + issuedLpToken: true, + }), + ); + expect( + filteredPairs.map( + (pair) => new PairModel({ address: pair.address }), + ), + ).toEqual([ new PairModel({ address: Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000012', @@ -143,17 +155,21 @@ describe('RouterService', () => { it('should get pairs filtered by fee state and volume', async () => { const service = module.get(RouterService); - const filteredPairs = await service.getAllPairs(0, Number.MAX_VALUE, { - firstTokenID: 'WEGLD-123456', - issuedLpToken: true, - addresses: null, - secondTokenID: null, - state: null, - feeState: false, - minVolume: 1000, - minLockedValueUSD: null, - }); - expect(filteredPairs).toEqual([ + const filteredPairs = await service.getAllPairs( + 0, + Number.MAX_VALUE, + createPairFilterArgs({ + firstTokenID: 'WEGLD-123456', + issuedLpToken: true, + feeState: false, + minVolume: 1000, + }), + ); + expect( + filteredPairs.map( + (pair) => new PairModel({ address: pair.address }), + ), + ).toEqual([ new PairModel({ address: Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000015', @@ -165,17 +181,19 @@ describe('RouterService', () => { it('should get pairs filtered by minimum locked value USD', async () => { const service = module.get(RouterService); - const filteredPairs = await service.getAllPairs(0, Number.MAX_VALUE, { - firstTokenID: null, - issuedLpToken: true, - addresses: null, - secondTokenID: null, - state: null, - feeState: null, - minVolume: null, - minLockedValueUSD: 300, - }); - expect(filteredPairs).toEqual([ + const filteredPairs = await service.getAllPairs( + 0, + Number.MAX_VALUE, + createPairFilterArgs({ + issuedLpToken: true, + minLockedValueUSD: 300, + }), + ); + expect( + filteredPairs.map( + (pair) => new PairModel({ address: pair.address }), + ), + ).toEqual([ new PairModel({ address: Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000012', diff --git a/src/modules/router/specs/router.transactions.service.spec.ts b/src/modules/router/specs/router.transactions.service.spec.ts index b2cac88d8..166970e06 100644 --- a/src/modules/router/specs/router.transactions.service.spec.ts +++ b/src/modules/router/specs/router.transactions.service.spec.ts @@ -23,7 +23,7 @@ import { ConfigModule } from '@nestjs/config'; import winston from 'winston'; import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; -import { PairFilteringService } from 'src/modules/pair/services/pair.filtering.service'; +import { PairsMetadataBuilder } from 'src/modules/pair/services/pair.metadata.builder'; describe('RouterService', () => { let module: TestingModule; @@ -60,7 +60,7 @@ describe('RouterService', () => { TokenServiceProvider, RouterService, MXApiServiceProvider, - PairFilteringService, + PairsMetadataBuilder, ], }).compile(); });