-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from DegenYM/TeahouseFinance
add Teahouse V3pair adapter, calculate user TVL in tokens and exclude the pairs out of range.
- Loading branch information
Showing
8 changed files
with
563 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
}) |
Oops, something went wrong.