From c4389776e2b9f8ab3dabef1a5c1d8965860ed01e Mon Sep 17 00:00:00 2001 From: Traian Anghel Date: Thu, 21 Mar 2024 13:04:40 +0200 Subject: [PATCH] Exclude market cap from low liquidity tokens when sorting (#1218) * apply total liquidity field, calculate whether it's low, and use that info in sorting * apply low liquidity criteria also for default sorting * exclude low liquidity tokens from ecosystem market cap * moved attributes to token list entity * added attributes to token entity * provide total volume 24h in the token info --- .../tokens/entities/token.detailed.ts | 5 --- src/endpoints/tokens/entities/token.ts | 17 ++++++++++ src/endpoints/tokens/token.service.ts | 34 +++++++++++++++++-- src/graphql/schema/schema.gql | 16 +++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/endpoints/tokens/entities/token.detailed.ts b/src/endpoints/tokens/entities/token.detailed.ts index ad6e0d3ea..773490a33 100644 --- a/src/endpoints/tokens/entities/token.detailed.ts +++ b/src/endpoints/tokens/entities/token.detailed.ts @@ -3,7 +3,6 @@ import { Field, ObjectType } from "@nestjs/graphql"; import { ApiProperty } from "@nestjs/swagger"; import { Token } from "./token"; import { TokenRoles } from "./token.roles"; -import { MexPairType } from "src/endpoints/mex/entities/mex.pair.type"; @ObjectType("TokenDetailed", { description: "TokenDetailed object type." }) export class TokenDetailed extends Token { @@ -39,8 +38,4 @@ export class TokenDetailed extends Token { @Field(() => Boolean, { description: 'If the given NFT collection can transfer the underlying tokens by default.', nullable: true }) @ApiProperty({ type: Boolean, nullable: true }) canTransfer: boolean | undefined = undefined; - - @Field(() => MexPairType, { description: "Mex pair type details." }) - @ApiProperty({ enum: MexPairType }) - mexPairType: MexPairType = MexPairType.experimental; } diff --git a/src/endpoints/tokens/entities/token.ts b/src/endpoints/tokens/entities/token.ts index f375d953b..1c27ee4c6 100644 --- a/src/endpoints/tokens/entities/token.ts +++ b/src/endpoints/tokens/entities/token.ts @@ -3,6 +3,7 @@ import { Field, Float, ObjectType } from "@nestjs/graphql"; import { ApiProperty } from "@nestjs/swagger"; import { TokenType } from "src/common/indexer/entities"; import { TokenAssets } from "../../../common/assets/entities/token.assets"; +import { MexPairType } from "src/endpoints/mex/entities/mex.pair.type"; @ObjectType("Token", { description: "Token object type." }) export class Token { @@ -129,4 +130,20 @@ export class Token { @Field(() => Number, { description: "Creation timestamp." }) @ApiProperty({ type: Number, description: 'Creation timestamp' }) timestamp: number | undefined = undefined; + + @Field(() => MexPairType, { description: "Mex pair type details." }) + @ApiProperty({ enum: MexPairType }) + mexPairType: MexPairType = MexPairType.experimental; + + @Field(() => Number, { description: "Total value captured in liquidity pools." }) + @ApiProperty({ type: Number, nullable: true }) + totalLiquidity: number | undefined = undefined; + + @Field(() => Number, { description: "Total traded value in the last 24h within the liquidity pools." }) + @ApiProperty({ type: Number, nullable: true }) + totalVolume24h: number | undefined = undefined; + + @Field(() => Boolean, { description: 'If the liquidity to market cap ratio is less than 1%, we consider it as low liquidity.', nullable: true }) + @ApiProperty({ type: Boolean, nullable: true }) + isLowLiquidity: boolean | undefined = undefined; } diff --git a/src/endpoints/tokens/token.service.ts b/src/endpoints/tokens/token.service.ts index 2a4a122c2..a65f6a0f8 100644 --- a/src/endpoints/tokens/token.service.ts +++ b/src/endpoints/tokens/token.service.ts @@ -40,6 +40,7 @@ import { TrieOperationsTimeoutError } from "../esdt/exceptions/trie.operations.t import { TokenSupplyOptions } from "./entities/token.supply.options"; import { TransferService } from "../transfers/transfer.service"; import { MexPairService } from "../mex/mex.pair.service"; +import { MexPairState } from "../mex/entities/mex.pair.state"; @Injectable() export class TokenService { @@ -181,7 +182,7 @@ export class TokenService { criteria = token => token.price ?? 0; break; case TokenSort.marketCap: - criteria = token => token.marketCap ?? 0; + criteria = token => token.isLowLiquidity ? 0 : (token.marketCap ?? 0); break; default: throw new Error(`Unsupported sorting criteria '${sort}'`); @@ -700,7 +701,7 @@ export class TokenService { const tokens = await this.getAllTokens(); for (const token of tokens) { - if (token.price && token.marketCap) { + if (token.price && token.marketCap && !token.isLowLiquidity) { totalMarketCap += token.marketCap; } } @@ -752,6 +753,7 @@ export class TokenService { await this.batchProcessTokens(tokens); + await this.applyMexLiquidity(tokens.filter(x => x.type !== TokenType.MetaESDT)); await this.applyMexPrices(tokens.filter(x => x.type !== TokenType.MetaESDT)); await this.applyMexPairType(tokens.filter(x => x.type !== TokenType.MetaESDT)); @@ -777,7 +779,11 @@ export class TokenService { } } - tokens = tokens.sortedDescending(token => token.assets ? 1 : 0, token => token.marketCap ?? 0, token => token.transactions ?? 0); + tokens = tokens.sortedDescending( + token => token.assets ? 1 : 0, + token => token.isLowLiquidity ? 0 : (token.marketCap ?? 0), + token => token.transactions ?? 0, + ); return tokens; } @@ -868,6 +874,24 @@ export class TokenService { } } + private async applyMexLiquidity(tokens: TokenDetailed[]): Promise { + try { + const pairs = await this.mexPairService.getAllMexPairs(); + const filteredPairs = pairs.filter(x => x.state === MexPairState.active); + + for (const token of tokens) { + const pairs = filteredPairs.filter(x => x.baseId === token.identifier || x.quoteId === token.identifier); + if (pairs.length > 0) { + token.totalLiquidity = pairs.sum(x => x.totalValue / 2); + token.totalVolume24h = pairs.sum(x => x.volume24h ?? 0); + } + } + } catch (error) { + this.logger.error('Could not apply mex liquidity'); + this.logger.error(error); + } + } + private async applyMexPrices(tokens: TokenDetailed[]): Promise { try { const indexedTokens = await this.mexTokenService.getMexPricesRaw(); @@ -883,6 +907,10 @@ export class TokenService { if (price.isToken) { token.price = price.price; token.marketCap = price.price * NumberUtils.denominateString(supply.circulatingSupply, token.decimals); + + if (token.totalLiquidity && token.marketCap && token.totalLiquidity / token.marketCap < 0.01) { + token.isLowLiquidity = true; + } } token.supply = supply.totalSupply; diff --git a/src/graphql/schema/schema.gql b/src/graphql/schema/schema.gql index 9b01ce0d3..b2db8e895 100644 --- a/src/graphql/schema/schema.gql +++ b/src/graphql/schema/schema.gql @@ -3607,6 +3607,11 @@ type TokenDetailed { """Token initial minted amount details.""" initialMinted: String! + """ + If the liquidity to market cap ratio is less than 1%, we consider it as low liquidity. + """ + isLowLiquidity: Boolean + """Token isPause property.""" isPaused: Boolean! @@ -3643,6 +3648,9 @@ type TokenDetailed { """Creation timestamp.""" timestamp: Float! + """Total value captured in liquidity pools.""" + totalLiquidity: Float! + """Tokens transactions.""" transactions: Float @@ -3792,6 +3800,11 @@ type TokenWithBalanceAccountFlat { """Token initial minting details.""" initialMinted: String! + """ + If the liquidity to market cap ratio is less than 1%, we consider it as low liquidity. + """ + isLowLiquidity: Boolean + """Token isPause property.""" isPaused: Boolean! @@ -3822,6 +3835,9 @@ type TokenWithBalanceAccountFlat { """Creation timestamp.""" timestamp: Float! + """Total value captured in liquidity pools.""" + totalLiquidity: Float! + """Tokens transactions.""" transactions: Float