diff --git a/package.json b/package.json index 72a622e7..a7dbf7b1 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,12 @@ "test:e2e": "dotenv -e .env.test -- jest --runInBand --config ./test/e2e/jest-e2e.json", "db": "docker compose up", "db:deploy": "docker compose up -d", - "db:remove": "docker compose down ", + "db:remove": "docker compose down -v", "db:redeploy": "npm run db:remove; npm run db:deploy", "db:stop": "docker compose stop ", "db:push-schema": "npx prisma db push", - "postdb:deploy": "sleep 1.5; npm run db:push-schema", + "db:migrate": "npx prisma migrate deploy", + "postdb:deploy": "sleep 1.5; npm run db:migrate", "pretest:e2e": "dotenv -e .env.test npm run db:push-schema", "prisma:test-studio": "dotenv -e .env.test npx prisma studio", "prisma:studio": "npx prisma studio" diff --git a/prisma/migrations/20240619141013_add_total_reserve_function/migration.sql b/prisma/migrations/20240619141013_add_total_reserve_function/migration.sql new file mode 100644 index 00000000..9e18e00d --- /dev/null +++ b/prisma/migrations/20240619141013_add_total_reserve_function/migration.sql @@ -0,0 +1,61 @@ +CREATE +OR REPLACE FUNCTION total_reserve (integer, interval) RETURNS numeric AS 'SELECT SUM(CASE + WHEN t.id = p.t0 THEN + (latest_liquidity_info."reserve0" / POW(10, t.decimals)) + ELSE (latest_liquidity_info."reserve1" / POW(10, t.decimals)) END + ) + FROM "Token" t + LEFT JOIN public."Pair" p on t.id = p.t0 OR t.id = p.t1 + LEFT JOIN LATERAL (SELECT * + FROM "PairLiquidityInfoHistory" + WHERE p.id = "pairId" + AND "microBlockTime" <= extract(epoch from NOW() - $2) * 1000 + + ORDER BY "microBlockTime" DESC, "logIndex" DESC + LIMIT 1) latest_liquidity_info ON TRUE + WHERE $1 = t.id' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE +OR REPLACE FUNCTION volume_usd (integer, interval) RETURNS numeric AS 'SELECT ROUND(SUM(CASE + WHEN t.id = p.t0 THEN + CASE + WHEN liquidity_history."token0AePrice" >= 0 AND + liquidity_history."eventType" = ''SwapTokens'' AND + liquidity_history."microBlockTime" >= + extract(epoch from NOW() - $2) * 1000 + THEN + (ABS(liquidity_history."deltaReserve0") / POW(10, t.decimals)) * + liquidity_history."token0AePrice" * + liquidity_history."aeUsdPrice" END + ELSE CASE + WHEN liquidity_history."token1AePrice" >= 0 AND + liquidity_history."eventType" = ''SwapTokens'' AND + liquidity_history."microBlockTime" >= + extract(epoch from NOW() - $2) * 1000 + THEN + (ABS(liquidity_history."deltaReserve1") / POW(10, t.decimals)) * + liquidity_history."token1AePrice" * + liquidity_history."aeUsdPrice" END END + )::numeric, 4) + FROM "Token" t + LEFT JOIN public."Pair" p on t.id = p.t0 OR t.id = p.t1 + LEFT JOIN "PairLiquidityInfoHistory" liquidity_history ON p.id = liquidity_history."pairId" + + WHERE $1 = t.id' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; + +CREATE +OR REPLACE FUNCTION historic_price (integer, interval) RETURNS numeric AS 'SELECT SUM(CASE + WHEN t.id = p.t0 THEN (latest_liquidity_info."token0AePrice") * + (latest_liquidity_info."reserve0" / POW(10, t.decimals)) + ELSE (latest_liquidity_info."token1AePrice") * + (latest_liquidity_info."reserve1" / POW(10, t.decimals)) END / + total_reserve(t.id, $2)) + FROM "Token" t + LEFT JOIN public."Pair" p on t.id = p.t0 OR t.id = p.t1 + LEFT JOIN LATERAL (SELECT * + FROM "PairLiquidityInfoHistory" + WHERE p.id = "pairId" + AND "microBlockTime" <= extract(epoch from NOW() - $2) * 1000 + ORDER BY "microBlockTime" DESC, "logIndex" DESC + LIMIT 1) latest_liquidity_info ON TRUE + WHERE $1 = t.id' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; diff --git a/src/api/api.model.ts b/src/api/api.model.ts index a58c0483..44f29cf2 100644 --- a/src/api/api.model.ts +++ b/src/api/api.model.ts @@ -7,7 +7,9 @@ export const transactionPattern = export const accountPattern = 'ak_([23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz]){49,50}'; export const bigNumberPattern = '[1-9]+'; -export const usdValuePattern = '[1-9]+(\.[1-9]{0,4})?'; +export const usdValuePattern = '[1-9]+(.[1-9]{0,4})?'; +export const aeValuePattern = '[1-9]+(.[1-9]{0,18})?'; +export const percentPattern = '[1-9]+(.[1-9]+)?'; export const microBlockTimePattern = '[1-9]{13}'; export const microBlockHashPattern = 'mh_([23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz]){49,50}'; @@ -140,41 +142,95 @@ export class TokenPairWithLiquidityInfo { } export class TokenWithUsd extends TokenWithListed { + @ApiProperty({ + description: 'Price of the token in AE', + pattern: aeValuePattern, + }) + priceAe: string; + @ApiProperty({ description: 'Price of the token in USD', - pattern: bigNumberPattern, + pattern: usdValuePattern, }) priceUsd: string; @ApiProperty({ - description: 'Price change in percent', - example: { - day: '0', - week: '0', - }, + description: 'Total locked value in AE', + pattern: aeValuePattern, + }) + tvlAe: string; + + @ApiProperty({ + description: 'Total locked value in USD', + pattern: usdValuePattern, + }) + tvlUsd: string; + + @ApiProperty({ + description: 'Total Reserve', + pattern: usdValuePattern, }) - priceChange: { - day: string; - week: string; - }; + totalReserve: string; @ApiProperty({ - description: 'Fully diluted valuation in USD', + description: 'Number of pairs for this token', pattern: bigNumberPattern, }) - fdvUsd: string; + pairs: number; @ApiProperty({ - description: 'Volume in USD', - example: { - day: '0', - week: '0', - }, - }) - volumeUsd: { - day: string; - week: string; - }; + description: 'Volume for last day in USD', + pattern: usdValuePattern, + }) + volumeUsdDay: string; + + @ApiProperty({ + description: 'Volume for last week in USD', + pattern: usdValuePattern, + }) + volumeUsdWeek: string; + + @ApiProperty({ + description: 'Volume for last month in USD', + pattern: usdValuePattern, + }) + volumeUsdMonth: string; + + @ApiProperty({ + description: 'Volume for last year in USD', + pattern: usdValuePattern, + }) + volumeUsdYear: string; + + @ApiProperty({ + description: 'Volume for all time in USD', + pattern: usdValuePattern, + }) + volumeUsdAll: string; + + @ApiProperty({ + description: 'Price change for last day in percent', + pattern: percentPattern, + }) + priceChangeDay: string; + + @ApiProperty({ + description: 'Price change for last week in percent', + pattern: percentPattern, + }) + priceChangeWeek: string; + + @ApiProperty({ + description: 'Price change for last month in percent', + pattern: percentPattern, + }) + priceChangeMonth: string; + + @ApiProperty({ + description: 'Price change for last year in percent', + pattern: percentPattern, + }) + priceChangeYear: string; } export class PairWithUsd extends PairBase { @@ -200,37 +256,37 @@ export class PairWithUsd extends PairBase { description: 'Total Value Locked in USD', pattern: usdValuePattern, }) - tvlUsd: number; + tvlUsd: string; @ApiProperty({ description: 'Volume for last day in USD', pattern: usdValuePattern, }) - volumeUsdDay: number; + volumeUsdDay: string; @ApiProperty({ description: 'Volume for last week in USD', pattern: usdValuePattern, }) - volumeUsdWeek: number; + volumeUsdWeek: string; @ApiProperty({ description: 'Volume for last month in USD', pattern: usdValuePattern, }) - volumeUsdMonth: number; + volumeUsdMonth: string; @ApiProperty({ description: 'Volume for last year in USD', pattern: usdValuePattern, }) - volumeUsdYear: number; + volumeUsdYear: string; @ApiProperty({ description: 'Volume for all time in USD', pattern: usdValuePattern, }) - volumeUsdAll: number; + volumeUsdAll: string; } class PairWithLiquidity extends PairBase { diff --git a/src/api/tokens/tokens.controller.ts b/src/api/tokens/tokens.controller.ts index 8d0b891b..42cb1459 100644 --- a/src/api/tokens/tokens.controller.ts +++ b/src/api/tokens/tokens.controller.ts @@ -54,20 +54,7 @@ export class TokensController { }) @ApiResponse({ status: 200, type: [dto.TokenWithUsd] }) async getAllTokens(): Promise { - return (await this.tokensService.getAllTokens()).map((token) => ({ - ...removeId(token), - priceUsd: '0', // TODO PIWO: fill me - priceChange: { - // in percent - day: '0', // TODO PIWO: fill me - week: '0', // TODO PIWO: fill me - }, - fdvUsd: '0', // TODO PIWO: fill me - volumeUsd: { - day: '0', // TODO PIWO: fill me - week: '0', // TODO PIWO: fill me - }, - })); + return this.tokensService.getAllTokensWithAggregation(); } @Get('listed') diff --git a/src/api/tokens/tokens.service.ts b/src/api/tokens/tokens.service.ts index e63dc08b..012d20d5 100644 --- a/src/api/tokens/tokens.service.ts +++ b/src/api/tokens/tokens.service.ts @@ -14,6 +14,9 @@ export class TokensService { async getAllTokens(): Promise { return this.tokenDbService.getAll(presentInvalidTokens); } + async getAllTokensWithAggregation() { + return this.tokenDbService.getAllWithAggregation(presentInvalidTokens); + } async getListedTokens(): Promise { return this.tokenDbService.getListed(); } diff --git a/src/database/pair/pair-db.service.ts b/src/database/pair/pair-db.service.ts index 9401781d..73a474aa 100644 --- a/src/database/pair/pair-db.service.ts +++ b/src/database/pair/pair-db.service.ts @@ -33,12 +33,12 @@ export class PairDbService { token1: string; synchronized: boolean; transactions: number; - tvlUsd: number; - volumeUsdDay: number; - volumeUsdWeek: number; - volumeUsdMonth: number; - volumeUsdYear: number; - volumeUsdAll: number; + tvlUsd: string; + volumeUsdDay: string; + volumeUsdWeek: string; + volumeUsdMonth: string; + volumeUsdYear: string; + volumeUsdAll: string; }[] >` SELECT diff --git a/src/database/token/token-db.service.ts b/src/database/token/token-db.service.ts index 9c49a189..327d7f59 100644 --- a/src/database/token/token-db.service.ts +++ b/src/database/token/token-db.service.ts @@ -16,6 +16,149 @@ export class TokenDbService { }); } + getAllWithAggregation(showInvalidTokens: boolean) { + return this.prisma.$queryRaw< + { + address: string; + symbol: string; + name: string; + decimals: number; + malformed: boolean; + noContract: boolean; + listed: boolean; + priceAe: string; + priceUsd: string; + tvlAe: string; + tvlUsd: string; + totalReserve: string; + pairs: number; + volumeUsdDay: string; + volumeUsdWeek: string; + volumeUsdMonth: string; + volumeUsdYear: string; + volumeUsdAll: string; + priceChangeDay: string; + priceChangeWeek: string; + priceChangeMonth: string; + priceChangeYear: string; + }[] + >` + SELECT + t.address, + t.symbol, + t.name, + t.decimals, + t.malformed, + t."noContract", + t.listed, + ROUND( + SUM( + CASE + WHEN t.id = p.t0 THEN (latest_liquidity_info."token0AePrice") * ( + latest_liquidity_info."reserve0" / POW (10, t.decimals) + ) + ELSE (latest_liquidity_info."token1AePrice") * ( + latest_liquidity_info."reserve1" / POW (10, t.decimals) + ) + END / total_reserve (t.id, INTERVAL '0 DAY') + )::numeric, + 18 + ) AS "priceAe", + ROUND( + SUM( + CASE + WHEN t.id = p.t0 THEN (latest_liquidity_info."token0AePrice") * ( + latest_liquidity_info."reserve0" / POW (10, t.decimals) + ) + ELSE (latest_liquidity_info."token1AePrice") * ( + latest_liquidity_info."reserve1" / POW (10, t.decimals) + ) + END * latest_liquidity_info."aeUsdPrice" / total_reserve (t.id, INTERVAL '0 DAY') + )::numeric, + 4 + ) AS "priceUsd", + ROUND( + SUM( + CASE + WHEN t.id = p.t0 THEN (latest_liquidity_info."token0AePrice") * ( + latest_liquidity_info."reserve0" / POW (10, t.decimals) + ) + ELSE (latest_liquidity_info."token1AePrice") * ( + latest_liquidity_info."reserve1" / POW (10, t.decimals) + ) + END + )::numeric, + 18 + ) AS "tvlAe", + ROUND( + SUM( + CASE + WHEN t.id = p.t0 THEN (latest_liquidity_info."token0AePrice") * ( + latest_liquidity_info."reserve0" / POW (10, t.decimals) + ) + ELSE (latest_liquidity_info."token1AePrice") * ( + latest_liquidity_info."reserve1" / POW (10, t.decimals) + ) + END * latest_liquidity_info."aeUsdPrice" + )::numeric, + 4 + ) AS "tvlUsd", + total_reserve (t.id, INTERVAL '0 DAY') AS "totalReserve", + count(p.id)::integer AS "pairs", + volume_usd (t.id, INTERVAL '1 DAY') AS "volumeUsdDay", + volume_usd (t.id, INTERVAL '1 WEEK') AS "volumeUsdWeek", + volume_usd (t.id, INTERVAL '1 YEAR') AS "volumeUsdYear", + volume_usd (t.id, INTERVAL '100 YEAR') AS "volumeUsdAll", + ( + ( + historic_price (t.id, INTERVAL '0 DAY') - historic_price (t.id, INTERVAL '1 DAY') + ) / historic_price (t.id, INTERVAL '1 DAY') + ) * 100 AS "priceChangeDay", + ( + ( + historic_price (t.id, INTERVAL '0 DAY') - historic_price (t.id, INTERVAL '1 WEEK') + ) / historic_price (t.id, INTERVAL '1 WEEK') + ) * 100 AS "priceChangeWeek", + ( + ( + historic_price (t.id, INTERVAL '0 DAY') - historic_price (t.id, INTERVAL '1 MONTH') + ) / historic_price (t.id, INTERVAL '1 MONTH') + ) * 100 AS "priceChangeMonth", + ( + ( + historic_price (t.id, INTERVAL '0 DAY') - historic_price (t.id, INTERVAL '1 YEAR') + ) / historic_price (t.id, INTERVAL '1 YEAR') + ) * 100 AS "priceChangeYear" + FROM + "Token" t + LEFT JOIN public."Pair" p ON t.id = p.t0 + OR t.id = p.t1 + LEFT JOIN LATERAL ( + SELECT + * + FROM + "PairLiquidityInfoHistory" + WHERE + p.id = "pairId" + AND "token0AePrice" >= 0 + AND "token1AePrice" >= 0 + ORDER BY + "microBlockTime" DESC, + "logIndex" DESC + LIMIT + 1 + ) latest_liquidity_info ON TRUE + WHERE + CASE + WHEN ${showInvalidTokens} THEN t.malformed = FALSE + AND t."noContract" = FALSE + ELSE TRUE + END + GROUP BY + t.id + `; + } + getListed(): Promise { //there is no reason to list invalid tokens return this.prisma.token.findMany({ @@ -100,6 +243,7 @@ export class TokenDbService { malformed: false, }); } + upsertMalformedToken(address: string): Promise { return this.commonUpsert(address, { malformed: true, noContract: false }); }