diff --git a/src/api/graph/graph.controller.ts b/src/api/graph/graph.controller.ts index 4a2fafd..6965a53 100644 --- a/src/api/graph/graph.controller.ts +++ b/src/api/graph/graph.controller.ts @@ -10,6 +10,7 @@ import { ApiQuery, ApiResponse } from '@nestjs/swagger'; import { Graph, GraphType, TimeFrame } from '@/api/graph/graph.model'; import { GraphService } from '@/api/graph/graph.service'; +import { ContractAddress } from '@/clients/sdk-client.model'; @UseInterceptors(CacheInterceptor) @Controller('graph') @@ -27,28 +28,33 @@ export class GraphController { description: 'Desired Time Frame (Default: MAX)', required: false, }) - // @ApiQuery({ - // name: 'tokenAddress', - // type: String, - // description: 'TBD', - // required: false, - // }) - // @ApiQuery({ - // name: 'pairAddress', - // type: String, - // description: 'TBD', - // required: false, - // }) + @ApiQuery({ + name: 'tokenAddress', + type: String, + description: 'Get graph for specific token', + required: false, + }) + @ApiQuery({ + name: 'pairAddress', + type: String, + description: 'Get graph for specific pair', + required: false, + }) @ApiResponse({ status: 200, type: Graph }) async get( @Query('graphType', new ParseEnumPipe(GraphType)) graphType: GraphType, @Query('timeFrame', new ParseEnumPipe(TimeFrame, { optional: true })) timeFrame?: TimeFrame, - // @Query('tokenAddress') tokenAddress?: ContractAddress, - // @Query('pairAddress') pairAddress?: ContractAddress, + @Query('tokenAddress') tokenAddress?: ContractAddress, + @Query('pairAddress') pairAddress?: ContractAddress, ): Promise { - return this.graphService.getGraph(graphType, timeFrame); + return this.graphService.getGraph( + graphType, + timeFrame, + tokenAddress, + pairAddress, + ); } constructor(private readonly graphService: GraphService) {} diff --git a/src/api/graph/graph.model.ts b/src/api/graph/graph.model.ts index 775a326..5deaef7 100644 --- a/src/api/graph/graph.model.ts +++ b/src/api/graph/graph.model.ts @@ -15,6 +15,8 @@ export enum GraphType { TVL = 'TVL', Volume = 'Volume', Price = 'Price', + Price0_1 = 'Price0_1', + Price1_0 = 'Price1_0', Locked = 'Locked', Fees = 'Fees', } diff --git a/src/api/graph/graph.service.ts b/src/api/graph/graph.service.ts index 4e84232..029cefd 100644 --- a/src/api/graph/graph.service.ts +++ b/src/api/graph/graph.service.ts @@ -11,6 +11,7 @@ import { PairLiquidityInfoHistoryWithTokens } from '@/api/pair-liquidity-info-hi import { PairLiquidityInfoHistoryService } from '@/api/pair-liquidity-info-history/pair-liquidity-info-history.service'; import { PairLiquidityInfoHistoryDbService } from '@/database/pair-liquidity-info-history/pair-liquidity-info-history-db.service'; import { OrderQueryEnum } from '@/api/api.model'; +import { ContractAddress } from '@/clients/sdk-client.model'; const TIME_FRAMES = { '1H': 1, @@ -31,20 +32,33 @@ export class GraphService { async getGraph( graphType: GraphType = GraphType.TVL, timeFrame: TimeFrame = TimeFrame.MAX, + tokenAddress?: ContractAddress, + pairAddress?: ContractAddress, ): Promise { const history = await this.pairLiquidityInfoHistoryDb.getAll({ limit: 9999999, offset: 0, order: OrderQueryEnum.asc, + pairAddress, + tokenAddress, }); - const graphData = this.graphData(history); - const filteredData = this.filteredData(graphData, graphType, timeFrame); - const bucketedGraphData = this.bucketedGraphData(filteredData, graphType); - return bucketedGraphData; + const graphData = this.graphData( + history, + pairAddress, + tokenAddress, + graphType, + ); + const filteredData = this.filteredData(graphData, graphType, timeFrame); + return this.bucketedGraphData(filteredData, graphType); } - private graphData(history: PairLiquidityInfoHistoryWithTokens[]) { + private graphData( + history: PairLiquidityInfoHistoryWithTokens[], + pairAddress: ContractAddress | undefined, + tokenAddress: ContractAddress | undefined, + graphType: GraphType, + ) { let tvl = new BigNumber(0); return history.reduce( ( @@ -56,34 +70,104 @@ export class GraphService { ) => { const entry = this.pairLiquidityInfoHistoryService.mapToEntryWithPrice(tx); - // TVL - // deltaUsdValue is already calculated but absolute, so we need to check the deltaReserve to get the sign - const delta0 = new BigNumber(entry.delta0UsdValue || '').times( - Math.sign(Number(entry.deltaReserve0)), - ); - const delta1 = new BigNumber(entry.delta1UsdValue || '').times( - Math.sign(Number(entry.deltaReserve1)), - ); - tvl = tvl - .plus(delta0.isNaN() ? 0 : delta0) - .plus(delta1.isNaN() ? 0 : delta1); - acc.datasets[0].data = [...acc.datasets[0].data, tvl.toString()].map( - (d) => d || '0', - ); - // VOLUME - if (entry.type === 'SwapTokens') { - acc.datasets[1].data = [ - ...acc.datasets[1].data, - new BigNumber(entry.delta0UsdValue || 0) - .plus(entry.delta1UsdValue || 0) - .toString(), - ].map((d) => d || '0'); - } else { - acc.datasets[1].data = [...acc.datasets[1].data, '0'].map( - (d) => d || '0', - ); + if (!pairAddress && !tokenAddress) { + // OVERVIEW + if (graphType === GraphType.TVL) { + // TVL + // deltaUsdValue is already calculated but absolute, so we need to check the deltaReserve to get the sign + const delta0 = new BigNumber(entry.delta0UsdValue || '').times( + Math.sign(Number(entry.deltaReserve0)), + ); + const delta1 = new BigNumber(entry.delta1UsdValue || '').times( + Math.sign(Number(entry.deltaReserve1)), + ); + tvl = tvl + .plus(delta0.isNaN() ? 0 : delta0) + .plus(delta1.isNaN() ? 0 : delta1); + acc.datasets[0].data = [ + ...acc.datasets[0].data, + tvl.toString(), + ].map((d) => d || '0'); + } else if (graphType === GraphType.Volume) { + // VOLUME + if (entry.type === 'SwapTokens') { + acc.datasets[0].data = [ + ...acc.datasets[0].data, + new BigNumber(entry.delta0UsdValue || 0) + .plus(entry.delta1UsdValue || 0) + .toString(), + ].map((d) => d || '0'); + } else { + acc.datasets[0].data = [...acc.datasets[0].data, '0'].map( + (d) => d || '0', + ); + } + } else { + return []; + } + } else if (tokenAddress && !pairAddress) { + // TOKENS + // TODO FILL + } else if (!tokenAddress && pairAddress) { + // POOLS + if (graphType === GraphType.Price0_1) { + // Price 0/1 + acc.datasets[0].data = [ + ...acc.datasets[0].data, + new BigNumber(entry.reserve0) + .div(BigNumber(10).pow(tx.pair.token0.decimals)) + .div( + new BigNumber(entry.reserve1).div( + BigNumber(10).pow(tx.pair.token1.decimals), + ), + ) + .toString(), + ].map((d) => d || '0'); + } else if (graphType === GraphType.Price1_0) { + // Price 1/0 + acc.datasets[0].data = [ + ...acc.datasets[0].data, + new BigNumber(entry.reserve1) + .div(BigNumber(10).pow(tx.pair.token1.decimals)) + .div( + new BigNumber(entry.reserve0).div( + BigNumber(10).pow(tx.pair.token0.decimals), + ), + ) + .toString(), + ].map((d) => d || '0'); + } else if (graphType === GraphType.TVL) { + // TVL + acc.datasets[0].data = [ + ...acc.datasets[0].data, + new BigNumber(entry.reserve0Usd || '') + .plus(entry.reserve1Usd || '') + .toString(), + ].map((d) => d || '0'); + } else if (graphType === GraphType.Fees) { + // Fee + acc.datasets[0].data = [ + ...acc.datasets[0].data, + entry.txUsdFee, + ].map((d) => d || '0'); + } else if (graphType === GraphType.Volume) { + // Volume + if (entry.type === 'SwapTokens') { + acc.datasets[0].data = [ + ...acc.datasets[0].data, + new BigNumber(entry.delta0UsdValue || '') + .plus(entry.delta1UsdValue || '') + .toString(), + ].map((d) => d || '0'); + } else { + acc.datasets[0].data = [...acc.datasets[0].data, '0'].map( + (d) => d || '0', + ); + } + } } + acc.x = [...acc.x, entry.microBlockTime]; return acc; }, @@ -91,11 +175,7 @@ export class GraphService { x: [], datasets: [ { - label: GraphType.TVL, - data: [], - }, - { - label: GraphType.Volume, + label: graphType, data: [], }, ],