Skip to content

Commit

Permalink
Merge pull request #123 from zkLinkProtocol/rseth-history-balance
Browse files Browse the repository at this point in the history
feat: add endpoint to query user rseth balance at a specific timestamp
  • Loading branch information
zkcarter authored Dec 26, 2024
2 parents 331e982 + 1a288db commit a1571ec
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 2 deletions.
41 changes: 39 additions & 2 deletions src/rseth/rseth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Logger, Query } from "@nestjs/common";
import { Controller, Get, Logger, Param, Query } from "@nestjs/common";
import {
ApiBadRequestResponse,
ApiExcludeController,
Expand All @@ -19,7 +19,11 @@ import {
import { PagingOptionsDto } from "src/common/pagingOptionsDto.dto";
import { PagingMetaDto } from "src/common/paging.dto";
import { PaginationUtil } from "src/common/pagination.util";
import { RsethPointItem, RsethReturnDto } from "./rseth.dto";
import {
RsethPointItem,
RsethReturnDto,
UserRsethDateBalanceDto,
} from "./rseth.dto";

@ApiTags("rseth")
@ApiExcludeController(false)
Expand Down Expand Up @@ -235,4 +239,37 @@ export class RsethController {
}
return result as RsethReturnDto;
}

@Get("/rseth/:address/balanceOfTimestamp")
@ApiOkResponse({
description:
"Return users' rseth balance at a specific time. Including the withdrawing and staked balance in dapp.",
type: UserRsethDateBalanceDto,
})
public async queryUserPufferDateBalance(
@Param("address", new ParseAddressPipe()) address: string,
@Query("timestamp") timestamp: number,
) {
try {
const data = await this.rsethService.getBalanceByAddresses(
address,
timestamp,
);
const res = {
errno: 0,
errmsg: "no error",
data: data,
};
return res;
} catch (err) {
this.logger.error(
`get rseth balance at a specific time failed: ${err.stack}`,
);
return {
errno: 1,
errmsg: err.message,
data: null,
};
}
}
}
85 changes: 85 additions & 0 deletions src/rseth/rseth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { PagingMetaDto } from "src/common/paging.dto";

export class RsethTotalPointDto {
Expand Down Expand Up @@ -98,3 +99,87 @@ export class RsethReturnDto {
})
public readonly data?: RsethPointItem[];
}

class LiquidityDetails {
@ApiProperty({
type: String,
description: "dapp name",
example: "Aqua",
})
dappName: string;

@ApiProperty({
type: String,
description: "rseth balance in the dapp",
example: "0.020000",
})
balance: string;
}

export class UserRsEthDateBalanceItem {
@ApiProperty({
type: String,
description: "total rseth balance",
example: "0.020000",
})
totalBalance: string;

@ApiProperty({
type: String,
description: "total withdrawn rseth balance in progress",
example: "0.010000",
})
withdrawingBalance: string;

@ApiProperty({
type: String,
description: "rseth balance of the user account",
example: "0.010000",
})
userBalance: string;

@ApiProperty({
type: String,
description: "total user staked rseth on dapps",
example: "0.000000",
})
liquidityBalance: string;

@ApiProperty({
type: LiquidityDetails,
description: "user staked details on dapps",
example: [
{
dappName: "Aqua",
balance: "0.000023",
},
],
})
@Type(() => LiquidityDetails)
liquidityDetails: LiquidityDetails[];
}

export class UserRsethDateBalanceDto {
@ApiProperty({
type: Number,
description: "error code",
example: 0,
})
public readonly errno: number;
//errmsg
@ApiProperty({
type: String,
description: "error message",
example: "no error",
})
public readonly errmsg: string;

@ApiProperty({
description: "rseth balance data",
nullable: true,
})
public readonly data: {
rsethEthereum: UserRsEthDateBalanceItem;
rsethArbitrum: UserRsEthDateBalanceItem;
};
}
137 changes: 137 additions & 0 deletions src/rseth/rseth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import BigNumber from "bignumber.js";
import waitFor from "src/utils/waitFor";
import { LocalPointsItem } from "../common/service/projectGraph.service";
import { Worker } from "src/common/worker";
import { ethers } from "ethers";
import { WithdrawService } from "src/common/service/withdraw.service";

