Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kelp: gain : tvl for blancer, pendle, spectra users #294

Merged
merged 29 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fafec39
Update pendle users
batphonghan Aug 22, 2024
b592f8b
get balancer agETH/rsETH user balances
aqua-regia Aug 22, 2024
dfb4693
added pagination to subgraph query
aqua-regia Aug 22, 2024
b699e1f
convert user lp balances to agETH balances
aqua-regia Aug 22, 2024
8f46444
Add balancer user
batphonghan Aug 22, 2024
630ad9b
Update pendle query
batphonghan Aug 22, 2024
317a5cd
Alway query from history 1 hour
batphonghan Aug 23, 2024
93c523e
Update env name
batphonghan Aug 23, 2024
51bd9d7
Merge remote-tracking branch 'origin/main' into add_balancer_pendle
batphonghan Aug 26, 2024
9141d58
Fix compile error
batphonghan Aug 26, 2024
a38c578
Convert share pendle to agETH
batphonghan Aug 29, 2024
f97cb06
Refactor
batphonghan Aug 29, 2024
9d39f21
Update pendle endpoint
batphonghan Sep 5, 2024
25bd868
Update test file
batphonghan Sep 5, 2024
4320481
Update agEth/rsEth rate
batphonghan Sep 5, 2024
4757933
Update pendle hourly snapshot
batphonghan Sep 9, 2024
4f58de1
Merge remote-tracking branch 'origin/main' into add_balancer_pendle
batphonghan Sep 11, 2024
949c95a
Add spectra
batphonghan Sep 11, 2024
38833b6
Update provider
batphonghan Sep 11, 2024
f0e808c
Retry
batphonghan Sep 11, 2024
fe3bb31
Revert "Add spectra"
batphonghan Sep 11, 2024
cf8f102
Revert "Revert "Add spectra""
batphonghan Sep 11, 2024
b1e7ecc
Remove debug
batphonghan Sep 11, 2024
ad32877
rename
batphonghan Sep 11, 2024
97a8e8d
Update spectra query
batphonghan Sep 16, 2024
ee4344d
Merge branch 'add_spectra' into add_balancer_pendle
batphonghan Sep 16, 2024
80551c2
Add start block
batphonghan Sep 16, 2024
589878f
Refactor
batphonghan Sep 16, 2024
a5839b9
Throw error when query for higher block
batphonghan Sep 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions adapters/kelp_gain_linea/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
"bignumber.js": "^9.1.2",
"csv-parser": "^3.0.0",
"decimal.js-light": "^2.5.1",
"ethereum-block-by-date": "^1.4.9",
"ethers": "^5.7.2",
"fast-csv": "^5.0.1",
"graphql": "^16.6.0",
"graphql-request": "^6.1.0",
"jsbi": "^4.3.0",
"tiny-invariant": "^1.3.1",
"toformat": "^2.0.0",
"ethereum-block-by-date": "^1.4.9"
"toformat": "^2.0.0"
},
"devDependencies": {
"@types/ethereum-block-by-date": "^1.4.1",
Expand Down
58 changes: 42 additions & 16 deletions adapters/kelp_gain_linea/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import fs from "fs";
import { write } from "fast-csv";
import csv from "csv-parser";
import {
agConvertToAssets,
agEthToRsEth,
agETHTotalLiquid,
getEtherumBlock,
getRsETHBalance,
getRsETHPrice,
getWRsETHBalance
getWRsETHBalance,
rsETHTotalSupply
} from "./lib/fetcher";
import { rsETH } from "./lib/utils";
import BigNumber from "bignumber.js";
Expand Down Expand Up @@ -40,6 +41,7 @@ const getMultiplierPercent = (tvlInUSD: BigNumber) => {
}
return 156;
};

