Skip to content

Commit

Permalink
Merge pull request #42 from DegenYM/TeahouseFinance
Browse files Browse the repository at this point in the history
add Teahouse V3pair adapter, calculate user TVL in tokens and exclude the pairs out of range.
  • Loading branch information
nitish-91 authored Apr 23, 2024
2 parents d3ca8af + a2e8cc8 commit c44e89c
Show file tree
Hide file tree
Showing 8 changed files with 563 additions and 0 deletions.
29 changes: 29 additions & 0 deletions adapters/teahouse/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "teahouse",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "commonjs",
"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",
"devDependencies": {
"@types/node": "^20.11.17",
"typescript": "^5.4.5"
},
"dependencies": {
"ethers": "^6.11.1",
"fs": "^0.0.1-security",
"viem": "^2.9.20",
"csv-parser": "^3.0.0",
"fast-csv": "^5.0.1"
}
}
181 changes: 181 additions & 0 deletions adapters/teahouse/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { promisify } from 'util';
import stream from 'stream';
import csv from 'csv-parser';
import * as fs from "fs";
import { write } from "fast-csv";
import { ethers } from 'ethers';
import ABI_JSON from "./sdk/v3pair_abi.json";
import { client } from "./sdk/config";
import { VAULT_ADDRESS } from "./sdk/vaults";
import { BlockData, OutputDataSchemaRow, UserTokenAmounts, TokenSymbol } from './sdk/types';
import {
getTimestampAtBlock,
getPoolInfoByBlock,
getVaultsAllPositionsByBlock,
getAmountsForLiquidityByBlock,
getUsersShareTokenBalancesByBlock
} from "./sdk/lib";

const ERC20abi = ["function symbol() view returns (string)"];
const provider = new ethers.JsonRpcProvider(client.transport.url);
const vault_ABI = ABI_JSON;
const pipeline = promisify(stream.pipeline);

const readBlocksFromCSV = async (filePath: string): Promise<number[]> => {
const blocks: number[] = [];
await pipeline(
fs.createReadStream(filePath),
csv(),
async function* (source) {
for await (const chunk of source) {
// Assuming each row in the CSV has a column 'block' with the block number
if (chunk.block) blocks.push(parseInt(chunk.block, 10));
}
}
);
return blocks;
};

const getData = async () => {
const blocks = [
3851417
]; //await readBlocksFromCSV('src/sdk/mode_chain_daily_blocks.csv');

const csvRows: OutputDataSchemaRow[] = [];

for (const block of blocks) {
const timestamp = await getTimestampAtBlock(block)

csvRows.push(...await getUserTVLByBlock({ blockNumber: block, blockTimestamp: timestamp }))
}

// Write the CSV output to a file
const ws = fs.createWriteStream('outputData.csv');
write(csvRows, { headers: true }).pipe(ws).on('finish', () => {
console.log("CSV file has been written.");
});
};

