Skip to content

Commit

Permalink
Merge pull request #288 from batphonghan/kelp_gain_linea
Browse files Browse the repository at this point in the history
kelp: gain : tvl by contract
  • Loading branch information
0xroll authored Aug 20, 2024
2 parents fdf3911 + 495e41a commit 15bef9b
Show file tree
Hide file tree
Showing 9 changed files with 1,550 additions and 0 deletions.
2 changes: 2 additions & 0 deletions adapters/kelp_gain_linea/hourly_blocks.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
number,timestamp
8111746 ,1723627927
36 changes: 36 additions & 0 deletions adapters/kelp_gain_linea/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "kelp_gain_linea",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node dist/index.js",
"compile": "tsc",
"watch": "tsc -w",
"clear": "rm -rf dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/big.js": "^6.2.2",
"big.js": "^6.2.1",
"bignumber.js": "^9.1.2",
"csv-parser": "^3.0.0",
"decimal.js-light": "^2.5.1",
"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"
},
"devDependencies": {
"@types/ethereum-block-by-date": "^1.4.1",
"@types/node": "^20.11.17",
"typescript": "^5.3.3"
}
}
171 changes: 171 additions & 0 deletions adapters/kelp_gain_linea/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import fs from "fs";
import { write } from "fast-csv";
import csv from "csv-parser";
import {
agConvertToAssets,
agETHTotalLiquid,
getEtherumBlock,
getRsETHBalance,
getRsETHPrice,
getWRsETHBalance
} from "./lib/fetcher";
import { rsETH } from "./lib/utils";
import BigNumber from "bignumber.js";
import { ethers } from "ethers";
import { getAllAgEthHodlers, UserBalanceSubgraphEntry } from "./lib/query";
interface BlockData {
blockTimestamp: number;
blockNumber: number;
}

type OutputDataSchemaRow = {
block_number: number;
timestamp: number;
user_address: string;
token_address: string;
token_balance: bigint;
token_symbol: string; // token symbol should be empty string if it is not available
usd_price: number; // assign 0 if not available
};

const getMultiplierPercent = (tvlInUSD: BigNumber) => {
if (tvlInUSD.lt(100_000_000)) {
return 138;
} else if (tvlInUSD.lt(300_000_000)) {
return 144;
} else if (tvlInUSD.lt(500_000_000)) {
return 150;
} else if (tvlInUSD.lt(800_000_000)) {
return 156;
}
return 156;
};
export const getRsEthTVLInUSD = async (blockNumber: number) => {
const [rsETHBalanceRaw, wrsETHBalanceRaw, rsEthPrice] = await Promise.all([
getRsETHBalance(blockNumber),
getWRsETHBalance(blockNumber),
getRsETHPrice(blockNumber)
]);

const rsETHBalance = new BigNumber(ethers.utils.formatEther(rsETHBalanceRaw));

const tvlInUSD = rsETHBalance.times(rsEthPrice);
const lineaTVLInRsEth = BigInt(rsETHBalanceRaw) + BigInt(wrsETHBalanceRaw);

return {
tvlInUSD,
lineaTVLInRsEth,
rsEthPrice
};
};

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)
]);

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

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

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

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

const lineaUserBalance =
(lineaToMainnetRatio * mainnetUserBalanceRsEth) / BigInt(10 ** 18);

csvRows.push({
block_number: blockNumber,
timestamp: blockTimestamp,
user_address: item.id.toLowerCase(),
token_address: rsETH.toLowerCase(),
token_balance: lineaUserBalance,
token_symbol: "rsETH",
usd_price: tvl.rsEthPrice.toNumber()
});
});

return csvRows;
};

