diff --git a/adapters/vfat/hourly_blocks.csv b/adapters/vfat/hourly_blocks.csv index bb659a24..35afaf0d 100644 --- a/adapters/vfat/hourly_blocks.csv +++ b/adapters/vfat/hourly_blocks.csv @@ -1,2 +1,2 @@ number,timestamp -9397663,1726210798 \ No newline at end of file +11234667,1729932385 diff --git a/adapters/vfat/src/index.ts b/adapters/vfat/src/index.ts index e8b253b2..e40f2a42 100644 --- a/adapters/vfat/src/index.ts +++ b/adapters/vfat/src/index.ts @@ -5,7 +5,6 @@ import fs from 'fs'; import { write } from 'fast-csv'; import { BlockData, OutputSchemaRow } from './sdk/pancake/types'; - import { UserVote, UserPosition } from './sdk/nile/types'; import { @@ -24,7 +23,6 @@ import { fetchUserVotes } from './sdk/nile/lensDetails'; import BigNumber from 'bignumber.js'; const pipeline = promisify(stream.pipeline); - const NILE_ADDRESS = '0xAAAac83751090C6ea42379626435f805DDF54DC8'.toLowerCase(); export const getUserTVLByBlock = async ({ @@ -69,10 +67,10 @@ export const getUserTVLByBlock = async ({ // Get sickles and their owners const sickleAddresses = await getSickles(blockNumber); const sickleOwners = await getSickleOwners( - sickleAddresses.map((s: any) => s.sickle) + sickleAddresses.map((s: any) => s.sickle), + BigInt(blockNumber) // Pass blockNumber as the second argument ); - -// remove non sickle addresses from balances + // Remove non-sickle addresses from balances for (const [user, tokenBalances] of Object.entries(balances)) { if (!sickleOwners[user]) { delete balances[user]; @@ -84,7 +82,7 @@ export const getUserTVLByBlock = async ({ for (const [user, tokenBalances] of Object.entries(balances)) { const owner = ( (sickleOwners as Record)[user] || user - ).toLowerCase(); // Replace sickle address with owner address + ).toLowerCase(); if (!updatedBalances[owner]) { updatedBalances[owner] = {}; } @@ -183,24 +181,13 @@ export const getUserVotesTVLByBlock = async ( [userAddress: string]: BigNumber; }; - const batchSize = 300; - let userVotesResult: any[] = []; - for (let i = 0; i < userAddresses.length; i += batchSize) { - const batch = userAddresses.slice(i, i + batchSize); - userVotesResult = userVotesResult.concat( - await Promise.all( - batch.map((user) => fetchUserVotes(BigInt(blockNumber), user)) - ) - ); - } + const userVotesResult = await fetchUserVotes(BigInt(blockNumber), userAddresses); for (const userFecthedVotes of userVotesResult) { - for (const userVote of userFecthedVotes) { - const userAddress = userVote.result.userAddress.toLowerCase(); - tokenBalanceMap[userAddress] = BigNumber( - tokenBalanceMap[userAddress] ?? 0 - ).plus(userVote.result.amount.toString()); - } + const userAddress = userFecthedVotes.result.userAddress.toLowerCase(); + tokenBalanceMap[userAddress] = BigNumber( + tokenBalanceMap[userAddress] ?? 0 + ).plus(userFecthedVotes.result.amount.toString()); } Object.entries(tokenBalanceMap).forEach(([userAddress, balance]) => { @@ -220,7 +207,7 @@ const readBlocksFromCSV = async (filePath: string): Promise => { await new Promise((resolve, reject) => { fs.createReadStream(filePath) - .pipe(csv()) // Specify the separator as '\t' for TSV files + .pipe(csv()) .on('data', (row) => { const blockNumber = parseInt(row.number, 10); const blockTimestamp = parseInt(row.timestamp, 10); @@ -228,26 +215,20 @@ const readBlocksFromCSV = async (filePath: string): Promise => { blocks.push({ blockNumber: blockNumber, blockTimestamp }); } }) - .on('end', () => { - resolve(); - }) - .on('error', (err) => { - reject(err); - }); + .on('end', resolve) + .on('error', reject); }); return blocks; }; readBlocksFromCSV('hourly_blocks.csv') - .then(async (blocks: any[]) => { - console.log(blocks); - const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks + .then(async (blocks: BlockData[]) => { + const allCsvRows: OutputSchemaRow[] = []; for (const block of blocks) { try { const result = await getUserTVLByBlock(block); - // Accumulate CSV rows for all blocks allCsvRows.push(...result); } catch (error) { console.error(`An error occurred for block ${block.blockNumber}:`, error); @@ -262,9 +243,7 @@ readBlocksFromCSV('hourly_blocks.csv') console.log('CSV file has been written.'); resolve; }) - .on('error', (err) => { - reject(err); - }); + .on('error', reject); }); }) .catch((err) => { diff --git a/adapters/vfat/src/sdk/nile/lensDetails.ts b/adapters/vfat/src/sdk/nile/lensDetails.ts index f2e4263f..f8da609c 100644 --- a/adapters/vfat/src/sdk/nile/lensDetails.ts +++ b/adapters/vfat/src/sdk/nile/lensDetails.ts @@ -2,7 +2,7 @@ import { Abi, Address, MulticallParameters, PublicClient } from "viem"; import { client } from "./config"; import veNILEAbi from "./abis/veNILE.json"; -export const VE_NILE_ADDRESS = "0xaaaea1fb9f3de3f70e89f37b69ab11b47eb9ce6f"; // veNILE +export const VE_NILE_ADDRESS = "0xaaaea1fb9f3de3f70e89f37b69ab11b47eb9ce6f"; export interface VoteRequest { userAddress: string; @@ -13,71 +13,73 @@ export interface VoteResponse { result: VoteRequest; } -// Function to fetch user votes with batching export const fetchUserVotes = async ( blockNumber: bigint, - userAddress: string, + userAddresses: string[], ): Promise => { const publicClient = client; - const userBalanceCall = await multicall( + const balanceCalls = userAddresses.map((userAddress) => ({ + address: VE_NILE_ADDRESS, + name: "balanceOf", + params: [userAddress], + })); + + const userBalances = await batchMulticall( publicClient, veNILEAbi as Abi, - [ - { - address: VE_NILE_ADDRESS, - name: "balanceOf", - params: [userAddress], - }, - ], + balanceCalls, blockNumber, + 200, + 2000, ); - const userBalance = userBalanceCall[0].result as number; - - if (userBalance === 0) return []; - - const calls = []; - for (let i = 0; i < userBalance; i++) { - calls.push({ - address: VE_NILE_ADDRESS, - name: "tokenOfOwnerByIndex", - params: [userAddress, i], - }); - } + const tokenCalls: any = []; + userBalances.forEach((balance, index) => { + const userAddress = userAddresses[index]; + const userBalance = balance.result as number; + + if (userBalance > 0) { + for (let i = 0; i < userBalance; i++) { + tokenCalls.push({ + address: VE_NILE_ADDRESS, + name: "tokenOfOwnerByIndex", + params: [userAddress, i], + }); + } + } + }); const userTokensCalls = await batchMulticall( publicClient, veNILEAbi as Abi, - calls, + tokenCalls, blockNumber, 500, - 200 + 200, ); - const detailsCall = userTokensCalls.map((call) => { - return { - address: VE_NILE_ADDRESS, - name: "locked", - params: [call.result], - }; - }); + const detailsCalls = userTokensCalls.map((call) => ({ + address: VE_NILE_ADDRESS, + name: "locked", + params: [call.result], + })); const res = (await batchMulticall( publicClient, veNILEAbi as Abi, - detailsCall, + detailsCalls, blockNumber, - 500, - 200 + 200, + 2000, )) as any; - return res.map((r: any) => { + return res.map((r: any, index: any) => { + const userAddress = userAddresses[Math.floor(index / tokenCalls.length)]; return { result: { amount: r.result[0], userAddress } }; }) as VoteResponse[]; }; -// Batch multicall function with a delay async function batchMulticall( publicClient: PublicClient, abi: Abi, @@ -101,11 +103,9 @@ async function batchMulticall( blockNumber, }; - // Send the batch of requests const res = await publicClient.multicall(call); results.push(...res); - // Introduce delay before sending the next batch if (i + batchSize < calls.length) { await new Promise((resolve) => setTimeout(resolve, delay)); } @@ -113,25 +113,3 @@ async function batchMulticall( return results; } - -// Regular multicall function -function multicall( - publicClient: PublicClient, - abi: Abi, - calls: any[], - blockNumber: bigint, -) { - const call: MulticallParameters = { - contracts: calls.map((call) => { - return { - address: call.address as Address, - abi, - functionName: call.name, - args: call.params, - }; - }), - blockNumber, - }; - - return publicClient.multicall(call); -} diff --git a/adapters/vfat/src/sdk/pancake/lib.ts b/adapters/vfat/src/sdk/pancake/lib.ts index 4240ef9f..d6384c03 100644 --- a/adapters/vfat/src/sdk/pancake/lib.ts +++ b/adapters/vfat/src/sdk/pancake/lib.ts @@ -172,80 +172,70 @@ export const getSickles = async (blockNumber: number) => { } }`; - const response = await fetch(VFAT_SUBGRAPH_URL, { - method: 'POST', - body: JSON.stringify({ query }), - headers: { 'Content-Type': 'application/json' }, - }); - - const { data } = await response.json(); - - return (data as { sickleAddresses: { sickle: `0x${string}` }[] }).sickleAddresses; + try { + const response = await fetch(VFAT_SUBGRAPH_URL, { + method: 'POST', + body: JSON.stringify({ query }), + headers: { 'Content-Type': 'application/json' }, + }); + const rawBody = await response.text(); + try { + const { data } = JSON.parse(rawBody); + return (data as { sickleAddresses: { sickle: `0x${string}` }[] }).sickleAddresses; + } catch (jsonError) { + console.error(`JSON parsing error for block ${blockNumber}:`, jsonError); + console.error('Raw response body:', rawBody); // Log raw body + throw jsonError; + } + } catch (error) { + // Log network or other fetch errors + console.error(`Error fetching data for block ${blockNumber}:`, error); + throw error; + } }; -const getOwnerFromMasterChef = async ( + +export const getOwnerFromMasterChef = async ( pids: string[], blockNumber: bigint, -) => { +): Promise => { const abi = [ { - inputs: [ - { internalType: 'uint256', name: '', type: 'uint256' }, - ], + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], name: 'userPositionInfos', outputs: [ - { - internalType: 'uint128', - name: 'liquidity', - type: 'uint128', - }, - { - internalType: 'uint128', - name: 'boostLiquidity', - type: 'uint128', - }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint128', name: 'boostLiquidity', type: 'uint128' }, { internalType: 'int24', name: 'tickLower', type: 'int24' }, { internalType: 'int24', name: 'tickUpper', type: 'int24' }, - { - internalType: 'uint256', - name: 'rewardGrowthInside', - type: 'uint256', - }, + { internalType: 'uint256', name: 'rewardGrowthInside', type: 'uint256' }, { internalType: 'uint256', name: 'reward', type: 'uint256' }, { internalType: 'address', name: 'user', type: 'address' }, { internalType: 'uint256', name: 'pid', type: 'uint256' }, - { - internalType: 'uint256', - name: 'boostMultiplier', - type: 'uint256', - }, + { internalType: 'uint256', name: 'boostMultiplier', type: 'uint256' }, ], stateMutability: 'view', type: 'function', }, ] as const; - const results = await client.multicall({ - allowFailure: false, - blockNumber, - contracts: pids.map( - (pid) => - ({ - abi, - address: masterChefAddress, - functionName: 'userPositionInfos', - args: [BigInt(pid)], - } as const), - ), - }); + const calls = pids.map((pid) => ({ + address: masterChefAddress, + name: 'userPositionInfos', + params: [BigInt(pid)], + })); - return results.map((r) => { - return r[6]; - }); + const results = await batchMulticall(abi, calls, blockNumber, 300, 200); + + return results.map((r) => (r as any)[6] as string); }; -export const getSickleOwners = async (sickleAddresses: `0x${string}`[]): Promise> => { + +export const getSickleOwners = async ( + sickleAddresses: `0x${string}`[], + blockNumber: bigint, +): Promise> => { const abi: Abi = [ { inputs: [], @@ -256,26 +246,19 @@ export const getSickleOwners = async (sickleAddresses: `0x${string}`[]): Promise }, ] as const; - const results = await client.multicall({ - allowFailure: false, - contracts: sickleAddresses.map( - (sickle) => - ({ - abi, - address: sickle, - functionName: 'owner', - args: [], - } as const), - ), - }); + const calls = sickleAddresses.map((sickle) => ({ + address: sickle, + name: 'owner', + params: [], + })); - const resultsArray = results as string[]; + const results = await batchMulticall(abi, calls, blockNumber, 300, 2000); const sickleOwners: Record = {}; for (let i = 0; i < sickleAddresses.length; i++) { - sickleOwners[sickleAddresses[i]] = resultsArray[i]; + sickleOwners[sickleAddresses[i]] = results[i] as string; } - + return sickleOwners; }; @@ -286,4 +269,33 @@ export const getTimestampAtBlock = async (blockNumber: number) => { blockNumber: BigInt(blockNumber), }); return Number(block.timestamp * 1000n); -}; \ No newline at end of file +}; + +async function batchMulticall( + abi: Abi, + calls: any[], + blockNumber: bigint, + batchSize: number, + delay: number, +) { + const results = []; + for (let i = 0; i < calls.length; i += batchSize) { + const batch = calls.slice(i, i + batchSize); + const res = await client.multicall({ + allowFailure: false, + blockNumber, + contracts: batch.map((call) => ({ + abi, + address: call.address, + functionName: call.name, + args: call.params, + })), + }); + results.push(...res); + + if (i + batchSize < calls.length) { + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + return results; +} \ No newline at end of file