export const getUserTVLByBlock = async ({ blockNumber, blockTimestamp }: BlockData): Promise<OutputDataSchemaRow[]> => {
const result: OutputDataSchemaRow[] = []
const amounts: UserTokenAmounts = {};
const symbols: TokenSymbol = {};

const usersShareTokenBalances = await getUsersShareTokenBalancesByBlock(blockNumber);
// console.log('Users share token balances:', usersShareTokenBalances);

for (const vaultAddress of VAULT_ADDRESS) {
try {
const contract = new ethers.Contract(vaultAddress, vault_ABI, provider);
console.log("Processing vault:", vaultAddress);

// Step 1: Get vault in-range positions by block
const positionsByBlock = await getVaultsAllPositionsByBlock(contract, blockNumber);
// Step 2: Get pool info by block
const poolInfo = await getPoolInfoByBlock(contract, blockNumber);
// Step 3: Filter positions within the pool tick range
const inRangePositions = positionsByBlock.filter(
position => position.tickLower <= poolInfo.tick && position.tickUpper >= poolInfo.tick
);

if (inRangePositions.length === 0) {
console.log("No in-range positions found for this vault:", vaultAddress);
continue;
}
// Step 4: Get vault token amounts for the in-range liquidity
let totalAmount0 = 0n;
let totalAmount1 = 0n;

for (const position of inRangePositions) {
const { amount0, amount1 } = await getAmountsForLiquidityByBlock(
contract,
position.tickLower,
position.tickUpper,
position.liquidity,
blockNumber
);

totalAmount0 += BigInt(amount0.toString());
totalAmount1 += BigInt(amount1.toString());
}
// console.log('Total amount 0:', totalAmount0.toString());
// console.log('Total amount 1:', totalAmount1.toString());

// Step 5: Get token symbols for token0 and token1
if (!symbols[poolInfo.token0]) {
const token0 = new ethers.Contract(poolInfo.token0, ERC20abi, provider);
const token0Symbol = await token0.symbol();
symbols[poolInfo.token0] = token0Symbol;
}
if (!symbols[poolInfo.token1]) {
const token1 = new ethers.Contract(poolInfo.token1, ERC20abi, provider);
const token1Symbol = await token1.symbol();
symbols[poolInfo.token1] = token1Symbol;
}

// Step 6: Get total supply of share token by block
const totalSupplyByBlock = await contract.totalSupply({ blockTag: blockNumber });
// console.log('Total supply by block:', totalSupplyByBlock);

// Step 6: Iterate over user share token balances and calculate token amounts
if (usersShareTokenBalances) {
for (const userBalance of usersShareTokenBalances) {
if (userBalance.contractId.toLowerCase() === vaultAddress.toLowerCase() && userBalance.balance > 0n) {
// Calculate token0 and token1 amounts based on the share ratio
const token0Amount: bigint = userBalance.balance === 0n || totalSupplyByBlock === 0n
? 0n // Handle division by zero or zero balance
: (userBalance.balance * totalAmount0) / totalSupplyByBlock;

const token1Amount: bigint = userBalance.balance === 0n || totalSupplyByBlock === 0n
? 0n // Handle division by zero or zero balance
: (userBalance.balance * totalAmount1) / totalSupplyByBlock;
// console.log('User Token 0 amount:', token0Amount.toString());
// console.log('User Token 1 amount:', token1Amount.toString());

// Add token amounts to the user
if (!amounts[userBalance.user]) {
amounts[userBalance.user] = {};
}
if (!amounts[userBalance.user][poolInfo.token0]) {
amounts[userBalance.user][poolInfo.token0] = 0n;
}
if (!amounts[userBalance.user][poolInfo.token1]) {
amounts[userBalance.user][poolInfo.token1] = 0n;
}
amounts[userBalance.user][poolInfo.token0] += token0Amount;
amounts[userBalance.user][poolInfo.token1] += token1Amount;
}
}
} else {
console.error("usersShareTokenBalances is null.");
}
} catch (error) {
console.error("Error processing vault:", vaultAddress, error);
}
}
// console.log('Amounts:', amounts);
for (const user in amounts) {
// Get the token amounts for the current user
const userTokenAmounts = amounts[user];
// Add token amounts to the rowData
for (const token in userTokenAmounts) {
const amount = userTokenAmounts[token];
// Create an OutputDataSchemaRow for the current user
const rowData: OutputDataSchemaRow = {
block_number: blockNumber,
timestamp: blockTimestamp,
user_address: user,
token_address: token, // Placeholder for token address
token_balance: amount, // Placeholder for token balance
token_symbol: symbols[token], // Placeholder for token symbol
usd_price: 0, // Assign 0 for usd_price
};
result.push(rowData);
}
}
return result;
};

getData().then(() => {
console.log("Done");
});
10 changes: 10 additions & 0 deletions adapters/teahouse/src/sdk/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// config.ts
import { createPublicClient, http } from "viem";
import { linea } from "viem/chains"

export const V3_SUBGRAPH_URL = "https://api.goldsky.com/api/public/project_clu5ow773st3501un98cv0861/subgraphs/TeavaultV3PairLinea-linea/1.0/gn";

export const client = createPublicClient({
chain: linea,
transport: http("https://rpc.linea.build")
})
Loading

0 comments on commit c44e89c

Please sign in to comment.