From 4baf96ce228c6532c8cdcc7842640ee9a8cc3e9c Mon Sep 17 00:00:00 2001 From: sdcrypt0 <> Date: Tue, 26 Mar 2024 11:37:41 +0300 Subject: [PATCH 1/3] metavault adapter --- adapters/metavault/package.json | 32 ++ adapters/metavault/src/index.ts | 247 +++++++++++ adapters/metavault/src/sdk/blockApi.ts | 35 ++ adapters/metavault/src/sdk/config.ts | 24 ++ .../metavault/src/sdk/entities/positions.ts | 24 ++ adapters/metavault/src/sdk/mathUtils.ts | 37 ++ adapters/metavault/src/sdk/poolDetails.ts | 0 adapters/metavault/src/sdk/positionDetails.ts | 51 +++ adapters/metavault/src/sdk/subgraphDetails.ts | 386 ++++++++++++++++++ adapters/metavault/src/sdk/utils/constant.ts | 38 ++ .../src/sdk/utils/fractions/fraction.ts | 159 ++++++++ .../src/sdk/utils/fractions/percent.ts | 43 ++ adapters/metavault/src/sdk/utils/fullMath.ts | 16 + .../src/sdk/utils/internalConstants.ts | 16 + .../src/sdk/utils/mostSignificantBit.ts | 20 + .../metavault/src/sdk/utils/positionMath.ts | 50 +++ .../src/sdk/utils/squarePriceMath.ts | 131 ++++++ .../metavault/src/sdk/utils/tickLibrary.ts | 59 +++ adapters/metavault/src/sdk/utils/tickMath.ts | 115 ++++++ adapters/metavault/tsconfig.json | 109 +++++ 20 files changed, 1592 insertions(+) create mode 100644 adapters/metavault/package.json create mode 100644 adapters/metavault/src/index.ts create mode 100644 adapters/metavault/src/sdk/blockApi.ts create mode 100644 adapters/metavault/src/sdk/config.ts create mode 100644 adapters/metavault/src/sdk/entities/positions.ts create mode 100644 adapters/metavault/src/sdk/mathUtils.ts create mode 100644 adapters/metavault/src/sdk/poolDetails.ts create mode 100644 adapters/metavault/src/sdk/positionDetails.ts create mode 100644 adapters/metavault/src/sdk/subgraphDetails.ts create mode 100644 adapters/metavault/src/sdk/utils/constant.ts create mode 100644 adapters/metavault/src/sdk/utils/fractions/fraction.ts create mode 100644 adapters/metavault/src/sdk/utils/fractions/percent.ts create mode 100644 adapters/metavault/src/sdk/utils/fullMath.ts create mode 100644 adapters/metavault/src/sdk/utils/internalConstants.ts create mode 100644 adapters/metavault/src/sdk/utils/mostSignificantBit.ts create mode 100644 adapters/metavault/src/sdk/utils/positionMath.ts create mode 100644 adapters/metavault/src/sdk/utils/squarePriceMath.ts create mode 100644 adapters/metavault/src/sdk/utils/tickLibrary.ts create mode 100644 adapters/metavault/src/sdk/utils/tickMath.ts create mode 100644 adapters/metavault/tsconfig.json diff --git a/adapters/metavault/package.json b/adapters/metavault/package.json new file mode 100644 index 00000000..eccdbff5 --- /dev/null +++ b/adapters/metavault/package.json @@ -0,0 +1,32 @@ +{ + "name": "metavault", + "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", + "fast-csv": "^5.0.1", + "jsbi": "^4.3.0", + "tiny-invariant": "^1.3.1", + "toformat": "^2.0.0", + "viem": "^2.8.13" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/adapters/metavault/src/index.ts b/adapters/metavault/src/index.ts new file mode 100644 index 00000000..22073aa7 --- /dev/null +++ b/adapters/metavault/src/index.ts @@ -0,0 +1,247 @@ +import BigNumber from "bignumber.js"; +import { CHAINS, PROTOCOLS, AMM_TYPES } from "./sdk/config"; +import { getLPValueByUserAndPoolFromPositions, getPositionAtBlock, getPositionDetailsFromPosition, getPositionsForAddressByPoolAtBlock, getTimestampAtBlock, getTradeLiquidityForAddressByPoolAtBlock } from "./sdk/subgraphDetails"; +(BigInt.prototype as any).toJSON = function () { + return this.toString(); +}; + +import { promisify } from 'util'; +import stream from 'stream'; +import csv from 'csv-parser'; +import fs from 'fs'; +import { format } from 'fast-csv'; +import { write } from 'fast-csv'; +import { pipeline as streamPipeline } from 'stream'; +import { captureRejectionSymbol } from "events"; +import { readBlocksFromApi } from "./sdk/blockApi"; + + +//Uncomment the following lines to test the getPositionAtBlock function + +// const position = getPositionAtBlock( +// 0, // block number 0 for latest block +// 2, // position id +// CHAINS.L2_CHAIN_ID, // chain id +// PROTOCOLS.PROTOCOL_NAME, // protocol +// AMM_TYPES.UNISWAPV3 // amm type +// ); +// position.then((position) => { +// // print response +// const result = getPositionDetailsFromPosition(position); +// console.log(`${JSON.stringify(result,null, 4)} +// `) +// }); + +interface LPValueDetails { + pool: string; + lpValue: string; +} + +interface UserLPData { + totalLP: string; + pools: LPValueDetails[]; +} + +// Define an object type that can be indexed with string keys, where each key points to a UserLPData object +interface OutputData { + [key: string]: UserLPData; +} + +interface CSVRow { + user: string; + pool: string; + block: number; + position: number; + lpvalue: string; +} + + +const pipeline = promisify(stream.pipeline); + +// Assuming you have the following functions and constants already defined +// getPositionsForAddressByPoolAtBlock, CHAINS, PROTOCOLS, AMM_TYPES, getPositionDetailsFromPosition, getLPValueByUserAndPoolFromPositions, BigNumber + +// const readBlocksFromCSV = async (filePath: string): Promise => { +// 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 snapshotBlocks = [ +// 3116208, 3159408, 3202608, 3245808, 3289008, 3332208, +// 3375408, 3418608, 3461808, 3505008, 3548208, 3591408, +// 3634608, 3677808, 3721008, 3764208, 3807408, 3850608, +// 3893808, 3937008, 3980208, 3983003, +// ]; //await readBlocksFromCSV('src/sdk/L2_CHAIN_ID_chain_daily_blocks.csv'); + +// const csvRows: CSVRow[] = []; + +// for (let block of snapshotBlocks) { +// const positions = await getPositionsForAddressByPoolAtBlock( +// block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.PROTOCOL_NAME, AMM_TYPES.UNISWAPV3 +// ); + +// console.log(`Block: ${block}`); +// console.log("Positions: ", positions.length); + +// // Assuming this part of the logic remains the same +// let positionsWithUSDValue = positions.map(getPositionDetailsFromPosition); +// let lpValueByUsers = getLPValueByUserAndPoolFromPositions(positionsWithUSDValue); + +// lpValueByUsers.forEach((value, key) => { +// let positionIndex = 0; // Define how you track position index +// value.forEach((lpValue, poolKey) => { +// const lpValueStr = lpValue.toString(); +// // Accumulate CSV row data +// csvRows.push({ +// user: key, +// pool: poolKey, +// block, +// position: positions.length, // Adjust if you have a specific way to identify positions +// lpvalue: lpValueStr, +// }); +// }); +// }); +// } + +// // 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."); +// }); +// }; + + + + +interface BlockData { + blockNumber: number; + blockTimestamp: number; +} + + +const readBlocksFromCSV = async (filePath: string): Promise => { + const blocks: BlockData[] = []; + + await new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(csv({ separator: '\t' })) // Specify the separator as '\t' for TSV files + .on('data', (row) => { + const blockNumber = parseInt(row.number, 10); + const blockTimestamp = parseInt(row.block_timestamp, 10); + if (!isNaN(blockNumber) && blockTimestamp) { + blocks.push({ blockNumber: blockNumber, blockTimestamp }); + } + }) + .on('end', () => { + resolve(); + }) + .on('error', (err) => { + reject(err); + }); + }); + + return blocks; +}; + +type OutputDataSchemaRow = { + block_number: number; + timestamp: number; + user_address: string; + token_address: string; + token_balance: bigint; + token_symbol: string; +}; +async function main() { + const snapshotBlocks = [3137407, 3138307]; + + // readBlocksFromApi(1711324800, 1711411200).then(async (blocks) => { // uncomment for block api + (async () => snapshotBlocks)().then(async (blocks) => { + console.log(blocks); + const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks + const batchSize = 10; // Size of batch to trigger writing to the file + let i = 0; + + for (const block of blocks) { + try { + const timestamp = new Date(await getTimestampAtBlock(block)).toISOString(); + const positions = await getPositionsForAddressByPoolAtBlock( + block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.UNISWAPV3 + ); + + console.log(`Block: ${block}`); + console.log("Positions: ", positions.length); + + // Assuming this part of the logic remains the same + let positionsWithUSDValue = positions.map(getPositionDetailsFromPosition); + // let lpValueByUsers = getLPValueByUserAndPoolFromPositions(positionsWithUSDValue); + + positionsWithUSDValue.forEach((value, key) => { + + // Accumulate CSV row data + allCsvRows.push({ + block_number: block, + timestamp, + user: value.owner, + token_address: value.token0.id, + token_balance: value.token0DecimalValue, + token_balance_usd: value.token0USDValue, + }); + + allCsvRows.push({ + block_number: block, + timestamp, + user: value.owner, + token_address: value.token1.id, + token_balance: value.token1DecimalValue, + token_balance_usd: value.token1USDValue, + }); + }); + const liquidities = await getTradeLiquidityForAddressByPoolAtBlock( + block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.TRADE + ); + liquidities.forEach((value, key) => { + allCsvRows.push({ + block_number: block, + timestamp, + user: value.user, + token_address: value.asset, + token_balance: value.amount, + token_balance_usd: value.amountUsd, + }); + }); + + // Write the CSV output to a file + const ws = fs.createWriteStream('outputData.csv'); + write(allCsvRows, { headers: true }).pipe(ws).on('finish', () => { + console.log("CSV file has been written."); + }); + } catch (error) { + console.error(`An error occurred for block ${block}:`, error); + } + + } + }) + .catch((err) => { + console.error('Error reading CSV file:', err); + }); + + +} +main().then(() => { + console.log("Done"); +}); +// getPrice(new BigNumber('1579427897588720602142863095414958'), 6, 18); //Uniswap +// getPrice(new BigNumber('3968729022398277600000000'), 18, 6); //PROTOCOL_NAME + diff --git a/adapters/metavault/src/sdk/blockApi.ts b/adapters/metavault/src/sdk/blockApi.ts new file mode 100644 index 00000000..5ac856bc --- /dev/null +++ b/adapters/metavault/src/sdk/blockApi.ts @@ -0,0 +1,35 @@ +export const readBlocksFromApi = async (startTime: number, endTime: number): Promise => { + const results = [] + for (let i = startTime; i <= endTime; i += 3600) { + let response = await fetch(`https://api.lineascan.build/api?module=block&action=getblocknobytime×tamp=${i}&closest=after&apikey=ADD_API_KEY`, { + headers: { "Content-Type": "application/json" }, + }); + + const json = await response.json() + // sleep + await new Promise(r => setTimeout(r, 1000)); + results.push(Number(json.result)) + } + console.log(results); + + return results + + // daily blocks + return [ + 1459540, 1473940, 1488339, 1502715, 1517109, 1531509, + 1545909, 1560309, 1574707, 1589104, 1603499, 1617898, + 1632296, 1646696, 1661096, 1675487, 1689885, 1704285, + 1718685, 1733085, 1747485, 1761885, 1779844, 1800972, + 1822511, 1844067, 1865652, 1887247, 1908844, 1930429, + 1951984, 1973539, 1994976, 2016544, 2038130, 2059720, + 2081313, 2102851, 2124303, 2145723, 2166829, 2188347, + 2209908, 2231357, 2252825, 2274361, 2295942, 2317539, + 2339139, 2360738, 2382328, 2403928, 2425521, 2447121, + 2468721, 2490321, 2511921, 2533521, 2555120, 2576720, + 2598320, 2619920, 2641520, 2663120, 2684720, 2706320, + 2727920, 2749520, 2771120, 2792720, 2814320, 2835920, + 2857520, 2879120, 2900720, 2922320, 2943907, 2965507, + 2987107, 3008707, 3030307, 3051907, 3073507, 3095107, + 3116707 + ] +} \ No newline at end of file diff --git a/adapters/metavault/src/sdk/config.ts b/adapters/metavault/src/sdk/config.ts new file mode 100644 index 00000000..62cbe677 --- /dev/null +++ b/adapters/metavault/src/sdk/config.ts @@ -0,0 +1,24 @@ +export const enum CHAINS{ + L2_CHAIN_ID = 59144, +} +export const enum PROTOCOLS{ + METAVAULT = 0, +} + +export const enum AMM_TYPES{ + UNISWAPV3 = 0, + TRADE = 1 +} + +export const SUBGRAPH_URLS = { + [CHAINS.L2_CHAIN_ID]: { + [PROTOCOLS.METAVAULT]: { + [AMM_TYPES.UNISWAPV3]: "https://api.studio.thegraph.com/query/55804/linea-v3/version/latest", + [AMM_TYPES.TRADE]: "http://localhost:8000/subgraphs/name/metavault/perpv1" + } + }, + +} +export const RPC_URLS = { + [CHAINS.L2_CHAIN_ID]: "https://rpc.linea.build" +} \ No newline at end of file diff --git a/adapters/metavault/src/sdk/entities/positions.ts b/adapters/metavault/src/sdk/entities/positions.ts new file mode 100644 index 00000000..bb410185 --- /dev/null +++ b/adapters/metavault/src/sdk/entities/positions.ts @@ -0,0 +1,24 @@ +import { Q128 } from '../utils/internalConstants' +import { subIn256 } from '../utils/tickLibrary' + +export abstract class PositionLibrary { + /** + * Cannot be constructed. + */ + private constructor() {} + + // replicates the portions of Position#update required to compute unaccounted fees + public static getTokensOwed( + feeGrowthInside0LastX128: bigint, + feeGrowthInside1LastX128: bigint, + liquidity: bigint, + feeGrowthInside0X128: bigint, + feeGrowthInside1X128: bigint + ) { + const tokensOwed0 = (subIn256(feeGrowthInside0X128, feeGrowthInside0LastX128) * liquidity) / Q128 + + const tokensOwed1 = (subIn256(feeGrowthInside1X128, feeGrowthInside1LastX128) * liquidity) / Q128 + + return [tokensOwed0, tokensOwed1] + } +} diff --git a/adapters/metavault/src/sdk/mathUtils.ts b/adapters/metavault/src/sdk/mathUtils.ts new file mode 100644 index 00000000..d245b2df --- /dev/null +++ b/adapters/metavault/src/sdk/mathUtils.ts @@ -0,0 +1,37 @@ +import { BigNumber } from 'bignumber.js'; + +export const getPrice = (sqrtPriceX96: BigNumber, Decimal0: number, Decimal1: number)=> { + console.log("sqrtPriceX96 : " + sqrtPriceX96.toString()); + console.log("Decimal0 : " + Decimal0); + console.log("Decimal1 : " + Decimal1); + + const twoPower96 = new BigNumber(2).pow(96); + const tenPowerDecimal0 = new BigNumber(10).pow(Decimal0); + const tenPowerDecimal1 = new BigNumber(10).pow(Decimal1); + + // Perform calculations using BigNumber for high precision arithmetic + const priceSquared = (sqrtPriceX96.dividedBy(twoPower96)).pow(2); + + console.log("priceSquared : " + priceSquared.toString()); + const ratio = priceSquared.multipliedBy(tenPowerDecimal0).dividedBy(tenPowerDecimal1); + console.log("ratio : " + ratio.toString()); + // Convert to string with fixed decimal places for display + const buyOneOfToken0 = ratio.toFixed(Decimal1); + const buyOneOfToken1 = new BigNumber(1).div(ratio).toFixed(Decimal0); + + console.log(`price of token0 in value of token1 : ${buyOneOfToken0}`); + console.log(`price of token1 in value of token0 : ${buyOneOfToken1}`); + console.log(""); + + // Convert to lowest decimal representation for display + const buyOneOfToken0Wei = ratio.multipliedBy(tenPowerDecimal1).toFixed(0); + const buyOneOfToken1Wei = new BigNumber(1).div(ratio).multipliedBy(tenPowerDecimal0).toFixed(0); + + console.log(`price of token0 in value of token1 in lowest decimal : ${buyOneOfToken0Wei}`); + console.log(`price of token1 in value of token1 in lowest decimal : ${buyOneOfToken1Wei}`); + console.log(""); +} + +// // Example usage with BigNumber inputs: +// // Convert string inputs to BigNumber. Ensure the input values are strings to prevent precision loss. +// getPrice(new BigNumber('3956146591263498000000000'), 18, 6); diff --git a/adapters/metavault/src/sdk/poolDetails.ts b/adapters/metavault/src/sdk/poolDetails.ts new file mode 100644 index 00000000..e69de29b diff --git a/adapters/metavault/src/sdk/positionDetails.ts b/adapters/metavault/src/sdk/positionDetails.ts new file mode 100644 index 00000000..a51d248a --- /dev/null +++ b/adapters/metavault/src/sdk/positionDetails.ts @@ -0,0 +1,51 @@ +function getTickAtSqrtPrice(sqrtPriceX96: bigint): number { + const Q96: bigint = 2n ** 96n; + return Math.floor(Math.log(Number(sqrtPriceX96 / Q96) ** 2) / Math.log(1.0001)); +} + +export const getTokenAmounts = async( + liquidity: bigint, + sqrtPriceX96: bigint, + tickLow: number, + tickHigh: number, + Decimal0: number, + Decimal1: number +)=> { + const Q96: bigint = 2n ** 96n; + const sqrtRatioA: number = Math.sqrt(1.0001 ** tickLow); + const sqrtRatioB: number = Math.sqrt(1.0001 ** tickHigh); + const currentTick: number = getTickAtSqrtPrice(sqrtPriceX96); + const sqrtPrice: number = Number(sqrtPriceX96 / Q96); + let amount0: bigint = 0n; + let amount1: bigint = 0n; + + // print all the values + console.log("liquidity : " + liquidity.toString()); + console.log("sqrtPriceX96 : " + sqrtPriceX96.toString()); + console.log("tickLow : " + tickLow); + console.log("tickHigh : " + tickHigh); + console.log("Decimal0 : " + Decimal0); + console.log("Decimal1 : " + Decimal1); + console.log("sqrtRatioA : " + sqrtRatioA); + console.log("sqrtRatioB : " + sqrtRatioB); + console.log("currentTick : " + currentTick); + console.log("sqrtPrice : " + sqrtPrice); + + + if (currentTick < tickLow) { + amount0 = BigInt(Math.floor(Number(liquidity) * ((sqrtRatioB - sqrtRatioA) / (sqrtRatioA * sqrtRatioB)))); + } else if (currentTick >= tickHigh) { + amount1 = BigInt(Math.floor(Number(liquidity) * (sqrtRatioB - sqrtRatioA))); + } else if (currentTick >= tickLow && currentTick < tickHigh) { + amount0 = BigInt(Math.floor(Number(liquidity) * ((sqrtRatioB - sqrtPrice) / (sqrtPrice * sqrtRatioB)))); + amount1 = BigInt(Math.floor(Number(liquidity) * (sqrtPrice - sqrtRatioA))); + } + let amount0Human: string = (Number(amount0) / 10 ** Decimal0).toFixed(Decimal0); + let amount1Human: string = (Number(amount1) / 10 ** Decimal1).toFixed(Decimal1); + + console.log("Amount Token0 in lowest decimal: " + amount0.toString()); + console.log("Amount Token1 in lowest decimal: " + amount1.toString()); + console.log("Amount Token0 : " + amount0Human); + console.log("Amount Token1 : " + amount1Human); + return [amount0, amount1]; +} diff --git a/adapters/metavault/src/sdk/subgraphDetails.ts b/adapters/metavault/src/sdk/subgraphDetails.ts new file mode 100644 index 00000000..9bf18158 --- /dev/null +++ b/adapters/metavault/src/sdk/subgraphDetails.ts @@ -0,0 +1,386 @@ +import BigNumber from "bignumber.js"; +import { AMM_TYPES, CHAINS, PROTOCOLS, RPC_URLS, SUBGRAPH_URLS } from "./config"; +import { PositionMath } from "./utils/positionMath"; +import { createPublicClient, extractChain, http } from "viem"; +import { linea } from "viem/chains"; + + + +export interface Position { + id: string; + liquidity: bigint; + owner: string; + pool: { + sqrtPrice: bigint; + tick: number; + id: string; + }; + tickLower: { + tickIdx: number; + }; + tickUpper: { + tickIdx: number; + }; + + token0: { + id: string; + decimals: number; + derivedUSD: number; + name: string; + symbol: string; + }; + token1: { + id: string; + decimals: number; + derivedUSD: number; + name: string; + symbol: string; + } +}; + + +export interface PositionWithUSDValue extends Position { + token0USDValue: string; + token1USDValue: string; + token0AmountsInWei: bigint; + token1AmountsInWei: bigint; + token0DecimalValue: number; + token1DecimalValue: number; +} + +export const getPositionsForAddressByPoolAtBlock = async ( + blockNumber: number, + address: string, + poolId: string, + chainId: CHAINS, + protocol: PROTOCOLS, + ammType: AMM_TYPES +): Promise => { + let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; + let blockQuery = blockNumber !== 0 ? ` block: {number: ${blockNumber}}` : ``; + let poolQuery = poolId !== "" ? ` pool_:{id: "${poolId.toLowerCase()}"}` : ``; + let ownerQuery = address !== "" ? `owner: "${address.toLowerCase()}"` : ``; + + let whereQuery = ownerQuery !== "" && poolQuery !== "" ? `where: {${ownerQuery} , ${poolQuery}}` : ownerQuery !== "" ? `where: {${ownerQuery}}` : poolQuery !== "" ? `where: {${poolQuery}}` : ``; + let skip = 0; + let fetchNext = true; + let result: Position[] = []; + while (fetchNext) { + let query = `{ + bundle( id: 1 ${blockQuery}){ + ethPriceUSD + } + positions(${whereQuery} ${blockQuery} orderBy: transaction__timestamp, first:1000,skip:${skip}) { + id + + liquidity + owner + pool { + sqrtPrice + tick + id + } + tickLower{ + tickIdx + } + tickUpper{ + tickIdx + } + token0 { + id + decimals + derivedETH + name + symbol + } + token1 { + id + decimals + derivedETH + name + symbol + } + }, + _meta{ + block{ + number + } + } + }`; + + // console.log(query) + + let response = await fetch(subgraphUrl, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + let positions = data.data.positions; + let ethPriceUSD = data.data.bundle.ethPriceUSD + + for (let i = 0; i < positions.length; i++) { + let position = positions[i]; + let transformedPosition: Position = { + id: position.id, + liquidity: BigInt(position.liquidity), + owner: position.owner, + pool: { + sqrtPrice: BigInt(position.pool.sqrtPrice), + tick: Number(position.pool.tick), + id: position.pool.id, + }, + tickLower: { + tickIdx: Number(position.tickLower.tickIdx), + }, + tickUpper: { + tickIdx: Number(position.tickUpper.tickIdx), + }, + token0: { + id: position.token0.id, + decimals: position.token0.decimals, + derivedUSD: Number(position.token0.derivedETH) * Number(ethPriceUSD), + name: position.token0.name, + symbol: position.token0.symbol, + }, + token1: { + id: position.token1.id, + decimals: position.token1.decimals, + derivedUSD: Number(position.token1.derivedETH) * Number(ethPriceUSD), + name: position.token1.name, + symbol: position.token1.symbol, + }, + }; + result.push(transformedPosition); + + } + if (positions.length < 1000) { + fetchNext = false; + } else { + skip += 1000; + } + } + return result; +} +export const getTradeLiquidityForAddressByPoolAtBlock = async ( + blockNumber: number, + address: string, + poolId: string, + chainId: CHAINS, + protocol: PROTOCOLS, + ammType: AMM_TYPES +): Promise => { + let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; + let blockQuery = blockNumber !== 0 ? ` block: {number: ${blockNumber}}` : ``; + let whereQuery = address !== "" ? `where: {user: "${address.toLowerCase()}"}` : `where: {period: "user"}`; + + ; + let skip = 0; + let fetchNext = true; + let result: any[] = []; + while (fetchNext) { + let query = `{ + liquidities(${whereQuery} ${blockQuery} orderBy: createTimestamp, first:1000,skip:${skip}) { + user + asset + amountUsd + amount + }, + _meta{ + block{ + number + } + } + }`; + + // console.log(query) + + let response = await fetch(subgraphUrl, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + let liquidities = data.data.liquidities; + + result = result.concat(liquidities) + if (liquidities.length < 1000) { + fetchNext = false; + } else { + skip += 1000; + } + } + return result; +} + +export const getPositionAtBlock = async ( + blockNumber: number, + positionId: number, + chainId: CHAINS, + protocol: PROTOCOLS, + ammType: AMM_TYPES +): Promise => { + let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; + let blockQuery = blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; + let query = `{ + bundle( id: 1 ${blockQuery}){ + ethPriceUSD + } + position(id: "${positionId}" ${blockQuery}) { + id + pool { + sqrtPrice + tick + } + tickLower{ + tickIdx + } + tickUpper{ + tickIdx + } + liquidity + token0 { + id + decimals + derivedETH + name + symbol + } + token1 { + id + decimals + derivedETH + name + symbol + } + }, + _meta{ + block{ + number + } + } + }`; + let response = await fetch(subgraphUrl, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + let position = data.data.position; + const ethPriceUSD = data.data.bundle.ethPriceUSD + + return { + id: position.id, + liquidity: BigInt(position.liquidity), + owner: position.owner, + pool: { + sqrtPrice: BigInt(position.pool.sqrtPrice), + tick: Number(position.pool.tick), + id: position.pool.id, + }, + tickLower: { + tickIdx: Number(position.tickLower.tickIdx), + }, + tickUpper: { + tickIdx: Number(position.tickUpper.tickIdx), + }, + token0: { + id: position.token0.id, + decimals: position.token0.decimals, + derivedUSD: Number(position.token0.derivedETH) * Number(ethPriceUSD), + name: position.token0.name, + symbol: position.token0.symbol, + }, + token1: { + id: position.token1.id, + decimals: position.token1.decimals, + derivedUSD: Number(position.token1.derivedETH) * Number(ethPriceUSD), + name: position.token1.name, + symbol: position.token1.symbol, + }, + }; + + // let tickLow = Number(position.tickLower.tickIdx); + // let tickHigh = Number(position.tickUpper.tickIdx); + // let liquidity = BigInt(position.liquidity); + // let sqrtPriceX96 = BigInt(position.pool.sqrtPrice); + // let tick = Number(position.pool.tick); + // let decimal0 = position.token0.decimals; + // let decimal1 = position.token1.decimals; + // let token0DerivedUSD = position.token0.derivedUSD; + // let token1DerivedUSD = position.token1.derivedUSD; + // let token0AmountsInWei = PositionMath.getToken0Amount(tick, tickLow, tickHigh, sqrtPriceX96, liquidity); + // let token1AmountsInWei = PositionMath.getToken1Amount(tick, tickLow, tickHigh, sqrtPriceX96, liquidity); + + + // let token0DecimalValue = Number(token0AmountsInWei) / 10 ** decimal0; + // let token1DecimalValue = Number(token1AmountsInWei) / 10 ** decimal1; + + // let token0UsdValue = BigNumber(token0AmountsInWei.toString()).multipliedBy(token0DerivedUSD).div(10 ** decimal0).toFixed(4); + // let token1UsdValue = BigNumber(token1AmountsInWei.toString()).multipliedBy(token1DerivedUSD).div(10 ** decimal1).toFixed(4); + + + // return [position.token0, position.token1,token0AmountsInWei, token1AmountsInWei, token0DecimalValue, token1DecimalValue,token0UsdValue, token1UsdValue,data.data._meta]; +} + +export const getPositionDetailsFromPosition = ( + position: Position +): PositionWithUSDValue => { + let tickLow = position.tickLower.tickIdx; + let tickHigh = position.tickUpper.tickIdx; + let liquidity = position.liquidity; + let sqrtPriceX96 = position.pool.sqrtPrice; + let tick = Number(position.pool.tick); + let decimal0 = position.token0.decimals; + let decimal1 = position.token1.decimals; + let token0DerivedUSD = position.token0.derivedUSD; + let token1DerivedUSD = position.token1.derivedUSD; + let token0AmountsInWei = PositionMath.getToken0Amount(tick, tickLow, tickHigh, sqrtPriceX96, liquidity); + let token1AmountsInWei = PositionMath.getToken1Amount(tick, tickLow, tickHigh, sqrtPriceX96, liquidity); + + let token0DecimalValue = Number(token0AmountsInWei) / 10 ** decimal0; + let token1DecimalValue = Number(token1AmountsInWei) / 10 ** decimal1; + + let token0UsdValue = BigNumber(token0AmountsInWei.toString()).multipliedBy(token0DerivedUSD).div(10 ** decimal0).toFixed(4); + let token1UsdValue = BigNumber(token1AmountsInWei.toString()).multipliedBy(token1DerivedUSD).div(10 ** decimal1).toFixed(4); + + + return { ...position, token0USDValue: token0UsdValue, token1USDValue: token1UsdValue, token0AmountsInWei, token1AmountsInWei, token0DecimalValue, token1DecimalValue }; + +} + +export const getLPValueByUserAndPoolFromPositions = ( + positions: Position[] +): Map> => { + let result = new Map>(); + for (let i = 0; i < positions.length; i++) { + let position = positions[i]; + let poolId = position.pool.id; + let owner = position.owner; + let userPositions = result.get(owner); + if (userPositions === undefined) { + userPositions = new Map(); + result.set(owner, userPositions); + } + let poolPositions = userPositions.get(poolId); + if (poolPositions === undefined) { + poolPositions = BigNumber(0); + } + let positionWithUSDValue = getPositionDetailsFromPosition(position); + poolPositions = poolPositions.plus(BigNumber(positionWithUSDValue.token0USDValue).plus(positionWithUSDValue.token1USDValue)); + userPositions.set(poolId, poolPositions); + } + return result; +} + +export const getTimestampAtBlock = async (blockNumber: number) => { + const publicClient = createPublicClient({ + chain: extractChain({ chains: [linea], id: CHAINS.L2_CHAIN_ID }), + transport: http(RPC_URLS[CHAINS.L2_CHAIN_ID]), + }); + + const block = await publicClient.getBlock({ + blockNumber: BigInt(blockNumber), + }); + return Number(block.timestamp * 1000n); +}; diff --git a/adapters/metavault/src/sdk/utils/constant.ts b/adapters/metavault/src/sdk/utils/constant.ts new file mode 100644 index 00000000..375569d6 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/constant.ts @@ -0,0 +1,38 @@ +// exports for external consumption +export type BigintIsh = bigint | number | string + +export enum TradeType { + EXACT_INPUT, + EXACT_OUTPUT, +} + +export enum Rounding { + ROUND_DOWN, + ROUND_HALF_UP, + ROUND_UP, +} + +export const MINIMUM_LIQUIDITY = 1000n + +// exports for internal consumption +export const ZERO = 0n +export const ONE = 1n +export const TWO = 2n +export const THREE = 3n +export const FIVE = 5n +export const TEN = 10n +export const _100 = 100n +export const _9975 = 9975n +export const _10000 = 10000n + +export const MaxUint256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') + +export enum VMType { + uint8 = 'uint8', + uint256 = 'uint256', +} + +export const VM_TYPE_MAXIMA = { + [VMType.uint8]: BigInt('0xff'), + [VMType.uint256]: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), +} diff --git a/adapters/metavault/src/sdk/utils/fractions/fraction.ts b/adapters/metavault/src/sdk/utils/fractions/fraction.ts new file mode 100644 index 00000000..3970dd43 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/fractions/fraction.ts @@ -0,0 +1,159 @@ +import invariant from 'tiny-invariant' +import _Decimal from 'decimal.js-light' +import _Big from 'big.js' +// @ts-ignore +import toFormat from 'toformat' + +import { BigintIsh, Rounding } from '../constant' + +const Decimal = toFormat(_Decimal) +const Big = toFormat(_Big) + +const toSignificantRounding = { + [Rounding.ROUND_DOWN]: Decimal.ROUND_DOWN, + [Rounding.ROUND_HALF_UP]: Decimal.ROUND_HALF_UP, + [Rounding.ROUND_UP]: Decimal.ROUND_UP, +} + +const enum RoundingL2_CHAIN_ID { + /** + * Rounds towards zero. + * I.e. truncate, no rounding. + */ + RoundDown = 0, + /** + * Rounds towards nearest neighbour. + * If equidistant, rounds away from zero. + */ + RoundHalfUp = 1, + /** + * Rounds towards nearest neighbour. + * If equidistant, rounds towards even neighbour. + */ + RoundHalfEven = 2, + /** + * Rounds away from zero. + */ + RoundUp = 3, +} + +const toFixedRounding = { + [Rounding.ROUND_DOWN]: RoundingL2_CHAIN_ID.RoundDown, + [Rounding.ROUND_HALF_UP]: RoundingL2_CHAIN_ID.RoundHalfUp, + [Rounding.ROUND_UP]: RoundingL2_CHAIN_ID.RoundUp, +} + +export class Fraction { + public readonly numerator: bigint + + public readonly denominator: bigint + + public constructor(numerator: BigintIsh, denominator: BigintIsh = 1n) { + this.numerator = BigInt(numerator) + this.denominator = BigInt(denominator) + } + + private static tryParseFraction(fractionish: BigintIsh | Fraction): Fraction { + if (typeof fractionish === 'bigint' || typeof fractionish === 'number' || typeof fractionish === 'string') + return new Fraction(fractionish) + + if ('numerator' in fractionish && 'denominator' in fractionish) return fractionish + throw new Error('Could not parse fraction') + } + + // performs floor division + public get quotient(): bigint { + return this.numerator / this.denominator + } + + // remainder after floor division + public get remainder(): Fraction { + return new Fraction(this.numerator % this.denominator, this.denominator) + } + + public invert(): Fraction { + return new Fraction(this.denominator, this.numerator) + } + + public add(other: Fraction | BigintIsh): Fraction { + const otherParsed = Fraction.tryParseFraction(other) + if (this.denominator === otherParsed.denominator) { + return new Fraction(this.numerator + otherParsed.numerator, this.denominator) + } + return new Fraction( + this.numerator * otherParsed.denominator + otherParsed.numerator * this.denominator, + this.denominator * otherParsed.denominator + ) + } + + public subtract(other: Fraction | BigintIsh): Fraction { + const otherParsed = Fraction.tryParseFraction(other) + if (this.denominator === otherParsed.denominator) { + return new Fraction(this.numerator - otherParsed.numerator, this.denominator) + } + return new Fraction( + this.numerator * otherParsed.denominator - otherParsed.numerator * this.denominator, + this.denominator * otherParsed.denominator + ) + } + + public lessThan(other: Fraction | BigintIsh): boolean { + const otherParsed = Fraction.tryParseFraction(other) + return this.numerator * otherParsed.denominator < otherParsed.numerator * this.denominator + } + + public equalTo(other: Fraction | BigintIsh): boolean { + const otherParsed = Fraction.tryParseFraction(other) + return this.numerator * otherParsed.denominator === otherParsed.numerator * this.denominator + } + + public greaterThan(other: Fraction | BigintIsh): boolean { + const otherParsed = Fraction.tryParseFraction(other) + return this.numerator * otherParsed.denominator > otherParsed.numerator * this.denominator + } + + public multiply(other: Fraction | BigintIsh): Fraction { + const otherParsed = Fraction.tryParseFraction(other) + return new Fraction(this.numerator * otherParsed.numerator, this.denominator * otherParsed.denominator) + } + + public divide(other: Fraction | BigintIsh): Fraction { + const otherParsed = Fraction.tryParseFraction(other) + return new Fraction(this.numerator * otherParsed.denominator, this.denominator * otherParsed.numerator) + } + + public toSignificant( + significantDigits: number, + format: object = { groupSeparator: '' }, + rounding: Rounding = Rounding.ROUND_HALF_UP + ): string { + invariant(Number.isInteger(significantDigits), `${significantDigits} is not an integer.`) + invariant(significantDigits > 0, `${significantDigits} is not positive.`) + + Decimal.set({ precision: significantDigits + 1, rounding: toSignificantRounding[rounding] }) + const quotient = new Decimal(this.numerator.toString()) + .div(this.denominator.toString()) + .toSignificantDigits(significantDigits) + return quotient.toFormat(quotient.decimalPlaces(), format) + } + + public toFixed( + decimalPlaces: number, + format: object = { groupSeparator: '' }, + rounding: Rounding = Rounding.ROUND_HALF_UP + ): string { + invariant(Number.isInteger(decimalPlaces), `${decimalPlaces} is not an integer.`) + invariant(decimalPlaces >= 0, `${decimalPlaces} is negative.`) + + Big.DP = decimalPlaces + Big.RM = toFixedRounding[rounding] + return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(decimalPlaces, format) + } + + /** + * Helper method for converting any super class back to a fraction + */ + public get asFraction(): Fraction { + return new Fraction(this.numerator, this.denominator) + } +} diff --git a/adapters/metavault/src/sdk/utils/fractions/percent.ts b/adapters/metavault/src/sdk/utils/fractions/percent.ts new file mode 100644 index 00000000..5fcb5d72 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/fractions/percent.ts @@ -0,0 +1,43 @@ +import { BigintIsh, Rounding } from '../constant' +import { Fraction } from './fraction' + +const ONE_HUNDRED = new Fraction(100n) + +/** + * Converts a fraction to a percent + * @param fraction the fraction to convert + */ +function toPercent(fraction: Fraction): Percent { + return new Percent(fraction.numerator, fraction.denominator) +} + +export class Percent extends Fraction { + /** + * This boolean prevents a fraction from being interpreted as a Percent + */ + public readonly isPercent = true as const + + add(other: Fraction | BigintIsh): Percent { + return toPercent(super.add(other)) + } + + subtract(other: Fraction | BigintIsh): Percent { + return toPercent(super.subtract(other)) + } + + multiply(other: Fraction | BigintIsh): Percent { + return toPercent(super.multiply(other)) + } + + divide(other: Fraction | BigintIsh): Percent { + return toPercent(super.divide(other)) + } + + public toSignificant(significantDigits = 5, format?: object, rounding?: Rounding): string { + return super.multiply(ONE_HUNDRED).toSignificant(significantDigits, format, rounding) + } + + public toFixed(decimalPlaces = 2, format?: object, rounding?: Rounding): string { + return super.multiply(ONE_HUNDRED).toFixed(decimalPlaces, format, rounding) + } +} diff --git a/adapters/metavault/src/sdk/utils/fullMath.ts b/adapters/metavault/src/sdk/utils/fullMath.ts new file mode 100644 index 00000000..fe84c76b --- /dev/null +++ b/adapters/metavault/src/sdk/utils/fullMath.ts @@ -0,0 +1,16 @@ +import { ONE, ZERO } from "./internalConstants" + +export abstract class FullMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static mulDivRoundingUp(a: bigint, b: bigint, denominator: bigint): bigint { + const product = a * b + let result = product / denominator + // eslint-disable-next-line operator-assignment + if (product % denominator !== ZERO) result = result + ONE + return result + } +} diff --git a/adapters/metavault/src/sdk/utils/internalConstants.ts b/adapters/metavault/src/sdk/utils/internalConstants.ts new file mode 100644 index 00000000..a9fc9a73 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/internalConstants.ts @@ -0,0 +1,16 @@ +import { Percent } from "./fractions/percent" + +// constants used internally but not expected to be used externally +export const NEGATIVE_ONE = BigInt(-1) +export const ZERO = 0n +export const ONE = 1n + +// used in liquidity amount math +export const Q96 = 2n ** 96n +export const Q192 = Q96 ** 2n + +// used in fee calculation +export const MAX_FEE = 10n ** 6n +export const ONE_HUNDRED_PERCENT = new Percent('1') +export const ZERO_PERCENT = new Percent('0') +export const Q128 = 2n ** 128n diff --git a/adapters/metavault/src/sdk/utils/mostSignificantBit.ts b/adapters/metavault/src/sdk/utils/mostSignificantBit.ts new file mode 100644 index 00000000..c1cb4c31 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/mostSignificantBit.ts @@ -0,0 +1,20 @@ +import invariant from 'tiny-invariant' +import { MaxUint256, ZERO } from './constant' + +const TWO = 2n +const POWERS_OF_2 = [128, 64, 32, 16, 8, 4, 2, 1].map((pow: number): [number, bigint] => [pow, TWO ** BigInt(pow)]) + +export function mostSignificantBit(x: bigint): number { + invariant(x > ZERO, 'ZERO') + invariant(x <= MaxUint256, 'MAX') + + let msb = 0 + for (const [power, min] of POWERS_OF_2) { + if (x >= min) { + // eslint-disable-next-line operator-assignment + x = x >> BigInt(power) + msb += power + } + } + return msb +} diff --git a/adapters/metavault/src/sdk/utils/positionMath.ts b/adapters/metavault/src/sdk/utils/positionMath.ts new file mode 100644 index 00000000..d20f2b90 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/positionMath.ts @@ -0,0 +1,50 @@ +import { ZERO } from "./constant" +import { SqrtPriceMath } from "./squarePriceMath" +import { TickMath } from "./tickMath" + +function getToken0Amount( + tickCurrent: number, + tickLower: number, + tickUpper: number, + sqrtRatioX96: bigint, + liquidity: bigint +): bigint { + if (tickCurrent < tickLower) { + return SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(tickLower), + TickMath.getSqrtRatioAtTick(tickUpper), + liquidity, + false + ) + } + if (tickCurrent < tickUpper) { + return SqrtPriceMath.getAmount0Delta(sqrtRatioX96, TickMath.getSqrtRatioAtTick(tickUpper), liquidity, false) + } + return ZERO +} + +function getToken1Amount( + tickCurrent: number, + tickLower: number, + tickUpper: number, + sqrtRatioX96: bigint, + liquidity: bigint +): bigint { + if (tickCurrent < tickLower) { + return ZERO + } + if (tickCurrent < tickUpper) { + return SqrtPriceMath.getAmount1Delta(TickMath.getSqrtRatioAtTick(tickLower), sqrtRatioX96, liquidity, false) + } + return SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(tickLower), + TickMath.getSqrtRatioAtTick(tickUpper), + liquidity, + false + ) +} + +export const PositionMath = { + getToken0Amount, + getToken1Amount, +} diff --git a/adapters/metavault/src/sdk/utils/squarePriceMath.ts b/adapters/metavault/src/sdk/utils/squarePriceMath.ts new file mode 100644 index 00000000..02639c62 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/squarePriceMath.ts @@ -0,0 +1,131 @@ +import invariant from "tiny-invariant" +import { MaxUint256, ONE, ZERO } from "./constant" +import { FullMath } from "./fullMath" +import { Q96 } from "./internalConstants" + +const MaxUint160 = 2n ** 160n - ONE + +function multiplyIn256(x: bigint, y: bigint): bigint { + const product = x * y + return product & MaxUint256 +} + +function addIn256(x: bigint, y: bigint): bigint { + const sum = x + y + return sum & MaxUint256 +} + +export abstract class SqrtPriceMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static getAmount0Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean + ): bigint { + if (sqrtRatioAX96 > sqrtRatioBX96) { + sqrtRatioAX96 = sqrtRatioBX96 + sqrtRatioBX96 = sqrtRatioAX96 + } + + const numerator1 = liquidity << 96n + const numerator2 = sqrtRatioBX96 - sqrtRatioAX96 + + return roundUp + ? FullMath.mulDivRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), ONE, sqrtRatioAX96) + : (numerator1 * numerator2) / sqrtRatioBX96 / sqrtRatioAX96 + } + + public static getAmount1Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean + ): bigint { + if (sqrtRatioAX96 > sqrtRatioBX96) { + sqrtRatioAX96 = sqrtRatioBX96 + sqrtRatioBX96 = sqrtRatioAX96 + } + + return roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Q96) + : (liquidity * (sqrtRatioBX96 - sqrtRatioAX96)) / Q96 + } + + public static getNextSqrtPriceFromInput( + sqrtPX96: bigint, + liquidity: bigint, + amountIn: bigint, + zeroForOne: boolean + ): bigint { + invariant(sqrtPX96 > ZERO) + invariant(liquidity > ZERO) + + return zeroForOne + ? this.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : this.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) + } + + public static getNextSqrtPriceFromOutput( + sqrtPX96: bigint, + liquidity: bigint, + amountOut: bigint, + zeroForOne: boolean + ): bigint { + invariant(sqrtPX96 > ZERO) + invariant(liquidity > ZERO) + + return zeroForOne + ? this.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : this.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) + } + + private static getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean + ): bigint { + if (amount === ZERO) return sqrtPX96 + const numerator1 = liquidity << 96n + + if (add) { + const product = multiplyIn256(amount, sqrtPX96) + if (product / amount === sqrtPX96) { + const denominator = addIn256(numerator1, product) + if (denominator >= numerator1) { + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator) + } + } + + return FullMath.mulDivRoundingUp(numerator1, ONE, numerator1 / sqrtPX96 + amount) + } + const product = multiplyIn256(amount, sqrtPX96) + + invariant(product / amount === sqrtPX96) + invariant(numerator1 > product) + const denominator = numerator1 - product + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator) + } + + private static getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean + ): bigint { + if (add) { + const quotient = amount <= MaxUint160 ? (amount << 96n) / liquidity : (amount * Q96) / liquidity + + return sqrtPX96 + quotient + } + const quotient = FullMath.mulDivRoundingUp(amount, Q96, liquidity) + + invariant(sqrtPX96 > quotient) + return sqrtPX96 - quotient + } +} diff --git a/adapters/metavault/src/sdk/utils/tickLibrary.ts b/adapters/metavault/src/sdk/utils/tickLibrary.ts new file mode 100644 index 00000000..7d59c95e --- /dev/null +++ b/adapters/metavault/src/sdk/utils/tickLibrary.ts @@ -0,0 +1,59 @@ +import { ZERO } from "./internalConstants" + +interface FeeGrowthOutside { + feeGrowthOutside0X128: bigint + feeGrowthOutside1X128: bigint +} + +const Q256 = 2n ** 256n + +export function subIn256(x: bigint, y: bigint): bigint { + const difference = x - y + + if (difference < ZERO) { + return Q256 + difference + } + return difference +} + +export abstract class TickLibrary { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static getFeeGrowthInside( + feeGrowthOutsideLower: FeeGrowthOutside, + feeGrowthOutsideUpper: FeeGrowthOutside, + tickLower: number, + tickUpper: number, + tickCurrent: number, + feeGrowthGlobal0X128: bigint, + feeGrowthGlobal1X128: bigint + ) { + let feeGrowthBelow0X128: bigint + let feeGrowthBelow1X128: bigint + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = feeGrowthOutsideLower.feeGrowthOutside0X128 + feeGrowthBelow1X128 = feeGrowthOutsideLower.feeGrowthOutside1X128 + } else { + feeGrowthBelow0X128 = subIn256(feeGrowthGlobal0X128, feeGrowthOutsideLower.feeGrowthOutside0X128) + feeGrowthBelow1X128 = subIn256(feeGrowthGlobal1X128, feeGrowthOutsideLower.feeGrowthOutside1X128) + } + + let feeGrowthAbove0X128: bigint + let feeGrowthAbove1X128: bigint + if (tickCurrent < tickUpper) { + feeGrowthAbove0X128 = feeGrowthOutsideUpper.feeGrowthOutside0X128 + feeGrowthAbove1X128 = feeGrowthOutsideUpper.feeGrowthOutside1X128 + } else { + feeGrowthAbove0X128 = subIn256(feeGrowthGlobal0X128, feeGrowthOutsideUpper.feeGrowthOutside0X128) + feeGrowthAbove1X128 = subIn256(feeGrowthGlobal1X128, feeGrowthOutsideUpper.feeGrowthOutside1X128) + } + + return [ + subIn256(subIn256(feeGrowthGlobal0X128, feeGrowthBelow0X128), feeGrowthAbove0X128), + subIn256(subIn256(feeGrowthGlobal1X128, feeGrowthBelow1X128), feeGrowthAbove1X128), + ] + } +} diff --git a/adapters/metavault/src/sdk/utils/tickMath.ts b/adapters/metavault/src/sdk/utils/tickMath.ts new file mode 100644 index 00000000..18a01d96 --- /dev/null +++ b/adapters/metavault/src/sdk/utils/tickMath.ts @@ -0,0 +1,115 @@ +import invariant from "tiny-invariant" +import { MaxUint256, ONE, ZERO } from "./constant" +import { mostSignificantBit } from "./mostSignificantBit" + + +function mulShift(val: bigint, mulBy: string): bigint { + return (val * BigInt(mulBy)) >> 128n +} + +const Q32 = 2n ** 32n + +export abstract class TickMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + /** + * The minimum tick that can be used on any pool. + */ + // eslint-disable-next-line @typescript-eslint/no-inferrable-types + public static MIN_TICK: number = -887272 + + /** + * The maximum tick that can be used on any pool. + */ + public static MAX_TICK: number = -TickMath.MIN_TICK + + /** + * The sqrt ratio corresponding to the minimum tick that could be used on any pool. + */ + public static MIN_SQRT_RATIO = 4295128739n + + /** + * The sqrt ratio corresponding to the maximum tick that could be used on any pool. + */ + public static MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n + + /** + * Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as sqrt(1.0001)^tick + * @param tick the tick for which to compute the sqrt ratio + */ + public static getSqrtRatioAtTick(tick: number): bigint { + + invariant(tick >= TickMath.MIN_TICK && tick <= TickMath.MAX_TICK && Number.isInteger(tick), 'TICK') + const absTick: number = tick < 0 ? tick * -1 : tick + + let ratio: bigint = + (absTick & 0x1) != 0 + ? BigInt('0xfffcb933bd6fad37aa2d162d1a594001') + : BigInt('0x100000000000000000000000000000000') + if ((absTick & 0x2) != 0) ratio = mulShift(ratio, '0xfff97272373d413259a46990580e213a') + if ((absTick & 0x4) != 0) ratio = mulShift(ratio, '0xfff2e50f5f656932ef12357cf3c7fdcc') + if ((absTick & 0x8) != 0) ratio = mulShift(ratio, '0xffe5caca7e10e4e61c3624eaa0941cd0') + if ((absTick & 0x10) != 0) ratio = mulShift(ratio, '0xffcb9843d60f6159c9db58835c926644') + if ((absTick & 0x20) != 0) ratio = mulShift(ratio, '0xff973b41fa98c081472e6896dfb254c0') + if ((absTick & 0x40) != 0) ratio = mulShift(ratio, '0xff2ea16466c96a3843ec78b326b52861') + if ((absTick & 0x80) != 0) ratio = mulShift(ratio, '0xfe5dee046a99a2a811c461f1969c3053') + if ((absTick & 0x100) != 0) ratio = mulShift(ratio, '0xfcbe86c7900a88aedcffc83b479aa3a4') + if ((absTick & 0x200) != 0) ratio = mulShift(ratio, '0xf987a7253ac413176f2b074cf7815e54') + if ((absTick & 0x400) != 0) ratio = mulShift(ratio, '0xf3392b0822b70005940c7a398e4b70f3') + if ((absTick & 0x800) != 0) ratio = mulShift(ratio, '0xe7159475a2c29b7443b29c7fa6e889d9') + if ((absTick & 0x1000) != 0) ratio = mulShift(ratio, '0xd097f3bdfd2022b8845ad8f792aa5825') + if ((absTick & 0x2000) != 0) ratio = mulShift(ratio, '0xa9f746462d870fdf8a65dc1f90e061e5') + if ((absTick & 0x4000) != 0) ratio = mulShift(ratio, '0x70d869a156d2a1b890bb3df62baf32f7') + if ((absTick & 0x8000) != 0) ratio = mulShift(ratio, '0x31be135f97d08fd981231505542fcfa6') + if ((absTick & 0x10000) != 0) ratio = mulShift(ratio, '0x9aa508b5b7a84e1c677de54f3e99bc9') + if ((absTick & 0x20000) != 0) ratio = mulShift(ratio, '0x5d6af8dedb81196699c329225ee604') + if ((absTick & 0x40000) != 0) ratio = mulShift(ratio, '0x2216e584f5fa1ea926041bedfe98') + if ((absTick & 0x80000) != 0) ratio = mulShift(ratio, '0x48a170391f7dc42444e8fa2') + + if (tick > 0) ratio = MaxUint256 / ratio + + // back to Q96 + return ratio % Q32 > ZERO ? ratio / Q32 + ONE : ratio / Q32 + } + + /** + * Returns the tick corresponding to a given sqrt ratio, s.t. #getSqrtRatioAtTick(tick) <= sqrtRatioX96 + * and #getSqrtRatioAtTick(tick + 1) > sqrtRatioX96 + * @param sqrtRatioX96 the sqrt ratio as a Q64.96 for which to compute the tick + */ + public static getTickAtSqrtRatio(sqrtRatioX96: bigint): number { + invariant(sqrtRatioX96 >= TickMath.MIN_SQRT_RATIO && sqrtRatioX96 < TickMath.MAX_SQRT_RATIO, 'SQRT_RATIO') + + const sqrtRatioX128 = sqrtRatioX96 << 32n + + const msb = mostSignificantBit(sqrtRatioX128) + + let r: bigint + if (BigInt(msb) >= 128n) { + r = sqrtRatioX128 >> BigInt(msb - 127) + } else { + r = sqrtRatioX128 << BigInt(127 - msb) + } + + let log_2: bigint = (BigInt(msb) - 128n) << 64n + + for (let i = 0; i < 14; i++) { + r = (r * r) >> 127n + const f = r >> 128n + // eslint-disable-next-line operator-assignment + log_2 = log_2 | (f << BigInt(63 - i)) + // eslint-disable-next-line operator-assignment + r = r >> f + } + + const log_sqrt10001 = log_2 * 255738958999603826347141n + + const tickLow = Number((log_sqrt10001 - 3402992956809132418596140100660247210n) >> 128n) + const tickHigh = Number((log_sqrt10001 + 291339464771989622907027621153398088495n) >> 128n) + + return tickLow === tickHigh ? tickLow : TickMath.getSqrtRatioAtTick(tickHigh) <= sqrtRatioX96 ? tickHigh : tickLow + } +} diff --git a/adapters/metavault/tsconfig.json b/adapters/metavault/tsconfig.json new file mode 100644 index 00000000..9852ff27 --- /dev/null +++ b/adapters/metavault/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + "rootDir": "src/" /* Specify the root folder within your source files. */, + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "dist/" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} From 2ec4e4e794d2629655926f1b4a9c317f2ab627fb Mon Sep 17 00:00:00 2001 From: sdcrypt0 <> Date: Tue, 26 Mar 2024 11:53:27 +0300 Subject: [PATCH 2/3] subgraph url update --- adapters/metavault/src/sdk/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/metavault/src/sdk/config.ts b/adapters/metavault/src/sdk/config.ts index 62cbe677..9fdbfc9f 100644 --- a/adapters/metavault/src/sdk/config.ts +++ b/adapters/metavault/src/sdk/config.ts @@ -14,7 +14,7 @@ export const SUBGRAPH_URLS = { [CHAINS.L2_CHAIN_ID]: { [PROTOCOLS.METAVAULT]: { [AMM_TYPES.UNISWAPV3]: "https://api.studio.thegraph.com/query/55804/linea-v3/version/latest", - [AMM_TYPES.TRADE]: "http://localhost:8000/subgraphs/name/metavault/perpv1" + [AMM_TYPES.TRADE]: "https://api.studio.thegraph.com/query/55804/linea-trade/version/latest" } }, From ad8c891e5e6e5d2b876c94b530253e828d717aad Mon Sep 17 00:00:00 2001 From: sdcrypt0 <> Date: Mon, 22 Apr 2024 13:28:45 +0300 Subject: [PATCH 3/3] metavault update --- adapters/metavault/src/index.ts | 286 ++++++++------------------------ 1 file changed, 65 insertions(+), 221 deletions(-) diff --git a/adapters/metavault/src/index.ts b/adapters/metavault/src/index.ts index 22073aa7..21de8724 100644 --- a/adapters/metavault/src/index.ts +++ b/adapters/metavault/src/index.ts @@ -1,247 +1,91 @@ -import BigNumber from "bignumber.js"; import { CHAINS, PROTOCOLS, AMM_TYPES } from "./sdk/config"; -import { getLPValueByUserAndPoolFromPositions, getPositionAtBlock, getPositionDetailsFromPosition, getPositionsForAddressByPoolAtBlock, getTimestampAtBlock, getTradeLiquidityForAddressByPoolAtBlock } from "./sdk/subgraphDetails"; +import { getPositionDetailsFromPosition, getPositionsForAddressByPoolAtBlock, getTimestampAtBlock, getTradeLiquidityForAddressByPoolAtBlock } from "./sdk/subgraphDetails"; (BigInt.prototype as any).toJSON = function () { return this.toString(); }; -import { promisify } from 'util'; -import stream from 'stream'; -import csv from 'csv-parser'; -import fs from 'fs'; -import { format } from 'fast-csv'; -import { write } from 'fast-csv'; -import { pipeline as streamPipeline } from 'stream'; -import { captureRejectionSymbol } from "events"; -import { readBlocksFromApi } from "./sdk/blockApi"; - - -//Uncomment the following lines to test the getPositionAtBlock function - -// const position = getPositionAtBlock( -// 0, // block number 0 for latest block -// 2, // position id -// CHAINS.L2_CHAIN_ID, // chain id -// PROTOCOLS.PROTOCOL_NAME, // protocol -// AMM_TYPES.UNISWAPV3 // amm type -// ); -// position.then((position) => { -// // print response -// const result = getPositionDetailsFromPosition(position); -// console.log(`${JSON.stringify(result,null, 4)} -// `) -// }); - -interface LPValueDetails { - pool: string; - lpValue: string; -} - -interface UserLPData { - totalLP: string; - pools: LPValueDetails[]; -} - -// Define an object type that can be indexed with string keys, where each key points to a UserLPData object -interface OutputData { - [key: string]: UserLPData; -} - -interface CSVRow { - user: string; - pool: string; - block: number; - position: number; - lpvalue: string; -} - - -const pipeline = promisify(stream.pipeline); - -// Assuming you have the following functions and constants already defined -// getPositionsForAddressByPoolAtBlock, CHAINS, PROTOCOLS, AMM_TYPES, getPositionDetailsFromPosition, getLPValueByUserAndPoolFromPositions, BigNumber - -// const readBlocksFromCSV = async (filePath: string): Promise => { -// 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 snapshotBlocks = [ -// 3116208, 3159408, 3202608, 3245808, 3289008, 3332208, -// 3375408, 3418608, 3461808, 3505008, 3548208, 3591408, -// 3634608, 3677808, 3721008, 3764208, 3807408, 3850608, -// 3893808, 3937008, 3980208, 3983003, -// ]; //await readBlocksFromCSV('src/sdk/L2_CHAIN_ID_chain_daily_blocks.csv'); - -// const csvRows: CSVRow[] = []; - -// for (let block of snapshotBlocks) { -// const positions = await getPositionsForAddressByPoolAtBlock( -// block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.PROTOCOL_NAME, AMM_TYPES.UNISWAPV3 -// ); - -// console.log(`Block: ${block}`); -// console.log("Positions: ", positions.length); - -// // Assuming this part of the logic remains the same -// let positionsWithUSDValue = positions.map(getPositionDetailsFromPosition); -// let lpValueByUsers = getLPValueByUserAndPoolFromPositions(positionsWithUSDValue); - -// lpValueByUsers.forEach((value, key) => { -// let positionIndex = 0; // Define how you track position index -// value.forEach((lpValue, poolKey) => { -// const lpValueStr = lpValue.toString(); -// // Accumulate CSV row data -// csvRows.push({ -// user: key, -// pool: poolKey, -// block, -// position: positions.length, // Adjust if you have a specific way to identify positions -// lpvalue: lpValueStr, -// }); -// }); -// }); -// } - -// // 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."); -// }); -// }; - - - - interface BlockData { blockNumber: number; blockTimestamp: number; } - -const readBlocksFromCSV = async (filePath: string): Promise => { - const blocks: BlockData[] = []; - - await new Promise((resolve, reject) => { - fs.createReadStream(filePath) - .pipe(csv({ separator: '\t' })) // Specify the separator as '\t' for TSV files - .on('data', (row) => { - const blockNumber = parseInt(row.number, 10); - const blockTimestamp = parseInt(row.block_timestamp, 10); - if (!isNaN(blockNumber) && blockTimestamp) { - blocks.push({ blockNumber: blockNumber, blockTimestamp }); - } - }) - .on('end', () => { - resolve(); - }) - .on('error', (err) => { - reject(err); - }); - }); - - return blocks; -}; - type OutputDataSchemaRow = { block_number: number; timestamp: number; user_address: string; token_address: string; - token_balance: bigint; - token_symbol: string; + token_balance: number; + token_symbol?: string; + usd_price: number; }; -async function main() { - const snapshotBlocks = [3137407, 3138307]; - - // readBlocksFromApi(1711324800, 1711411200).then(async (blocks) => { // uncomment for block api - (async () => snapshotBlocks)().then(async (blocks) => { - console.log(blocks); - const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks - const batchSize = 10; // Size of batch to trigger writing to the file - let i = 0; - for (const block of blocks) { - try { - const timestamp = new Date(await getTimestampAtBlock(block)).toISOString(); - const positions = await getPositionsForAddressByPoolAtBlock( - block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.UNISWAPV3 - ); - console.log(`Block: ${block}`); - console.log("Positions: ", positions.length); - - // Assuming this part of the logic remains the same - let positionsWithUSDValue = positions.map(getPositionDetailsFromPosition); - // let lpValueByUsers = getLPValueByUserAndPoolFromPositions(positionsWithUSDValue); - - positionsWithUSDValue.forEach((value, key) => { - - // Accumulate CSV row data - allCsvRows.push({ - block_number: block, - timestamp, - user: value.owner, - token_address: value.token0.id, - token_balance: value.token0DecimalValue, - token_balance_usd: value.token0USDValue, - }); +export const getUserTVLByBlock = async ({ + blockNumber, + blockTimestamp, +}: BlockData): Promise => { + return await getPoolData({ blockNumber, blockTimestamp }); +} - allCsvRows.push({ - block_number: block, - timestamp, - user: value.owner, - token_address: value.token1.id, - token_balance: value.token1DecimalValue, - token_balance_usd: value.token1USDValue, - }); +export const getPoolData = async ({ + blockNumber, + blockTimestamp, +}: BlockData): Promise => { + const allCsvRows: OutputDataSchemaRow[] = []; // Array to accumulate CSV rows for all blocks + try { + // const blockTimestamp = new Date(await getTimestampAtBlock(blockNumber)).toISOString(); + const positions = await getPositionsForAddressByPoolAtBlock( + blockNumber, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.UNISWAPV3 + ); + + console.log(`Block: ${blockNumber}`); + console.log("Positions: ", positions.length); + + // Assuming this part of the logic remains the same + let positionsWithUSDValue = positions.map(getPositionDetailsFromPosition); + // let lpValueByUsers = getLPValueByUserAndPoolFromPositions(positionsWithUSDValue); + + positionsWithUSDValue.forEach((value, key) => { + // Accumulate CSV row data + if (value.token0DecimalValue > 0) { + allCsvRows.push({ + block_number: blockNumber, + timestamp: blockTimestamp, + user_address: value.owner, + token_address: value.token0.id, + token_balance: value.token0DecimalValue, + usd_price: 0, }); - const liquidities = await getTradeLiquidityForAddressByPoolAtBlock( - block, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.TRADE - ); - liquidities.forEach((value, key) => { - allCsvRows.push({ - block_number: block, - timestamp, - user: value.user, - token_address: value.asset, - token_balance: value.amount, - token_balance_usd: value.amountUsd, - }); + } + if (value.token1DecimalValue > 0) { + allCsvRows.push({ + block_number: blockNumber, + timestamp: blockTimestamp, + user_address: value.owner, + token_address: value.token1.id, + token_balance: value.token1DecimalValue, + usd_price: 0, }); - - // Write the CSV output to a file - const ws = fs.createWriteStream('outputData.csv'); - write(allCsvRows, { headers: true }).pipe(ws).on('finish', () => { - console.log("CSV file has been written."); + } + }); + const liquidities = await getTradeLiquidityForAddressByPoolAtBlock( + blockNumber, "", "", CHAINS.L2_CHAIN_ID, PROTOCOLS.METAVAULT, AMM_TYPES.TRADE + ); + liquidities.forEach((value, key) => { + if (value.amount > 0) { + allCsvRows.push({ + block_number: blockNumber, + timestamp: blockTimestamp, + user_address: value.user, + token_address: value.asset, + token_balance: value.amount, + usd_price: 0, }); - } catch (error) { - console.error(`An error occurred for block ${block}:`, error); } - - } - }) - .catch((err) => { - console.error('Error reading CSV file:', err); }); - + } catch (error) { + console.error(`An error occurred for block ${blockNumber}:`, error); + } + return allCsvRows } -main().then(() => { - console.log("Done"); -}); -// getPrice(new BigNumber('1579427897588720602142863095414958'), 6, 18); //Uniswap -// getPrice(new BigNumber('3968729022398277600000000'), 18, 6); //PROTOCOL_NAME -