Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SERVICES-2370] Pairs query pagination, sorting and filtering #1347

Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 12 additions & 0 deletions src/modules/pair/mocks/pair.compute.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ export class PairComputeServiceMock implements IPairComputeService {
): Promise<BigNumber> {
return new BigNumber('0.000001');
}

async tradesCount(pairAddress: string): Promise<number> {
return PairsData(pairAddress).tradesCount;
}

async hasFarms(pairAddress: string): Promise<boolean> {
return PairsData(pairAddress).hasFarms;
}

async hasDualFarms(pairAddress: string): Promise<boolean> {
return PairsData(pairAddress).hasDualFarms;
}
}

export const PairComputeServiceProvider = {
Expand Down
24 changes: 24 additions & 0 deletions src/modules/pair/mocks/pair.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: true,
tradesCount: 1000,
hasFarms: false,
hasDualFarms: false,
},
{
address: Address.fromHex(
Expand All @@ -589,6 +592,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: true,
tradesCount: 1010,
hasFarms: true,
hasDualFarms: false,
},
{
address: Address.fromHex(
Expand All @@ -614,6 +620,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: true,
tradesCount: 0,
hasFarms: true,
hasDualFarms: false,
},
{
address: Address.fromHex(
Expand All @@ -639,6 +648,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: false,
tradesCount: 30,
hasFarms: true,
hasDualFarms: true,
},
{
address: Address.fromHex(
Expand All @@ -664,6 +676,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: true,
tradesCount: 0,
hasFarms: false,
hasDualFarms: false,
},
{
address: Address.fromHex(
Expand All @@ -689,6 +704,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: false,
tradesCount: 0,
hasFarms: false,
hasDualFarms: false,
},
{
address: Address.fromHex(
Expand All @@ -714,6 +732,9 @@ export const pairs = [
volumeUSD: '1000',
state: 'Active',
feeState: false,
tradesCount: 0,
hasFarms: false,
hasDualFarms: true,
},
{
address: Address.fromHex(
Expand All @@ -739,6 +760,9 @@ export const pairs = [
volumeUSD: '700',
state: 'Active',
feeState: false,
tradesCount: 0,
hasFarms: false,
hasDualFarms: false,
},
];

Expand Down
36 changes: 35 additions & 1 deletion src/modules/router/models/filter.args.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
import { ArgsType, Field } from '@nestjs/graphql';
import { ArgsType, Field, registerEnumType } from '@nestjs/graphql';
import { PaginationArgs } from 'src/modules/dex.model';

export enum PairSortableFields {
TRADES_COUNT = 'trades_count',
TVL = 'total_value_locked',
VOLUME_24 = 'volume_24h',
FEES_24 = 'fees_24h',
DEPLOYED_AT = 'deployed_at',
}

export enum PairSortOrder {
ASC = 'ascending',
DESC = 'descending',
}

registerEnumType(PairSortableFields, { name: 'PairSortableFields' });
registerEnumType(PairSortOrder, { name: 'PairSortOrder' });

@ArgsType()
export class PairFilterArgs {
Expand All @@ -18,4 +35,21 @@ export class PairFilterArgs {
feeState: boolean;
@Field({ nullable: true })
minLockedValueUSD: number;
@Field({ nullable: true })
minTradesCount: number;
@Field({ nullable: true })
hasFarms: boolean;
@Field({ nullable: true })
hasDualFarms: boolean;
@Field({ nullable: true })
minDeployedAt: number;
}

@ArgsType()
export class PairPaginationArgs extends PaginationArgs {
@Field(() => PairSortableFields, { nullable: true })
sortField?: string;

@Field(() => PairSortOrder, { defaultValue: PairSortOrder.ASC })
sortOrder: string;
}
6 changes: 3 additions & 3 deletions src/modules/router/router.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { UserAuthResult } from '../auth/user.auth.result';
import { RemoteConfigGetterService } from '../remote-config/remote-config.getter.service';
import { InputTokenModel } from 'src/models/inputToken.model';
import { SetLocalRoleOwnerArgs } from './models/router.args';
import { PairFilterArgs } from './models/filter.args';
import { PairFilterArgs, PairPaginationArgs } from './models/filter.args';
import { RouterAbiService } from './services/router.abi.service';
import { RouterComputeService } from './services/router.compute.service';
import { JwtOrNativeAdminGuard } from '../auth/jwt.or.native.admin.guard';
Expand Down Expand Up @@ -148,10 +148,10 @@ export class RouterResolver {

@Query(() => [PairModel])
async pairs(
@Args() page: GetPairsArgs,
@Args() page: PairPaginationArgs,
@Args() filter: PairFilterArgs,
): Promise<PairModel[]> {
return this.routerService.getAllPairs(page.offset, page.limit, filter);
return this.routerService.getAllPairs(page, filter);
}

@UseGuards(JwtOrNativeAuthGuard)
Expand Down
181 changes: 177 additions & 4 deletions src/modules/router/services/router.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Injectable } from '@nestjs/common';
import { scAddress } from '../../../config';
import { PairModel } from '../../pair/models/pair.model';
import { PairMetadata } from '../models/pair.metadata.model';
import { PairFilterArgs } from '../models/filter.args';
import {
PairFilterArgs,
PairPaginationArgs,
PairSortOrder,
PairSortableFields,
} from '../models/filter.args';
import { Constants } from '@multiversx/sdk-nestjs-common';
import { PairAbiService } from 'src/modules/pair/services/pair.abi.service';
import { RouterAbiService } from './router.abi.service';
Expand Down Expand Up @@ -31,8 +36,7 @@ export class RouterService {
}

async getAllPairs(
offset: number,
limit: number,
pagination: PairPaginationArgs,
pairFilter: PairFilterArgs,
): Promise<PairModel[]> {
let pairsMetadata = await this.routerAbi.pairsMetadata();
Expand All @@ -58,6 +62,30 @@ export class RouterService {
pairFilter,
pairsMetadata,
);
pairsMetadata = await this.filterPairsByTradesCount(
pairFilter,
pairsMetadata,
);
pairsMetadata = await this.filterPairsByHasFarms(
pairFilter,
pairsMetadata,
);
pairsMetadata = await this.filterPairsByHasDualFarms(
bogdan-rosianu marked this conversation as resolved.
Show resolved Hide resolved
pairFilter,
pairsMetadata,
);
pairsMetadata = await this.filterPairsByDeployedAt(
pairFilter,
pairsMetadata,
);

if (pagination.sortField) {
pairsMetadata = await this.sortPairs(
pairsMetadata,
pagination.sortField,
pagination.sortOrder,
);
}

return pairsMetadata
.map(
Expand All @@ -66,7 +94,7 @@ export class RouterService {
address: pairMetadata.address,
}),
)
.slice(offset, limit);
.slice(pagination.offset, pagination.offset + pagination.limit);
}

private filterPairsByAddress(
Expand Down Expand Up @@ -226,8 +254,153 @@ export class RouterService {
});
}

private async filterPairsByTradesCount(
pairFilter: PairFilterArgs,
pairsMetadata: PairMetadata[],
): Promise<PairMetadata[]> {
if (!pairFilter.minTradesCount) {
return pairsMetadata;
}

const pairsTradesCount = await Promise.all(
pairsMetadata.map((pairMetadata) =>
this.pairCompute.tradesCount(pairMetadata.address),
),
);

return pairsMetadata.filter(
(_, index) => pairsTradesCount[index] >= pairFilter.minTradesCount,
);
}

private async filterPairsByHasFarms(
pairFilter: PairFilterArgs,
pairsMetadata: PairMetadata[],
): Promise<PairMetadata[]> {
if (
typeof pairFilter.hasFarms === 'undefined' ||
pairFilter.hasFarms === null
) {
return pairsMetadata;
}

const pairsHasFarms = await Promise.all(
pairsMetadata.map((pairMetadata) =>
this.pairCompute.hasFarms(pairMetadata.address),
),
);

return pairsMetadata.filter(
(_, index) => pairsHasFarms[index] === pairFilter.hasFarms,
);
}

private async filterPairsByHasDualFarms(
pairFilter: PairFilterArgs,
pairsMetadata: PairMetadata[],
): Promise<PairMetadata[]> {
if (
typeof pairFilter.hasDualFarms === 'undefined' ||
pairFilter.hasDualFarms === null
) {
return pairsMetadata;
}

const pairsHasDualFarms = await Promise.all(
pairsMetadata.map((pairMetadata) =>
this.pairCompute.hasDualFarms(pairMetadata.address),
),
);

return pairsMetadata.filter(
(_, index) => pairsHasDualFarms[index] === pairFilter.hasDualFarms,
);
}

private async filterPairsByDeployedAt(
pairFilter: PairFilterArgs,
pairsMetadata: PairMetadata[],
): Promise<PairMetadata[]> {
if (!pairFilter.minDeployedAt) {
return pairsMetadata;
}

const pairsDeployedAt = await Promise.all(
pairsMetadata.map((pairMetadata) =>
this.pairCompute.deployedAt(pairMetadata.address),
),
);

return pairsMetadata.filter(
(_, index) => pairsDeployedAt[index] >= pairFilter.minDeployedAt,
);
}

async requireOwner(sender: string) {
if ((await this.routerAbi.owner()) !== sender)
throw new Error('You are not the owner.');
}

private async sortPairs(
pairsMetadata: PairMetadata[],
sortField: string,
sortOrder: string,
): Promise<PairMetadata[]> {
let sortFieldData = [];

switch (sortField) {
case PairSortableFields.DEPLOYED_AT:
sortFieldData = await Promise.all(
pairsMetadata.map((pair) =>
this.pairCompute.deployedAt(pair.address),
),
);
break;
case PairSortableFields.FEES_24:
sortFieldData = await Promise.all(
pairsMetadata.map((pair) =>
this.pairCompute.feesUSD(pair.address, '24h'),
),
);
break;
case PairSortableFields.TRADES_COUNT:
sortFieldData = await Promise.all(
pairsMetadata.map((pair) =>
this.pairCompute.tradesCount(pair.address),
),
);
break;
case PairSortableFields.TVL:
sortFieldData = await Promise.all(
pairsMetadata.map((pair) =>
this.pairCompute.lockedValueUSD(pair.address),
),
);
break;
case PairSortableFields.VOLUME_24:
sortFieldData = await Promise.all(
pairsMetadata.map((pair) =>
this.pairCompute.volumeUSD(pair.address, '24h'),
),
);
break;
default:
break;
}

const combined = pairsMetadata.map((pair, index) => ({
pair,
sortValue: new BigNumber(sortFieldData[index]),
}));

combined.sort((a, b) => {
if (sortOrder === PairSortOrder.ASC) {
return a.sortValue.comparedTo(b.sortValue);
}

return b.sortValue.comparedTo(a.sortValue);
});

return combined.map((item) => item.pair);
}
}
Loading