const readBlocksFromCSV = async (filePath: string): Promise<BlockData[]> => {
const blocks: BlockData[] = [];

await new Promise<void>((resolve, reject) => {
fs.createReadStream(filePath)
.pipe(csv()) // Specify the separator as '\t' for TSV files
.on("data", (row) => {
const blockNumber = parseInt(row.number, 10);
const blockTimestamp = parseInt(row.timestamp, 10);
if (!isNaN(blockNumber) && blockTimestamp) {
blocks.push({ blockNumber: blockNumber, blockTimestamp });
}
})
.on("end", () => {
resolve();
})
.on("error", (err) => {
reject(err);
});
});

return blocks;
};

readBlocksFromCSV("hourly_blocks.csv")
.then(async (blocks: any[]) => {
console.log(blocks);
const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks
const batchSize = 1000; // Size of batch to trigger writing to the file
let i = 0;

for (const block of blocks) {
try {
const result = await getUserTVLByBlock(block);
allCsvRows.push(...result);
} catch (error) {
console.error(`An error occurred for block ${block}:`, error);
}
}
await new Promise((resolve, reject) => {
// const randomTime = Math.random() * 1000;
// setTimeout(resolve, randomTime);
const ws = fs.createWriteStream(`outputData.csv`, { flags: "w" });
write(allCsvRows, { headers: true })
.pipe(ws)
.on("finish", () => {
console.log(`CSV file has been written.`);
resolve;
});
});

// Clear the accumulated CSV rows
// allCsvRows.length = 0;
})
.catch((err) => {
console.error("Error reading CSV file:", err);
});
110 changes: 110 additions & 0 deletions adapters/kelp_gain_linea/src/lib/fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ethers } from "ethers";
import BigNumber from "bignumber.js";
import {
chainlinkOracleContract,
KelpOracleContract as kelpOracleContract,
kelpGAINLinea,
rsETHContract,
dater,
agETHContract,
wrsETHContract,
agETH
} from "./utils";

export async function getEtherumBlock(blockTimestampSecs: number) {
const blockTimestampInMill = blockTimestampSecs * 1000;
const date = new Date(blockTimestampInMill); //
// External API

const res = await dater.getDate(date);
let blockNumber = res.block; // Try to get the exact block number

return blockNumber;
}

// Total supply - total agETH in the contract
export async function agETHTotalLiquid(blockNumber: number): Promise<bigint> {
const [totalSupply, locked] = await Promise.all([
agETHTotalSupply(blockNumber),
agETHTotalLocked(blockNumber)
]);

return totalSupply - locked;
}

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

return totalSupply;
}

async function agETHTotalLocked(blockNumber: number): Promise<bigint> {
let lockedAmount = await agETHContract.balanceOf(agETH, {
blockTag: blockNumber
});

return lockedAmount;
}

export async function getRsETHBalance(blockNumber: number): Promise<bigint> {
let rsETHBalance = await rsETHContract.balanceOf(kelpGAINLinea, {
blockTag: blockNumber
});

return rsETHBalance;
}

export async function getWRsETHBalance(blockNumber: number): Promise<bigint> {
let wrsETHBalance = await wrsETHContract.balanceOf(kelpGAINLinea, {
blockTag: blockNumber
});

return wrsETHBalance;
}

async function getETHPrice(blockNumber: number): Promise<string> {
const latestAnswer = await chainlinkOracleContract.latestAnswer({
blockTag: blockNumber
});

return latestAnswer;
}

async function rsETHRate(blockNumber: number): Promise<string> {
const rsETHRate = kelpOracleContract.rate({
blockTag: blockNumber
});
return rsETHRate;
}

async function decimals(blockNumber: number): Promise<string> {
const decimals = await chainlinkOracleContract.decimals({
blockTag: blockNumber
});

return decimals;
}

export async function agConvertToAssets(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),
getETHPrice(blockNumber),
decimals(blockNumber)
]);

let rsEthRate = new BigNumber(ethers.utils.formatEther(rsEthRateRaw));
let ethPrice = new BigNumber(
ethers.utils.formatUnits(ethPriceRaw, ethPriceDec)
);

return rsEthRate.times(ethPrice);
}
Loading

0 comments on commit 15bef9b

Please sign in to comment.