export interface RsethPointItemWithBalance {
address: string;
Expand Down Expand Up @@ -43,6 +45,13 @@ export interface RsethData {
items: RsethPointItemWithBalance[] | RsethPointItemWithoutBalance[];
}

const RSETH_ETHEREUM = "0x186c0c42C617f1Ce65C4f7DF31842eD7C5fD8260";
const RSETH_ARBITRUM = "0x4A2da287deB06163fB4D77c52901683d69bD06f4";
const AQUA_VAULT =
"0x4AC97E2727B0e92AE32F5796b97b7f98dc47F059".toLocaleLowerCase();
const AQUA_RSETH_LP =
"0xae8AF9bdFE0099f6d0A5234009b78642EfAC1b00".toLocaleLowerCase();

@Injectable()
export class RsethService extends Worker {
private readonly projectName: string = "rseth";
Expand All @@ -64,6 +73,7 @@ export class RsethService extends Worker {
private readonly graphQueryService: GraphQueryService,
private readonly explorerService: ExplorerService,
private readonly configService: ConfigService,
private readonly withdrawService: WithdrawService,
) {
super();
this.logger = new Logger(RsethService.name);
Expand Down Expand Up @@ -293,4 +303,131 @@ export class RsethService extends Worker {
}
return tokensMapBridgeTokens;
}

public async getBalanceByAddresses(address: string, toTimestamp: number) {
const rsethEthereum = await this.getBalanceByAddress(
address,
toTimestamp,
RSETH_ETHEREUM,
);
const rsethArbitrum = await this.getBalanceByAddress(
address,
toTimestamp,
RSETH_ARBITRUM,
);
return {
rsethEthereum,
rsethArbitrum,
};
}

public async getBalanceByAddress(
address: string,
toTimestamp: number,
tokenAddress: string,
) {
const blocks = await this.explorerService.getLastBlocks(toTimestamp);
if (!blocks || blocks.length === 0) {
throw new Error("Failed to get blocks.");
}
const blockNumber = blocks[0].number ?? 0;
if (blockNumber === 0) {
throw new Error("Failed to get block number.");
}
let directBalance = BigInt(0);
let withdrawBalance = BigInt(0);
let aquaBalance = BigInt(0);

const provider = new ethers.JsonRpcProvider("https://rpc.zklink.io");
const block = await provider.getBlock(Number(blockNumber));
const balanceOfMethod = "0x70a08231";
const totalSupplyMethod = "0x18160ddd";
const promiseList = [];

// rsseth balance of address
promiseList.push(
provider.call({
to: tokenAddress,
data: balanceOfMethod + address.replace("0x", "").padStart(64, "0"),
blockTag: Number(blockNumber),
}),
);

// rsseth balance of aqua pairaddress
promiseList.push(
provider.call({
to: tokenAddress,
data: balanceOfMethod + AQUA_VAULT.replace("0x", "").padStart(64, "0"),
blockTag: Number(blockNumber),
}),
);

// aq-lrseth balance of address
promiseList.push(
provider.call({
to: AQUA_RSETH_LP,
data: balanceOfMethod + address.replace("0x", "").padStart(64, "0"),
blockTag: Number(blockNumber),
}),
);

// aq-lrseth total supply
promiseList.push(
provider.call({
to: AQUA_RSETH_LP,
data: totalSupplyMethod,
blockTag: Number(blockNumber),
}),
);

const [
rsethEthAddress,
rsethEthAqua,
aqlrsethAddress,
aqlrsethTotalSupply,
] = await Promise.all(promiseList);

directBalance = BigInt(rsethEthAddress);

const rsethAquaBigInt = BigNumber(rsethEthAqua);
const aqlrsethAddressBigInt = BigNumber(aqlrsethAddress);
const aqlrsethTotalSupplyBigInt = BigNumber(aqlrsethTotalSupply);

// aqua balance
const aquaBalanceBg = aqlrsethAddressBigInt
.multipliedBy(rsethAquaBigInt)
.div(aqlrsethTotalSupplyBigInt);
aquaBalance = BigInt(aquaBalanceBg.toFixed(0));

// withdrawHistory
const withdrawHistory = await this.withdrawService.getWithdrawHistory(
address,
tokenAddress,
block.timestamp,
);
const blockTimestamp = block.timestamp;
for (const item of withdrawHistory) {
const tmpEndTime = this.withdrawService.findWithdrawEndTime(
item.blockTimestamp,
);
// if withdrawTime is in the future, add balance to withdrawBalance
if (tmpEndTime > blockTimestamp) {
withdrawBalance = withdrawBalance + BigInt(item.balance);
}
}

const totalBalance = directBalance + withdrawBalance + aquaBalance;
return {
totalBalance: ethers.formatEther(totalBalance).toString(),
withdrawingBalance: ethers.formatEther(withdrawBalance).toString(),
userBalance: ethers.formatEther(directBalance).toString(),
liquidityBalance: ethers.formatEther(aquaBalance).toString(),
liquidityDetails: [
{
dappName: "aqua",
balance: ethers.formatEther(aquaBalance).toString(),
},
],
};
}
}

0 comments on commit a1571ec

Please sign in to comment.