From 7441aebfd24b7e98015b3bb11da53a7a4a86b968 Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sat, 4 May 2024 19:38:39 +0800 Subject: [PATCH 1/6] fix: support query user balance by time --- src/puffer/points.controller.ts | 64 +++++++++++++++++ src/puffer/points.dto.ts | 60 +++++++++++++++- src/puffer/puffPoints.service.ts | 117 +++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 1 deletion(-) diff --git a/src/puffer/points.controller.ts b/src/puffer/points.controller.ts index 01c1ecd..d700039 100644 --- a/src/puffer/points.controller.ts +++ b/src/puffer/points.controller.ts @@ -35,6 +35,8 @@ import { ElPointsDto, PointsDto, ElPointsDtoItem, + LayerBankPufferPointQueryOptionsDto, + PufferPointUserBalance, } from "./points.dto"; import { TokensDto } from "./tokens.dto"; import { NovaService } from "src/nova/nova.service"; @@ -594,4 +596,66 @@ export class PointsController { return res; } + + @Get("/puffer/:address/balances") + @ApiOkResponse({ + description: + "Return paginated results of all users' puffer points. The rule is to add 30 points per hour.\nTiming starts from the user's first deposit, with each user having an independent timer.", + type: ElPointsDto, + }) + @ApiBadRequestResponse({ + description: '{ "message": "Not Found", "statusCode": 404 }', + }) + public async queryUserPufferHistoricData( + @Param("address", new ParseAddressPipe()) address: string, + @Query() queryOptions: LayerBankPufferPointQueryOptionsDto, + ): Promise { + let res: PufferPointUserBalance; + try { + const [userPosition, pools] = + await this.puffPointsService.getPufferLBPoints( + address, + queryOptions.time, + ); + + console.log(userPosition, pools); + const dappBalance = userPosition.positionHistory.map((item) => { + const pool = pools.find((i) => i.id === item.pool); + return { + dappName: item.poolName, + balance: Number( + ethers.formatEther( + (BigInt(pool.balance) * BigInt(item.supplied)) / + BigInt(pool.totalSupplied), + ), + ).toFixed(6), + }; + }); + res = { + errno: 0, + errmsg: "no error", + data: { + dappBalance: dappBalance, + withdrawingBalance: Number( + ethers.formatEther( + userPosition.withdrawHistory.reduce((prev, cur) => { + return prev + BigInt(cur.balance); + }, BigInt(0)), + ), + ).toFixed(6), + }, + }; + } catch (e) { + res = { + errno: 1, + errmsg: "Not Found", + data: { + dappBalance: [], + withdrawingBalance: "0", + }, + }; + } + + return res; + } } diff --git a/src/puffer/points.dto.ts b/src/puffer/points.dto.ts index 03b6501..9679eff 100644 --- a/src/puffer/points.dto.ts +++ b/src/puffer/points.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; -// import { IsArray, IsNumber, IsString } from "class-validator"; +import { IsDateString } from "class-validator"; export class PointsDto { @ApiProperty({ @@ -220,3 +220,61 @@ export class ElPointsDto { }) public readonly data: ElPointsDtoData; } + +export class LayerBankPufferPointQueryOptionsDto { + @ApiProperty({ + type: Number, + description: "date time to query", + example: "2024-04-28 10:20:22", + }) + @IsDateString() + public readonly time: string; +} + +export class PufferPointUserBalanceData { + @ApiProperty({ + type: String, + description: "withdrawing balance", + example: "0.020000", + }) + public readonly withdrawingBalance: string; + + @ApiProperty({ + type: LiquidityDetails, + description: "user staked details on dapps", + example: [ + { + dappName: "LayerBank", + balance: "0.000023", + }, + { + dappName: "Aqua", + balance: "0.010000", + }, + ], + }) + public readonly dappBalance: LiquidityDetails[]; +} + +export class PufferPointUserBalance { + @ApiProperty({ + type: Number, + description: "error code", + example: 0, + }) + public readonly errno: number; + //err msg + @ApiProperty({ + type: String, + description: "error message", + example: "no error", + }) + public readonly errmsg: string; + + @ApiProperty({ + type: PufferPointUserBalanceData, + description: "puffer points data", + nullable: true, + }) + public readonly data: PufferPointUserBalanceData; +} diff --git a/src/puffer/puffPoints.service.ts b/src/puffer/puffPoints.service.ts index d74494a..d1a342a 100644 --- a/src/puffer/puffPoints.service.ts +++ b/src/puffer/puffPoints.service.ts @@ -65,6 +65,22 @@ interface PufferElPoints { userPositions: EigenlayerPosition[]; } +type PufferUserBalance = [ + { + id: string; + balance: string; + positionHistory: { + id: string; + pool: string; + supplied: string; + token: string; + poolName: string; + }[]; + withdrawHistory: WithdrawnItem[]; + }, + EigenlayerPool[], +]; + const LAYERBANK_LPUFFER = "0xdd6105865380984716C6B2a1591F9643e6ED1C48".toLocaleLowerCase(); @@ -239,6 +255,7 @@ export class PuffPointsService { .toNumber(), })); } + public async getPuffElPointsByAddress( address: string, ): Promise { @@ -292,6 +309,106 @@ export class PuffPointsService { } } + public async getPufferLBPoints( + address: string, + date: string, + ): Promise { + const protocolName = ["LayerBank"]; // "Aqua" to be added + + const specialDateTime = new Date("2015-07-30 00:00:00").getTime(); + const queryDateTime = new Date(date).getTime(); + + const queryUnixTime = + queryDateTime > specialDateTime + ? Math.floor((queryDateTime - 7 * 24 * 60 * 60 * 1000) / 1000) + : Math.floor((queryDateTime - 14 * 24 * 60 * 60 * 1000) / 1000); + + try { + const balanceQueryBody = { + query: `{ + userPosition(id: "${address}") { + id + balance + positionHistory( + where: { + poolName_in: ${JSON.stringify(protocolName)} + blockTimestamp_lte: "${queryUnixTime}" + } + first: 1 + orderBy: blockNumber + orderDirection: desc + ) { + id + pool + supplied + token + poolName + blockNumber + blockTimestamp + } + withdrawHistory(first: 1000, where: {blockTimestamp_gt: "${queryUnixTime}", token: "0x1B49eCf1A8323Db4abf48b2F5EFaA33F7DdAB3FC"}) { + token + id + blockTimestamp + blockNumber + balance + } + } + }`, + }; + + const response = await fetch( + "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(balanceQueryBody), + }, + ); + const { data } = await response.json(); + + const historicData = data.userPosition.positionHistory.map((i) => ({ + poolId: i.pool, + blockNumber: i.blockNumber, + })); + + const genPoolQueryBody = (id: string, blockNumber: number) => ({ + query: `{ + pool(block: {number: ${blockNumber}}, id: "${id}") { + decimals + id + name + symbol + totalSupplied + underlying + balance + } + }`, + }); + + const poolData = await Promise.all( + historicData.map(async (item) => { + const queryString = genPoolQueryBody(item.poolId, item.blockNumber); + const response = await fetch( + "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(queryString), + }, + ); + const { data } = await response.json(); + return data.pool; + }), + ); + + return [data.userPosition, poolData]; + } catch (err) { + this.logger.error("Fetch puffer points by address data fail", err.stack); + return undefined; + } + } + public async getPuffElPoints( pagingOption: PagingOptionsDto, ): Promise { From 36a1958244114fea3ff05f7420c255d5670d60bd Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sat, 4 May 2024 20:19:56 +0800 Subject: [PATCH 2/6] fix: update puffer points subgraph uri --- src/config.ts | 3 +-- src/puffer/points.controller.ts | 3 +-- src/puffer/puffPoints.service.ts | 30 ++++++++++++------------------ 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/config.ts b/src/config.ts index 27fd0ac..ee8b5b3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,7 +32,6 @@ export default async () => { L1_ERC20_BRIDGE_BLAST, NOVA_POINT_REDISTRIBUTE_GRAPH_API, - NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API, } = process.env; return { @@ -77,7 +76,7 @@ export default async () => { novaPointRedistributeGraphApi: NOVA_POINT_REDISTRIBUTE_GRAPH_API || "", novaPointPufferElPointsGraphApi: - NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API || "", + "https://graph.zklink.io/subgraphs/name/puffer-el-points-v2", l1Erc20BridgeEthereum: L1_ERC20_BRIDGE_ETHEREUM || "", l1Erc20BridgeArbitrum: L1_ERC20_BRIDGE_ARBITRUM || "", l1Erc20BridgeLinea: L1_ERC20_BRIDGE_LINEA || "", diff --git a/src/puffer/points.controller.ts b/src/puffer/points.controller.ts index d700039..d741a5a 100644 --- a/src/puffer/points.controller.ts +++ b/src/puffer/points.controller.ts @@ -613,12 +613,11 @@ export class PointsController { let res: PufferPointUserBalance; try { const [userPosition, pools] = - await this.puffPointsService.getPufferLBPoints( + await this.puffPointsService.getPufferUserBalance( address, queryOptions.time, ); - console.log(userPosition, pools); const dappBalance = userPosition.positionHistory.map((item) => { const pool = pools.find((i) => i.id === item.pool); return { diff --git a/src/puffer/puffPoints.service.ts b/src/puffer/puffPoints.service.ts index d1a342a..b4b8d00 100644 --- a/src/puffer/puffPoints.service.ts +++ b/src/puffer/puffPoints.service.ts @@ -309,13 +309,13 @@ export class PuffPointsService { } } - public async getPufferLBPoints( + public async getPufferUserBalance( address: string, date: string, ): Promise { const protocolName = ["LayerBank"]; // "Aqua" to be added - const specialDateTime = new Date("2015-07-30 00:00:00").getTime(); + const specialDateTime = new Date("2024-05-05 00:00:00").getTime(); const queryDateTime = new Date(date).getTime(); const queryUnixTime = @@ -357,14 +357,11 @@ export class PuffPointsService { }`, }; - const response = await fetch( - "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(balanceQueryBody), - }, - ); + const response = await fetch(this.puffElPointsGraphApi, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(balanceQueryBody), + }); const { data } = await response.json(); const historicData = data.userPosition.positionHistory.map((i) => ({ @@ -389,14 +386,11 @@ export class PuffPointsService { const poolData = await Promise.all( historicData.map(async (item) => { const queryString = genPoolQueryBody(item.poolId, item.blockNumber); - const response = await fetch( - "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(queryString), - }, - ); + const response = await fetch(this.puffElPointsGraphApi, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(queryString), + }); const { data } = await response.json(); return data.pool; }), From 9486d48c427425aa3e9ccb79362d9ee724df6103 Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sat, 4 May 2024 20:27:34 +0800 Subject: [PATCH 3/6] docs: update api description --- src/puffer/points.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/puffer/points.controller.ts b/src/puffer/points.controller.ts index d741a5a..40a0f2b 100644 --- a/src/puffer/points.controller.ts +++ b/src/puffer/points.controller.ts @@ -492,7 +492,7 @@ export class PointsController { @Get("/puffer") @ApiOkResponse({ description: - "Return paginated results of all users' Puffer Eigenlayer Points. The rule is to add 30 points per hour.\nTiming starts from the user's first deposit, with each user having an independent timer.", + "Return paginated results of all users' Puffer Points. The rule is to add 30 points per hour.\nTiming starts from the user's first deposit, with each user having an independent timer.", type: ElPointsDto, }) @ApiBadRequestResponse({ @@ -600,7 +600,7 @@ export class PointsController { @Get("/puffer/:address/balances") @ApiOkResponse({ description: - "Return paginated results of all users' puffer points. The rule is to add 30 points per hour.\nTiming starts from the user's first deposit, with each user having an independent timer.", + "Return users' puffer balance. Including the withdrawing and staked balance in dapp.", type: ElPointsDto, }) @ApiBadRequestResponse({ From 389f656102b4de1a7b7152dc69a3881202fab3b6 Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sun, 5 May 2024 14:59:08 +0800 Subject: [PATCH 4/6] fix: withdrawn query time --- .env.example | 3 +-- src/config.ts | 2 +- src/puffer/points.controller.ts | 2 +- src/puffer/puffPoints.service.ts | 31 +++++++++++++++++++++++-------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 82e666a..476a607 100644 --- a/.env.example +++ b/.env.example @@ -33,5 +33,4 @@ L1_ERC20_BRIDGE_ARBITRUM=0xfB0Ad0B3C2605A7CA33d6badd0C685E11b8F5585 L1_ERC20_BRIDGE_LINEA=0x62cE247f34dc316f93D3830e4Bf10959FCe630f8 L1_ERC20_BRIDGE_BLAST=0x8Df0c2bA3916bF4789c50dEc5A79b2fc719F500b -NOVA_POINT_REDISTRIBUTE_GRAPH_API= -NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API= \ No newline at end of file +NOVA_POINT_REDISTRIBUTE_GRAPH_API= \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index ee8b5b3..c6ab768 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,7 @@ export default async () => { novaPointRedistributeGraphApi: NOVA_POINT_REDISTRIBUTE_GRAPH_API || "", novaPointPufferElPointsGraphApi: - "https://graph.zklink.io/subgraphs/name/puffer-el-points-v2", + "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", l1Erc20BridgeEthereum: L1_ERC20_BRIDGE_ETHEREUM || "", l1Erc20BridgeArbitrum: L1_ERC20_BRIDGE_ARBITRUM || "", l1Erc20BridgeLinea: L1_ERC20_BRIDGE_LINEA || "", diff --git a/src/puffer/points.controller.ts b/src/puffer/points.controller.ts index 40a0f2b..8bada78 100644 --- a/src/puffer/points.controller.ts +++ b/src/puffer/points.controller.ts @@ -619,7 +619,7 @@ export class PointsController { ); const dappBalance = userPosition.positionHistory.map((item) => { - const pool = pools.find((i) => i.id === item.pool); + const pool = pools.find((i) => i.pool === item.pool); return { dappName: item.poolName, balance: Number( diff --git a/src/puffer/puffPoints.service.ts b/src/puffer/puffPoints.service.ts index b4b8d00..e31cfcc 100644 --- a/src/puffer/puffPoints.service.ts +++ b/src/puffer/puffPoints.service.ts @@ -78,7 +78,7 @@ type PufferUserBalance = [ }[]; withdrawHistory: WithdrawnItem[]; }, - EigenlayerPool[], + Array, ]; const LAYERBANK_LPUFFER = @@ -318,7 +318,8 @@ export class PuffPointsService { const specialDateTime = new Date("2024-05-05 00:00:00").getTime(); const queryDateTime = new Date(date).getTime(); - const queryUnixTime = + const queryUnixTime = Math.floor(queryDateTime) / 1000; + const queryWithdrawnUnixTime = queryDateTime > specialDateTime ? Math.floor((queryDateTime - 7 * 24 * 60 * 60 * 1000) / 1000) : Math.floor((queryDateTime - 14 * 24 * 60 * 60 * 1000) / 1000); @@ -346,7 +347,11 @@ export class PuffPointsService { blockNumber blockTimestamp } - withdrawHistory(first: 1000, where: {blockTimestamp_gt: "${queryUnixTime}", token: "0x1B49eCf1A8323Db4abf48b2F5EFaA33F7DdAB3FC"}) { + withdrawHistory(first: 1000, where: { + blockTimestamp_gte: "${queryWithdrawnUnixTime}", + blockTimestamp_lte: "${queryUnixTime}", + token: "0x1B49eCf1A8323Db4abf48b2F5EFaA33F7DdAB3FC"} + ) { token id blockTimestamp @@ -366,33 +371,43 @@ export class PuffPointsService { const historicData = data.userPosition.positionHistory.map((i) => ({ poolId: i.pool, - blockNumber: i.blockNumber, })); - const genPoolQueryBody = (id: string, blockNumber: number) => ({ + const genPoolQueryBody = (poolId: string) => ({ query: `{ - pool(block: {number: ${blockNumber}}, id: "${id}") { + poolHistoricItems( + where: { + pool: "${poolId}" + blockTimestamp_lte: "${queryUnixTime}" + } + orderBy: blockTimestamp + orderDirection: desc + first: 1 + ) { decimals id + pool name symbol totalSupplied underlying balance + blockTimestamp + blockNumber } }`, }); const poolData = await Promise.all( historicData.map(async (item) => { - const queryString = genPoolQueryBody(item.poolId, item.blockNumber); + const queryString = genPoolQueryBody(item.poolId); const response = await fetch(this.puffElPointsGraphApi, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(queryString), }); const { data } = await response.json(); - return data.pool; + return data.poolHistoricItems[0]; }), ); From d305107cca85bda382ac8ced09d3906ebb8f2cff Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sun, 5 May 2024 15:27:43 +0800 Subject: [PATCH 5/6] fix: config --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index c6ab768..ee8b5b3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,7 @@ export default async () => { novaPointRedistributeGraphApi: NOVA_POINT_REDISTRIBUTE_GRAPH_API || "", novaPointPufferElPointsGraphApi: - "http://3.114.68.110:8000/subgraphs/name/puffer-el-points-v2", + "https://graph.zklink.io/subgraphs/name/puffer-el-points-v2", l1Erc20BridgeEthereum: L1_ERC20_BRIDGE_ETHEREUM || "", l1Erc20BridgeArbitrum: L1_ERC20_BRIDGE_ARBITRUM || "", l1Erc20BridgeLinea: L1_ERC20_BRIDGE_LINEA || "", From f7f289c203b6a428d184c1debc690e2a78d0b45e Mon Sep 17 00:00:00 2001 From: xsteadybcgo Date: Sun, 5 May 2024 15:53:41 +0800 Subject: [PATCH 6/6] fix: revert env config --- .env.example | 3 ++- src/config.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 476a607..f55b4e3 100644 --- a/.env.example +++ b/.env.example @@ -33,4 +33,5 @@ L1_ERC20_BRIDGE_ARBITRUM=0xfB0Ad0B3C2605A7CA33d6badd0C685E11b8F5585 L1_ERC20_BRIDGE_LINEA=0x62cE247f34dc316f93D3830e4Bf10959FCe630f8 L1_ERC20_BRIDGE_BLAST=0x8Df0c2bA3916bF4789c50dEc5A79b2fc719F500b -NOVA_POINT_REDISTRIBUTE_GRAPH_API= \ No newline at end of file +NOVA_POINT_REDISTRIBUTE_GRAPH_API= +NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API=http://3.114.68.110:8000/subgraphs/name/puffer-eth-points-v2 \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index ee8b5b3..27fd0ac 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,6 +32,7 @@ export default async () => { L1_ERC20_BRIDGE_BLAST, NOVA_POINT_REDISTRIBUTE_GRAPH_API, + NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API, } = process.env; return { @@ -76,7 +77,7 @@ export default async () => { novaPointRedistributeGraphApi: NOVA_POINT_REDISTRIBUTE_GRAPH_API || "", novaPointPufferElPointsGraphApi: - "https://graph.zklink.io/subgraphs/name/puffer-el-points-v2", + NOVA_POINT_PUFFER_EL_POINTS_GRAPH_API || "", l1Erc20BridgeEthereum: L1_ERC20_BRIDGE_ETHEREUM || "", l1Erc20BridgeArbitrum: L1_ERC20_BRIDGE_ARBITRUM || "", l1Erc20BridgeLinea: L1_ERC20_BRIDGE_LINEA || "",