export const getRsEthTVLInUSD = async (blockNumber: number) => {
const [rsETHBalanceRaw, wrsETHBalanceRaw, rsEthPrice] = await Promise.all([
getRsETHBalance(blockNumber),
Expand All @@ -63,37 +65,40 @@ export const getUserTVLByBlock = async (blocks: BlockData) => {
const { blockNumber, blockTimestamp } = blocks;

const ethBlockNumber = await getEtherumBlock(blockTimestamp);
const [tvl, agRate, agEthTotalSupply, allUser] = await Promise.all([
getRsEthTVLInUSD(blockNumber),
agConvertToAssets(ethBlockNumber),
agETHTotalLiquid(ethBlockNumber),
getAllAgEthHodlers(ethBlockNumber)
]);
const [tvl, agEthPerRsEthRate, agEthTotalSupply, allUser] = await Promise.all(
[
getRsEthTVLInUSD(blockNumber),
agEthToRsEth(ethBlockNumber),
agETHTotalLiquid(ethBlockNumber),
getAllAgEthHodlers(ethBlockNumber, blockTimestamp)
]
);

// Total rsETH deposit to mainnet
const mainnetTVLInRsETH =
BigInt(agEthTotalSupply * agRate) / BigInt(10 ** 18);
BigInt(agEthTotalSupply * agEthPerRsEthRate) / BigInt(10 ** 18);

const lineaToMainnetRatio =
(BigInt(tvl.lineaTVLInRsEth) * BigInt(10 ** 18)) /
BigInt(mainnetTVLInRsETH);

const mulPercent = getMultiplierPercent(tvl.tvlInUSD);
console.log(
`Ratio linea/mainnet ${ethers.utils.formatEther(
lineaToMainnetRatio
)}, lineaTVL: ${ethers.utils.formatEther(
)}, lineaTVL : ${ethers.utils.formatEther(
tvl.lineaTVLInRsEth
)} rsETH, mainnetTVL: ${ethers.utils.formatEther(mainnetTVLInRsETH)} rsETH`
)} rsETH, mainnetTVL: ${ethers.utils.formatEther(
mainnetTVLInRsETH
)} rsETH mulPercent: ${mulPercent}`
);
const csvRows: OutputDataSchemaRow[] = [];
const mulPercent = getMultiplierPercent(tvl.tvlInUSD);

allUser.forEach((item: UserBalanceSubgraphEntry) => {
const userBalanceAgEth = item.balance;
const userBalance = item.balance;
const balanceInRsEthRaw = BigInt(userBalance) * BigInt(agEthPerRsEthRate);
const mainnetUserBalanceRsEth =
(((BigInt(userBalanceAgEth) * BigInt(agRate)) / BigInt(10 ** 18)) *
BigInt(mulPercent)) /
100n;
((balanceInRsEthRaw / BigInt(10 ** 18)) * BigInt(mulPercent)) / 100n;

const lineaUserBalance =
(lineaToMainnetRatio * mainnetUserBalanceRsEth) / BigInt(10 ** 18);
Expand All @@ -109,6 +114,27 @@ export const getUserTVLByBlock = async (blocks: BlockData) => {
});
});

let totalRsEthSaveToCSV = csvRows.reduce(
(acc, s) => acc + s.token_balance,
0n
);

const rsEthTotalSupply = await rsETHTotalSupply(blockNumber);

console.log(
`TOTAL rsEth balance in CSV : ${ethers.utils.formatEther(
totalRsEthSaveToCSV.toString()
)}\nTOTAL rsETH supply linea: ${ethers.utils.formatEther(
rsEthTotalSupply.toString()
)}`
);

if (totalRsEthSaveToCSV > rsEthTotalSupply) {
throw new Error(
`The total balance in CSV ${totalRsEthSaveToCSV} can not more than total supply ${rsEthTotalSupply}`
);
}
Comment on lines +134 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently this is just checking if sum of rseth in csv is more than total supply. but my concern here is that one of the function might return 0 result when it is not suppose to, which we ended up ingesting that.

can we modify this check to make sure those function that is suppose to return data in the specific block is doing so, and throw error if it isnt?


return csvRows;
};

Expand Down
130 changes: 130 additions & 0 deletions adapters/kelp_gain_linea/src/lib/balancer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { gql } from "graphql-request";
import { ethers } from "ethers";
import { subgraphFetchAllById, subgraphFetchOne } from "./query";
import { BALANCER_START_BLOCK } from "./utils";

const BALANCER_V2_ENDPOINT =
"https://api.thegraph.com/subgraphs/id/QmQ5TT2yYBZgoUxsat3bKmNe5Fr9LW9YAtDs8aeuc1BRhj";
const AGETH_POOL_ID =
"0xf1bbc5d95cd5ae25af9916b8a193748572050eb00000000000000000000006bc";
interface GraphQLQuery {
query: string;
collection: string;
}

interface UserAddress {
id: string;
}

interface Share {
id: string;
userAddress: UserAddress;
balance: string;
}

interface Token {
priceRate: string; // or number, depending on the actual data type
weight: string; // or number
balance: string; // or number
symbol: string;
}

interface Pool {
tokens: Token[];
totalShares: string;
}

interface GetPoolDetailsResponse {
pool: Pool;
}

const BALANCER_POOL_SHARES_QUERY: GraphQLQuery = {
query: gql`
query GetPoolShares($poolId: ID!, $block: Int, $lastId: ID!) {
poolShares(
where: {
poolId: $poolId
id_gt: $lastId
balance_gt: "0"
userAddress_not: "0x0000000000000000000000000000000000000000"
}
block: { number: $block }
first: 1000
orderBy: id
orderDirection: asc
) {
id
balance
userAddress {
id
}
}
}
`,
collection: "poolShares"
};

const POOL_DETAILS_QUERY: GraphQLQuery = {
query: gql`
query GetPoolDetails($poolId: ID!, $block: Int) {
pool(id: $poolId, block: { number: $block }) {
tokens {
priceRate
weight
balance
symbol
}
totalShares
}
}
`,
collection: "pool"
};

export async function getPoolDetails(block: number): Promise<Pool> {
return await subgraphFetchOne<Pool>(
BALANCER_V2_ENDPOINT,
POOL_DETAILS_QUERY.query,
POOL_DETAILS_QUERY.collection,
{ poolId: AGETH_POOL_ID, block: block }
);
}

export async function fetchBalancerAgEthPoolShares(
block: number
): Promise<Share[]> {
return await subgraphFetchAllById<Share>(
BALANCER_V2_ENDPOINT,
BALANCER_POOL_SHARES_QUERY.query,
BALANCER_POOL_SHARES_QUERY.collection,
{ poolId: AGETH_POOL_ID, block: block }
);
}

function convertLpToAgETH(balances: Share[], poolDetails: Pool) {
const agETH = poolDetails.tokens.filter(
(token) => token.symbol == "agETH"
)[0];
const totalPoolAgETH = ethers.utils.parseEther(agETH.balance).toBigInt();
const totalLiquidity = ethers.utils
.parseEther(poolDetails.totalShares)
.toBigInt();

for (let i = 0; i < balances.length; i++) {
const userLpBalance = ethers.utils
.parseEther(balances[i].balance)
.toBigInt();
const userAgETH = (userLpBalance * totalPoolAgETH) / totalLiquidity;
balances[i].balance = userAgETH.toString();
}
return balances;
}

export async function fetchAllBalancerShare(blockNumber: number) {
if (blockNumber < BALANCER_START_BLOCK) {
return [];
}
let balances = await fetchBalancerAgEthPoolShares(blockNumber);
const poolDetails = await getPoolDetails(blockNumber);
return convertLpToAgETH(balances, poolDetails);
}
84 changes: 82 additions & 2 deletions adapters/kelp_gain_linea/src/lib/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ import {
agETH
} from "./utils";

export async function getEtherumBlock(blockTimestampSecs: number) {
export async function getEtherumBlock(
blockTimestampSecs: number
): Promise<number> {
return retry({
fn: async () => {
return await _getEtherumBlock(blockTimestampSecs);
},
name: `_getEtherumBlock`
});
}

export async function _getEtherumBlock(blockTimestampSecs: number) {
const blockTimestampInMill = blockTimestampSecs * 1000;
const date = new Date(blockTimestampInMill); //
// External API
Expand All @@ -32,6 +43,14 @@ export async function agETHTotalLiquid(blockNumber: number): Promise<bigint> {
return totalSupply - locked;
}

export async function rsETHTotalSupply(blockNumber: number): Promise<bigint> {
let totalSupply = await rsETHContract.totalSupply({
blockTag: blockNumber
});

return totalSupply;
}

async function agETHTotalSupply(blockNumber: number): Promise<bigint> {
let totalSupply = await agETHContract.totalSupply({
blockTag: blockNumber
Expand All @@ -48,6 +67,17 @@ async function agETHTotalLocked(blockNumber: number): Promise<bigint> {
return lockedAmount;
}

export async function agETHBalancerOf(
blockNumber: number,
address: string
): Promise<bigint> {
let balance = await agETHContract.balanceOf(address, {
blockTag: blockNumber
});

return balance;
}

export async function getRsETHBalance(blockNumber: number): Promise<bigint> {
let rsETHBalance = await rsETHContract.balanceOf(kelpGAINLinea, {
blockTag: blockNumber
Expand Down Expand Up @@ -87,13 +117,23 @@ async function decimals(blockNumber: number): Promise<string> {
return decimals;
}

export async function agConvertToAssets(blockNumber: number): Promise<bigint> {
// Giving rsETH, return agETH
export async function rsEthToAgEth(blockNumber: number): Promise<bigint> {
const rate = await agETHContract.convertToShares(BigInt(10 ** 18), {
blockTag: blockNumber
});

return rate;
}
// Giving agETH, return rsETH
export async function agEthToRsEth(blockNumber: number): Promise<bigint> {
const rate = await agETHContract.convertToAssets(BigInt(10 ** 18), {
blockTag: blockNumber
});

return rate;
}

export async function getRsETHPrice(blockNumber: number): Promise<BigNumber> {
const [rsEthRateRaw, ethPriceRaw, ethPriceDec] = await Promise.all([
rsETHRate(blockNumber),
Expand All @@ -108,3 +148,43 @@ export async function getRsETHPrice(blockNumber: number): Promise<BigNumber> {

return rsEthRate.times(ethPrice);
}

/**
* A wrapper function that retries a function that returns a promise of some resource
* @param fn - The function that returns a promise of some resource
* @param retries - The number of times to retry the function
* @param delayInSecs - The delay between retries in seconds
* @returns - A promise of the resource
*/
export async function retry<T>({
fn,
retries = 10,
delayInSecs = 1000,
name = "Function"
}: SimpleRetry<T>): Promise<T> {
let currentAttempt = 0;
do {
try {
const res = await fn();
return res as T;
} catch (error) {
currentAttempt++;
console.log(
`Error in retry(${name}): Retry count ${currentAttempt}`,
error
);
}
await wait(delayInSecs);
} while (currentAttempt <= retries);

throw new Error(`Error in retry(${name})`);
}

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

type SimpleRetry<T> = {
fn: () => T | Promise<T>;
retries?: number;
delayInSecs?: number;
name?: string;
};
10 changes: 10 additions & 0 deletions adapters/kelp_gain_linea/src/lib/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type Result = {
rows: Row[];
};

export type Row = {
user: string;
share: string;
block_number: string;
day: string;
};
Loading
Loading