From e75244c4d5b251d6079298680be2ddf50155f964 Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Wed, 23 Oct 2024 14:56:20 +0800 Subject: [PATCH 1/2] feat: support query rsETH --- src/positions/positions.controller.ts | 75 ++++++++++++++++++++++++++- src/positions/positions.dto.ts | 44 ++++++++++++++++ src/positions/positions.service.ts | 74 ++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/src/positions/positions.controller.ts b/src/positions/positions.controller.ts index 17f44a4..6029372 100644 --- a/src/positions/positions.controller.ts +++ b/src/positions/positions.controller.ts @@ -1,22 +1,30 @@ -import { Controller, Get, Param, Query } from "@nestjs/common"; +import { Controller, Get, Logger, Param, Query } from "@nestjs/common"; import { ApiBadRequestResponse, ApiExcludeController, ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, ApiParam, ApiTags, } from "@nestjs/swagger"; import { + BalanceQueryDto, + BalanceReturnDto, GetAGXPositionDto, GetUserPositionsDto, UserPositionsResponseDto, } from "./positions.dto"; import { PositionsService } from "./positions.service"; +import { PaginationUtil } from "src/common/pagination.util"; +import { ParseAddressPipe } from "src/common/pipes/parseAddress.pipe"; +import { SERVICE_EXCEPTION } from "src/puffer/tokenPointsWithoutDecimals.dto"; @ApiTags("positions") @ApiExcludeController(false) @Controller("positions") export class PositionsController { + private readonly logger = new Logger(PositionsController.name); constructor(private positionsService: PositionsService) {} @Get(":projectName") @@ -57,4 +65,69 @@ export class PositionsController { return data; } + + @Get("/balance/:token") + @ApiOperation({ + summary: "Get balance list by block", + }) + @ApiOkResponse({ + description: "Return all users' balance.", + type: BalanceReturnDto, + }) + @ApiBadRequestResponse({ + description: '{ "errno": 1, "errmsg": "Service exception" }', + }) + @ApiNotFoundResponse({ + description: '{ "errno": 1, "errmsg": "not found" }', + }) + public async getRsethBalance( + @Param("token", new ParseAddressPipe()) token: string, + @Query() rsethQueryDto: BalanceQueryDto, + ): Promise> { + const { block, page = 1, limit = 500 } = rsethQueryDto; + try { + // If more tokens need to be supported, please modify the subgraph. + // Currently, only rsETH.eth and rsETH.arb are supported. + const userPositions = await this.positionsService.getUserPositionByToken({ + token, + block, + page, + limit, + }); + const data = userPositions.map((position) => { + const account = position.id; + const liquidityPosition = position.liquidityPositions.reduce( + (acc, cur) => + acc + + (BigInt(cur.supplied) * BigInt(cur.balance)) / + BigInt(cur.totalSupplied), + BigInt(0), + ); + const balancePosition = position.balances[0] + ? BigInt(position.balances[0].balance) + : BigInt(0); + const balance = (balancePosition + liquidityPosition).toString(); + + return { account, balance }; + }); + console.log( + data.reduce((acc, cur) => BigInt(cur.balance) + acc, BigInt(0)), + ); + const paging = PaginationUtil.paginate(data, page, limit); + const list = paging.items; + const meta = paging.meta; + return { + errno: 0, + errmsg: "no error", + data: list, + meta, + }; + } catch (err) { + this.logger.error( + `Get balance ${token} on block ${block} failed`, + err.stack, + ); + return SERVICE_EXCEPTION; + } + } } diff --git a/src/positions/positions.dto.ts b/src/positions/positions.dto.ts index 4603ca8..5dbd9a4 100644 --- a/src/positions/positions.dto.ts +++ b/src/positions/positions.dto.ts @@ -89,3 +89,47 @@ export class UserPositionsResponseDto { }) public readonly data?: UserPositionsDto[]; } + +export class BalanceQueryDto extends PagingOptionsDto { + @ApiProperty({ + required: true, + description: "block number on nova network", + }) + block: number; +} + +export class BalanceReturnDto { + @ApiProperty({ + type: Number, + description: "error code", + example: 0, + }) + public readonly errno: number; + + @ApiProperty({ + type: String, + description: "error message", + example: "no error", + }) + public readonly errmsg: string; + + @ApiProperty({ + type: Array<{ account: string; balance: string }>, + description: "user's balance", + example: [ + { + balance: "10000000000", + account: "0x22723cc5ae5a1b4514ca41f2466e2ade15cf529b", + }, + ], + required: false, + }) + public readonly data?: Array<{ account: string; balance: string }>; + + @ApiProperty({ + type: PagingMetaDto, + description: "page meta", + example: 0, + }) + public readonly meta?: PagingMetaDto; +} diff --git a/src/positions/positions.service.ts b/src/positions/positions.service.ts index 46b7eca..18e3a7f 100644 --- a/src/positions/positions.service.ts +++ b/src/positions/positions.service.ts @@ -3,6 +3,7 @@ import { BalanceOfLpRepository } from "src/repositories/balanceOfLp.repository"; import { GetUserPositionsDto } from "./positions.dto"; import { ethers } from "ethers"; import { PaginationUtil } from "src/common/pagination.util"; +import { fetchGraphQLData } from "src/utils/fetchDataFromGraph"; @Injectable() export class PositionsService { @@ -46,4 +47,77 @@ export class PositionsService { Result: result, }; } + + async getUserPositionByToken(params: { + token: string; + limit: number; + page: number; + block: number; + }) { + const { token, block, limit, page } = params; + const data = await fetchGraphQLData<{ userPositions: any[] }>( + "https://graph.zklink.io/subgraphs/name/rseth-balance", // If more tokens need to be supported, please modify the subgraph + `query MyQuery($token: Bytes = \"${token}\") { + userPositions( + first: ${limit}, + skip: ${(page - 1) * limit}, + block: {number: ${block}} + where: { + and: [ + { + valid: true + }, + { + or: [ + { + balances_: { + tokenAddress: $token, + balance_gt: 0 + } + }, + { + liquidityPositions_: { + token: $token, + supplied_gt: 0 + } + } + ] + } + ] + } + ) { + id + valid + balances( + where: { + tokenAddress: $token + } + ) { + symbol + id + decimals + balance + tokenAddress + } + liquidityPositions( + where: { + token: $token + } + ) { + id + supplied + token + pool { + balance + totalSupplied + symbol + } + } + } + } + `, + ); + + return data.userPositions; + } } From 8ef735f4f37f311945f0d1ed597c877848204d93 Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Wed, 23 Oct 2024 15:12:37 +0800 Subject: [PATCH 2/2] fix: filter out aqua vault address --- src/positions/positions.controller.ts | 4 ++-- src/positions/positions.service.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/positions/positions.controller.ts b/src/positions/positions.controller.ts index 6029372..41d7bad 100644 --- a/src/positions/positions.controller.ts +++ b/src/positions/positions.controller.ts @@ -99,8 +99,8 @@ export class PositionsController { const liquidityPosition = position.liquidityPositions.reduce( (acc, cur) => acc + - (BigInt(cur.supplied) * BigInt(cur.balance)) / - BigInt(cur.totalSupplied), + (BigInt(cur.supplied) * BigInt(cur.pool.balance)) / + BigInt(cur.pool.totalSupplied), BigInt(0), ); const balancePosition = position.balances[0] diff --git a/src/positions/positions.service.ts b/src/positions/positions.service.ts index 18e3a7f..c144dfa 100644 --- a/src/positions/positions.service.ts +++ b/src/positions/positions.service.ts @@ -65,6 +65,7 @@ export class PositionsService { where: { and: [ { + id_not_in: ["0x4ac97e2727b0e92ae32f5796b97b7f98dc47f059"] valid: true }, {