From 806fa4e29a7a5eeeca6df240932a62b18823ac8d Mon Sep 17 00:00:00 2001 From: Jac <39693487+lijac@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:11:20 -0400 Subject: [PATCH 1/3] feat: add secta adapter --- adapters/secta/package.json | 33 + adapters/secta/src/index.ts | 287 +++++++ adapters/secta/src/sdk/abis.ts | 713 ++++++++++++++++++ adapters/secta/src/sdk/config.ts | 25 + adapters/secta/src/sdk/entities/positions.ts | 24 + adapters/secta/src/sdk/liquidityTypes.ts | 76 ++ adapters/secta/src/sdk/mathUtils.ts | 37 + adapters/secta/src/sdk/poolDetails.ts | 70 ++ adapters/secta/src/sdk/positionDetails.ts | 51 ++ adapters/secta/src/sdk/subgraphDetails.ts | 411 ++++++++++ adapters/secta/src/sdk/utils/constant.ts | 38 + .../secta/src/sdk/utils/fractions/fraction.js | 107 +++ .../secta/src/sdk/utils/fractions/fraction.ts | 159 ++++ .../secta/src/sdk/utils/fractions/percent.js | 37 + .../secta/src/sdk/utils/fractions/percent.ts | 43 ++ adapters/secta/src/sdk/utils/fullMath.ts | 16 + .../secta/src/sdk/utils/internalConstants.ts | 16 + .../secta/src/sdk/utils/mostSignificantBit.ts | 20 + adapters/secta/src/sdk/utils/positionMath.ts | 50 ++ .../secta/src/sdk/utils/squarePriceMath.ts | 131 ++++ adapters/secta/src/sdk/utils/tickLibrary.ts | 59 ++ adapters/secta/src/sdk/utils/tickMath.ts | 115 +++ adapters/secta/tsconfig.json | 101 +++ 23 files changed, 2619 insertions(+) create mode 100644 adapters/secta/package.json create mode 100644 adapters/secta/src/index.ts create mode 100644 adapters/secta/src/sdk/abis.ts create mode 100644 adapters/secta/src/sdk/config.ts create mode 100644 adapters/secta/src/sdk/entities/positions.ts create mode 100644 adapters/secta/src/sdk/liquidityTypes.ts create mode 100644 adapters/secta/src/sdk/mathUtils.ts create mode 100644 adapters/secta/src/sdk/poolDetails.ts create mode 100644 adapters/secta/src/sdk/positionDetails.ts create mode 100644 adapters/secta/src/sdk/subgraphDetails.ts create mode 100644 adapters/secta/src/sdk/utils/constant.ts create mode 100644 adapters/secta/src/sdk/utils/fractions/fraction.js create mode 100644 adapters/secta/src/sdk/utils/fractions/fraction.ts create mode 100644 adapters/secta/src/sdk/utils/fractions/percent.js create mode 100644 adapters/secta/src/sdk/utils/fractions/percent.ts create mode 100644 adapters/secta/src/sdk/utils/fullMath.ts create mode 100644 adapters/secta/src/sdk/utils/internalConstants.ts create mode 100644 adapters/secta/src/sdk/utils/mostSignificantBit.ts create mode 100644 adapters/secta/src/sdk/utils/positionMath.ts create mode 100644 adapters/secta/src/sdk/utils/squarePriceMath.ts create mode 100644 adapters/secta/src/sdk/utils/tickLibrary.ts create mode 100644 adapters/secta/src/sdk/utils/tickMath.ts create mode 100644 adapters/secta/tsconfig.json diff --git a/adapters/secta/package.json b/adapters/secta/package.json new file mode 100644 index 00000000..3acc5e6f --- /dev/null +++ b/adapters/secta/package.json @@ -0,0 +1,33 @@ +{ + "name": "secta", + "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", + "web3": "^4.6.0" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "@types/web3": "^1.2.2", + "typescript": "^5.3.3" + } +} diff --git a/adapters/secta/src/index.ts b/adapters/secta/src/index.ts new file mode 100644 index 00000000..e8a2a0d8 --- /dev/null +++ b/adapters/secta/src/index.ts @@ -0,0 +1,287 @@ +import BigNumber from "bignumber.js"; +import { CHAINS, PROTOCOLS, AMM_TYPES, RPC_URLS } from "./sdk/config"; +import { + getLPValueByUserAndPoolFromPositions, + getMintedAddresses, + getPositionAtBlock, + getPositionDetailsFromPosition, + getPositionsForAddressByPoolAtBlock, + getV2Pairs, +} from "./sdk/subgraphDetails"; +import { + LiquidityMap, + TokenLiquidityInfo, + LiquidityInfo, + combineLiquidityInfoMaps, +} from "./sdk/liquidityTypes"; + +(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 path from "path"; +import { format } from "fast-csv"; +import { write } from "fast-csv"; +import { pipeline as streamPipeline } from "stream"; +import { captureRejectionSymbol } from "events"; +import { getV2LpValue } from "./sdk/poolDetails"; + +//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[] = []; + //console.log(`Reading: ${filePath}`); + + await new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(csv({ separator: "\t" })) // Specify the separator as '\t' for TSV files + .on("data", (row) => { + //console.log(row); + const blockNumber = parseInt(row.number, 10); + const blockTimestamp = parseInt(row.block_timestamp, 10); + //console.log(`Maybe Data ${blockNumber} ${blockTimestamp}`); + if (!isNaN(blockNumber) && blockTimestamp) { + //console.log(`Valid Data`); + blocks.push({ blockNumber: blockNumber, blockTimestamp }); + } + }) + .on("end", () => { + resolve(); + }) + .on("error", (err) => { + reject(err); + }); + }); + + //console.log(`blocks: ${blocks.length}`); + return blocks; +}; + +type OutputDataSchemaRow = { + block_number: number; + timestamp: number; + user_address: string; + token_address: string; + token_balance: bigint; + token_symbol: string; +}; + +export const getUserTVLByBlock = async (blocks: BlockData) => { + const { blockNumber, blockTimestamp } = blocks; + //console.log(`Getting tvl in block: ${blockNumber}`); + + const v3Positions = await getPositionsForAddressByPoolAtBlock( + blockNumber, + "", + "", + CHAINS.LINEA_ID, + PROTOCOLS.SECTA, + AMM_TYPES.SECTAV3 + ); + let v3PositionsWithValue = v3Positions.map(getPositionDetailsFromPosition); + let v3LpValue = getLPValueByUserAndPoolFromPositions(v3PositionsWithValue); + + let pairs = await getV2Pairs( + blockNumber, + CHAINS.LINEA_ID, + PROTOCOLS.SECTA, + AMM_TYPES.SECTAV2 + ); + let mintedAddresses = await getMintedAddresses( + blockNumber, + CHAINS.LINEA_ID, + PROTOCOLS.SECTA, + AMM_TYPES.SECTAV2 + ); + + let v2LpValue = await getV2LpValue( + RPC_URLS[CHAINS.LINEA_ID], + pairs, + mintedAddresses, + blockNumber + ); + + const combinedLpValue = combineLiquidityInfoMaps(v3LpValue, v2LpValue); + + let csvRows: OutputDataSchemaRow[] = []; + + Object.entries(combinedLpValue).forEach(([userAddress, tokens]) => { + Object.entries(tokens).forEach(([token, { amount, decimals }]) => { + csvRows.push({ + block_number: blockNumber, + timestamp: blockTimestamp, + user_address: userAddress, + token_address: token, + token_balance: BigInt( + BigNumber(amount) + .times(BigNumber(10 ** decimals)) + .integerValue() + .toNumber() + ), + token_symbol: "", + }); + }); + }); + + return csvRows; +}; + +/*readBlocksFromCSV( + path.resolve(__dirname, "../../block_numbers.tsv") +) + .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 result = await getUserTVLByBlock(block); + + // Accumulate CSV rows for all blocks + allCsvRows.push(...result); + + i++; + console.log(`Processed block ${i}`); + + // Write to file when batch size is reached or at the end of loop + if (i % batchSize === 0 || i === blocks.length) { + const ws = fs.createWriteStream(`outputData.csv`, { + flags: i === batchSize ? "w" : "a", + }); + write(allCsvRows, { + headers: i === batchSize ? true : false, + }) + .pipe(ws) + .on("finish", () => { + console.log(`CSV file has been written.`); + }); + + // Clear the accumulated CSV rows + allCsvRows.length = 0; + } + } 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/secta/src/sdk/abis.ts b/adapters/secta/src/sdk/abis.ts new file mode 100644 index 00000000..ef8f3b69 --- /dev/null +++ b/adapters/secta/src/sdk/abis.ts @@ -0,0 +1,713 @@ +export const SECTA_V2_LP = [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint112", + "name": "reserve0", + "type": "uint112" + }, + { + "indexed": false, + "internalType": "uint112", + "name": "reserve1", + "type": "uint112" + } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint112", + "name": "_reserve0", + "type": "uint112" + }, + { + "internalType": "uint112", + "name": "_reserve1", + "type": "uint112" + }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "skim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "sync", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/adapters/secta/src/sdk/config.ts b/adapters/secta/src/sdk/config.ts new file mode 100644 index 00000000..3653b52e --- /dev/null +++ b/adapters/secta/src/sdk/config.ts @@ -0,0 +1,25 @@ +export const enum CHAINS{ + LINEA_ID = 59144, +} +export const enum PROTOCOLS{ + SECTA = 0, +} + +export const enum AMM_TYPES{ + SECTAV3 = 0, + SECTAV2 = 1, +} + +export const SUBGRAPH_URLS = { + [CHAINS.LINEA_ID]: { + [PROTOCOLS.SECTA]: { + [AMM_TYPES.SECTAV3]: "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v3/version/latest", + // "https://gateway-arbitrum.network.thegraph.com/api/3700f7806f624898da7631bb01f5253f/subgraphs/id/DQz9g5ZRSiprkXXCRwRSTjh6J5gsRMuhr8TymEo1pZe6", + [AMM_TYPES.SECTAV2]: "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v2/version/latest" + // "https://gateway-arbitrum.network.thegraph.com/api/3700f7806f624898da7631bb01f5253f/subgraphs/id/4YKqZQ3pH5wZ3seW2ojc1o5HxoJVYQ6UBdunW8ovJCBz", + } + } +} +export const RPC_URLS = { + [CHAINS.LINEA_ID]: "https://rpc.linea.build/" +} \ No newline at end of file diff --git a/adapters/secta/src/sdk/entities/positions.ts b/adapters/secta/src/sdk/entities/positions.ts new file mode 100644 index 00000000..bb410185 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/liquidityTypes.ts b/adapters/secta/src/sdk/liquidityTypes.ts new file mode 100644 index 00000000..2c516a45 --- /dev/null +++ b/adapters/secta/src/sdk/liquidityTypes.ts @@ -0,0 +1,76 @@ +import BigNumber from "bignumber.js"; + +export type TokenLiquidityInfo = { + amount: number; + decimals: number; +}; + +export type LiquidityMap = { + [token: string]: TokenLiquidityInfo; +}; + +export type LiquidityInfo = { + [user_address: string]: LiquidityMap; +}; + +export function getOrCreateTokenLiquidityInfo( + liquidityInfo: LiquidityInfo, + userAddress: string, + token: string, + decimals: number, + ): TokenLiquidityInfo { + // Check if the user exists + if (!liquidityInfo[userAddress]) { + liquidityInfo[userAddress] = {}; // Create a new entry for the user + } + + // Check if the token exists for the user + if (!liquidityInfo[userAddress][token]) { + liquidityInfo[userAddress][token] = {amount:0, decimals: decimals}; // Create a default entry for the token + } + + // Return the liquidity info for the token of the user + return liquidityInfo[userAddress][token]; +} + +export function combineLiquidityInfoMaps( + map1: LiquidityInfo, + map2: LiquidityInfo + ): LiquidityInfo { + const combinedMap: LiquidityInfo = {}; + + // Helper function to add or update liquidity info in the combined map + const addOrUpdateLiquidityInfo = (userAddress: string, token: string, liquidityInfo: TokenLiquidityInfo) => { + + if (!combinedMap[userAddress]) { + combinedMap[userAddress] = {}; + } + + if (!combinedMap[userAddress][token]) { + combinedMap[userAddress][token] = { amount: 0, decimals: liquidityInfo.decimals}; + } else if (combinedMap[userAddress][token].decimals != liquidityInfo.decimals){ + throw new Error(`Token precision doesn't match. user: ${userAddress} token: ${token}`); + } + + const existingAmount = new BigNumber(combinedMap[userAddress][token].amount); + const newAmount = existingAmount.plus(liquidityInfo.amount); + combinedMap[userAddress][token].amount = newAmount.toNumber(); + + }; + + // Combine map1 into the combinedMap + for (const userAddress in map1) { + for (const token in map1[userAddress]) { + addOrUpdateLiquidityInfo(userAddress, token, map1[userAddress][token]); + } + } + + // Combine map2 into the combinedMap, adding to existing entries + for (const userAddress in map2) { + for (const token in map2[userAddress]) { + addOrUpdateLiquidityInfo(userAddress, token, map2[userAddress][token]); + } + } + + return combinedMap; + } \ No newline at end of file diff --git a/adapters/secta/src/sdk/mathUtils.ts b/adapters/secta/src/sdk/mathUtils.ts new file mode 100644 index 00000000..d245b2df --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/poolDetails.ts b/adapters/secta/src/sdk/poolDetails.ts new file mode 100644 index 00000000..2a1d04de --- /dev/null +++ b/adapters/secta/src/sdk/poolDetails.ts @@ -0,0 +1,70 @@ +import Web3 from 'web3'; +import BigNumber from "bignumber.js"; +import { V2MintedUserAddresses, V2Pair } from './subgraphDetails'; +import { LiquidityInfo } from './liquidityTypes'; +import { SECTA_V2_LP } from './abis'; + + +export const getERC20TokenBalanceAtBlock = async (rpc: string, tokenAddress: string, userAddress: string, blockNumber: number): Promise => { + + const web3 = new Web3(new Web3.providers.HttpProvider(rpc)); + const contract = new web3.eth.Contract(SECTA_V2_LP, tokenAddress); + + const balanceHex = await contract.methods.balanceOf(userAddress).call({}, blockNumber); + + // Convert the balance from hex to a decimal string + const balance: BigInt = web3.utils.toBigInt(balanceHex); + return balance; +}; + +export const getV2LpValue = async (rpc: string, pairs: V2Pair[], mintedAddresses: V2MintedUserAddresses, blockNumber: number): Promise => { + + const liquidityInfo: LiquidityInfo = {}; + + for (const pair of pairs) { + const userAddresses = mintedAddresses[pair.id] || new Set(); + + for (const userAddress of userAddresses) { + // Get the user's balance of the LP token as a BigNumber + const userLpBalanceBigInt = await getERC20TokenBalanceAtBlock(rpc, pair.id, userAddress, blockNumber); + const userLpBalance = new BigNumber(userLpBalanceBigInt.toString()); + + const totalSupply = new BigNumber(pair.totalSupply); + + const userShare = userLpBalance.dividedBy(totalSupply); + + // Calculate user's share of token0 and token1 + const token0Amount = userShare.multipliedBy(new BigNumber(pair.reserve0)); + const token1Amount = userShare.multipliedBy(new BigNumber(pair.reserve1)); + + // Ensure user's entry exists in the liquidity info + if (!liquidityInfo[userAddress]) { + liquidityInfo[userAddress] = {}; + } + + // Populate or update the user's share for token0 + const existingToken0 = liquidityInfo[userAddress][pair.token0.id]; + if (existingToken0) { + existingToken0.amount += token0Amount.toNumber(); + } else { + liquidityInfo[userAddress][pair.token0.id] = { + amount: token0Amount.toNumber(), + decimals: pair.token0.decimals, + }; + } + + // Populate or update the user's share for token1 + const existingToken1 = liquidityInfo[userAddress][pair.token1.id]; + if (existingToken1) { + existingToken1.amount += token1Amount.toNumber(); + } else { + liquidityInfo[userAddress][pair.token1.id] = { + amount: token1Amount.toNumber(), + decimals: pair.token1.decimals, + }; + } + } + } + + return liquidityInfo; +}; \ No newline at end of file diff --git a/adapters/secta/src/sdk/positionDetails.ts b/adapters/secta/src/sdk/positionDetails.ts new file mode 100644 index 00000000..a51d248a --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/subgraphDetails.ts b/adapters/secta/src/sdk/subgraphDetails.ts new file mode 100644 index 00000000..1a7c2e69 --- /dev/null +++ b/adapters/secta/src/sdk/subgraphDetails.ts @@ -0,0 +1,411 @@ +import BigNumber from "bignumber.js"; +import { AMM_TYPES, CHAINS, PROTOCOLS, SUBGRAPH_URLS } from "./config"; +import { PositionMath } from "./utils/positionMath"; +import { LiquidityMap, TokenLiquidityInfo, LiquidityInfo, getOrCreateTokenLiquidityInfo } from "./liquidityTypes"; + + + + +export interface V3Position{ + 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 V3PositionWithUSDValue extends V3Position{ + token0USDValue: string; + token1USDValue: string; + token0AmountsInWei: bigint; + token1AmountsInWei: bigint; + token0DecimalValue: number; + token1DecimalValue: number; +} + +export interface V2Pair{ + id: string; + token0: { + id: string; + decimals: number; + }; + token1: { + id: string; + decimals: number; + } + reserve0: number; + reserve1: number; + totalSupply: number; +} + +export interface V2MintedUserAddresses{ + [token: string]: Set; +} + +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: V3Position[] = []; + while(fetchNext){ + let query = `{ + positions(${whereQuery} ${blockQuery} orderBy: transaction__timestamp, first:1000,skip:${skip}) { + id + + liquidity + owner + pool { + sqrtPrice + tick + id + } + tickLower{ + tickIdx + } + tickUpper{ + tickIdx + } + token0 { + id + decimals + derivedUSD + name + symbol + } + token1 { + id + decimals + derivedUSD + 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; + for (let i = 0; i < positions.length; i++) { + let position = positions[i]; + let transformedPosition: V3Position = { + 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: position.token0.derivedUSD, + name: position.token0.name, + symbol: position.token0.symbol, + }, + token1: { + id: position.token1.id, + decimals: position.token1.decimals, + derivedUSD: position.token1.derivedUSD, + name: position.token1.name, + symbol: position.token1.symbol, + }, + }; + result.push(transformedPosition); + + } + if(positions.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 = `{ + position(id: "${positionId}" ${blockQuery}) { + id + pool { + sqrtPrice + tick + } + tickLower{ + tickIdx + } + tickUpper{ + tickIdx + } + liquidity + token0 { + id + decimals + derivedUSD + name + symbol + } + token1 { + id + decimals + derivedUSD + 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; + + + 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: position.token0.derivedUSD, + name: position.token0.name, + symbol: position.token0.symbol, + }, + token1: { + id: position.token1.id, + decimals: position.token1.decimals, + derivedUSD: position.token1.derivedUSD, + name: position.token1.name, + symbol: position.token1.symbol, + }, + }; +} + +export const getPositionDetailsFromPosition = ( + position: V3Position +):V3PositionWithUSDValue => { + 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: V3Position[] +): LiquidityInfo => { + let result: LiquidityInfo = {}; + for (let i = 0; i < positions.length; i++) { + let position = positions[i]; + let poolId = position.pool.id; + let owner = position.owner; + let token0 = position.token0.id; + let token0Decimals = position.token0.decimals; + let token1 = position.token1.id; + let token1Decimals = position.token1.decimals; + + let token0LiquidityInfo = getOrCreateTokenLiquidityInfo(result, owner, token0, token0Decimals) + let token1LiquidityInfo = getOrCreateTokenLiquidityInfo(result, owner, token1, token1Decimals) + + let positionWithUSDValue = getPositionDetailsFromPosition(position); + + token0LiquidityInfo.amount = BigNumber(token0LiquidityInfo.amount).plus(BigNumber(positionWithUSDValue.token0DecimalValue)).toNumber() + token1LiquidityInfo.amount = BigNumber(token1LiquidityInfo.amount).plus(BigNumber(positionWithUSDValue.token1DecimalValue)).toNumber() + + } + return result; +} + +export const getV2Pairs = async ( + blockNumber: 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 = `{ + pairs (block: {number: ${blockNumber}}){ + id + token1 { + id + decimals + } + token0 { + id + decimals + } + reserve0 + reserve1 + totalSupply + } + }`; + + let response = await fetch(subgraphUrl, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + let pairs: any[] = data.data.pairs; + + let rv: V2Pair[] = [] + + for (let i = 0; i < pairs.length; i++) { + rv.push({ + id: pairs[i].id, + token0: { + id: pairs[i].token0.id, + decimals: pairs[i].token0.decimals, + }, + token1: { + id: pairs[i].token1.id, + decimals: pairs[i].token1.decimals, + }, + totalSupply: pairs[i].totalSupply, + reserve0: pairs[i].reserve0, + reserve1: pairs[i].reserve1, + + }) + } + return rv; +} + +export const getMintedAddresses = async ( + blockNumber: 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 = `{ + mints { + pair { + id + } + to + } + }`; + + let response = await fetch(subgraphUrl, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + let mints: any[] = data.data.mints; + + let rv: V2MintedUserAddresses = {} + + for(let i = 0; i < mints.length; i++) { + const tokenAddress = mints[i].pair.id; + const userAddress = mints[i].to; + + if(!(tokenAddress in rv)){ + rv[tokenAddress] = new Set(); + } + + rv[tokenAddress].add(userAddress); + } + + return rv; +} diff --git a/adapters/secta/src/sdk/utils/constant.ts b/adapters/secta/src/sdk/utils/constant.ts new file mode 100644 index 00000000..375569d6 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/fractions/fraction.js b/adapters/secta/src/sdk/utils/fractions/fraction.js new file mode 100644 index 00000000..bbba9890 --- /dev/null +++ b/adapters/secta/src/sdk/utils/fractions/fraction.js @@ -0,0 +1,107 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Fraction = void 0; +const tiny_invariant_1 = __importDefault(require("tiny-invariant")); +const decimal_js_light_1 = __importDefault(require("decimal.js-light")); +const big_js_1 = __importDefault(require("big.js")); +// @ts-ignore +const toformat_1 = __importDefault(require("toformat")); +const constant_1 = require("../constant"); +const Decimal = (0, toformat_1.default)(decimal_js_light_1.default); +const Big = (0, toformat_1.default)(big_js_1.default); +const toSignificantRounding = { + [constant_1.Rounding.ROUND_DOWN]: Decimal.ROUND_DOWN, + [constant_1.Rounding.ROUND_HALF_UP]: Decimal.ROUND_HALF_UP, + [constant_1.Rounding.ROUND_UP]: Decimal.ROUND_UP, +}; +const toFixedRounding = { + [constant_1.Rounding.ROUND_DOWN]: 0 /* RoundingL2_CHAIN_ID.RoundDown */, + [constant_1.Rounding.ROUND_HALF_UP]: 1 /* RoundingL2_CHAIN_ID.RoundHalfUp */, + [constant_1.Rounding.ROUND_UP]: 3 /* RoundingL2_CHAIN_ID.RoundUp */, +}; +class Fraction { + numerator; + denominator; + constructor(numerator, denominator = 1n) { + this.numerator = BigInt(numerator); + this.denominator = BigInt(denominator); + } + static tryParseFraction(fractionish) { + 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 + get quotient() { + return this.numerator / this.denominator; + } + // remainder after floor division + get remainder() { + return new Fraction(this.numerator % this.denominator, this.denominator); + } + invert() { + return new Fraction(this.denominator, this.numerator); + } + add(other) { + 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); + } + subtract(other) { + 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); + } + lessThan(other) { + const otherParsed = Fraction.tryParseFraction(other); + return this.numerator * otherParsed.denominator < otherParsed.numerator * this.denominator; + } + equalTo(other) { + const otherParsed = Fraction.tryParseFraction(other); + return this.numerator * otherParsed.denominator === otherParsed.numerator * this.denominator; + } + greaterThan(other) { + const otherParsed = Fraction.tryParseFraction(other); + return this.numerator * otherParsed.denominator > otherParsed.numerator * this.denominator; + } + multiply(other) { + const otherParsed = Fraction.tryParseFraction(other); + return new Fraction(this.numerator * otherParsed.numerator, this.denominator * otherParsed.denominator); + } + divide(other) { + const otherParsed = Fraction.tryParseFraction(other); + return new Fraction(this.numerator * otherParsed.denominator, this.denominator * otherParsed.numerator); + } + toSignificant(significantDigits, format = { groupSeparator: '' }, rounding = constant_1.Rounding.ROUND_HALF_UP) { + (0, tiny_invariant_1.default)(Number.isInteger(significantDigits), `${significantDigits} is not an integer.`); + (0, tiny_invariant_1.default)(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); + } + toFixed(decimalPlaces, format = { groupSeparator: '' }, rounding = constant_1.Rounding.ROUND_HALF_UP) { + (0, tiny_invariant_1.default)(Number.isInteger(decimalPlaces), `${decimalPlaces} is not an integer.`); + (0, tiny_invariant_1.default)(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 + */ + get asFraction() { + return new Fraction(this.numerator, this.denominator); + } +} +exports.Fraction = Fraction; diff --git a/adapters/secta/src/sdk/utils/fractions/fraction.ts b/adapters/secta/src/sdk/utils/fractions/fraction.ts new file mode 100644 index 00000000..3970dd43 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/fractions/percent.js b/adapters/secta/src/sdk/utils/fractions/percent.js new file mode 100644 index 00000000..80bd2ce9 --- /dev/null +++ b/adapters/secta/src/sdk/utils/fractions/percent.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Percent = void 0; +const fraction_1 = require("./fraction"); +const ONE_HUNDRED = new fraction_1.Fraction(100n); +/** + * Converts a fraction to a percent + * @param fraction the fraction to convert + */ +function toPercent(fraction) { + return new Percent(fraction.numerator, fraction.denominator); +} +class Percent extends fraction_1.Fraction { + /** + * This boolean prevents a fraction from being interpreted as a Percent + */ + isPercent = true; + add(other) { + return toPercent(super.add(other)); + } + subtract(other) { + return toPercent(super.subtract(other)); + } + multiply(other) { + return toPercent(super.multiply(other)); + } + divide(other) { + return toPercent(super.divide(other)); + } + toSignificant(significantDigits = 5, format, rounding) { + return super.multiply(ONE_HUNDRED).toSignificant(significantDigits, format, rounding); + } + toFixed(decimalPlaces = 2, format, rounding) { + return super.multiply(ONE_HUNDRED).toFixed(decimalPlaces, format, rounding); + } +} +exports.Percent = Percent; diff --git a/adapters/secta/src/sdk/utils/fractions/percent.ts b/adapters/secta/src/sdk/utils/fractions/percent.ts new file mode 100644 index 00000000..5fcb5d72 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/fullMath.ts b/adapters/secta/src/sdk/utils/fullMath.ts new file mode 100644 index 00000000..fe84c76b --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/internalConstants.ts b/adapters/secta/src/sdk/utils/internalConstants.ts new file mode 100644 index 00000000..a9fc9a73 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/mostSignificantBit.ts b/adapters/secta/src/sdk/utils/mostSignificantBit.ts new file mode 100644 index 00000000..c1cb4c31 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/positionMath.ts b/adapters/secta/src/sdk/utils/positionMath.ts new file mode 100644 index 00000000..d20f2b90 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/squarePriceMath.ts b/adapters/secta/src/sdk/utils/squarePriceMath.ts new file mode 100644 index 00000000..02639c62 --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/tickLibrary.ts b/adapters/secta/src/sdk/utils/tickLibrary.ts new file mode 100644 index 00000000..7d59c95e --- /dev/null +++ b/adapters/secta/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/secta/src/sdk/utils/tickMath.ts b/adapters/secta/src/sdk/utils/tickMath.ts new file mode 100644 index 00000000..18a01d96 --- /dev/null +++ b/adapters/secta/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/secta/tsconfig.json b/adapters/secta/tsconfig.json new file mode 100644 index 00000000..8df9e19a --- /dev/null +++ b/adapters/secta/tsconfig.json @@ -0,0 +1,101 @@ +{ + "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. */ + } +} \ No newline at end of file From b8248337df46d0bb47caed9c6fa955ba709e10ac Mon Sep 17 00:00:00 2001 From: Jac <39693487+lijac@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:42:19 -0400 Subject: [PATCH 2/3] chore: secta - tidy --- adapters/secta/block_numbers_secta.tsv | 5 + adapters/secta/src/index.ts | 100 +--------- adapters/secta/src/sdk/config.ts | 22 ++- adapters/secta/src/sdk/subgraphDetails.ts | 218 +++++++++++++--------- 4 files changed, 154 insertions(+), 191 deletions(-) create mode 100644 adapters/secta/block_numbers_secta.tsv diff --git a/adapters/secta/block_numbers_secta.tsv b/adapters/secta/block_numbers_secta.tsv new file mode 100644 index 00000000..4c2635bb --- /dev/null +++ b/adapters/secta/block_numbers_secta.tsv @@ -0,0 +1,5 @@ +block_timestamp number +1708904831 2518028 +1708991231 2539628 +1709077631 2561228 +1709164031 2582828 \ No newline at end of file diff --git a/adapters/secta/src/index.ts b/adapters/secta/src/index.ts index e8a2a0d8..d2c44c25 100644 --- a/adapters/secta/src/index.ts +++ b/adapters/secta/src/index.ts @@ -29,21 +29,6 @@ import { pipeline as streamPipeline } from "stream"; import { captureRejectionSymbol } from "events"; import { getV2LpValue } from "./sdk/poolDetails"; -//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; @@ -60,79 +45,8 @@ 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; @@ -174,6 +88,7 @@ type OutputDataSchemaRow = { token_address: string; token_balance: bigint; token_symbol: string; + usd_price: number; }; export const getUserTVLByBlock = async (blocks: BlockData) => { @@ -184,7 +99,7 @@ export const getUserTVLByBlock = async (blocks: BlockData) => { blockNumber, "", "", - CHAINS.LINEA_ID, + CHAINS.LINEA, PROTOCOLS.SECTA, AMM_TYPES.SECTAV3 ); @@ -193,19 +108,19 @@ export const getUserTVLByBlock = async (blocks: BlockData) => { let pairs = await getV2Pairs( blockNumber, - CHAINS.LINEA_ID, + CHAINS.LINEA, PROTOCOLS.SECTA, AMM_TYPES.SECTAV2 ); let mintedAddresses = await getMintedAddresses( blockNumber, - CHAINS.LINEA_ID, + CHAINS.LINEA, PROTOCOLS.SECTA, AMM_TYPES.SECTAV2 ); let v2LpValue = await getV2LpValue( - RPC_URLS[CHAINS.LINEA_ID], + RPC_URLS[CHAINS.LINEA], pairs, mintedAddresses, blockNumber @@ -229,6 +144,7 @@ export const getUserTVLByBlock = async (blocks: BlockData) => { .toNumber() ), token_symbol: "", + usd_price: 0, }); }); }); @@ -236,9 +152,7 @@ export const getUserTVLByBlock = async (blocks: BlockData) => { return csvRows; }; -/*readBlocksFromCSV( - path.resolve(__dirname, "../../block_numbers.tsv") -) +/*readBlocksFromCSV(path.resolve(__dirname, "../block_numbers_secta.tsv")) .then(async (blocks) => { console.log(blocks); const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks diff --git a/adapters/secta/src/sdk/config.ts b/adapters/secta/src/sdk/config.ts index 3653b52e..58233af7 100644 --- a/adapters/secta/src/sdk/config.ts +++ b/adapters/secta/src/sdk/config.ts @@ -1,5 +1,5 @@ -export const enum CHAINS{ - LINEA_ID = 59144, +export const enum CHAINS { + LINEA = 59144, } export const enum PROTOCOLS{ SECTA = 0, @@ -11,15 +11,17 @@ export const enum AMM_TYPES{ } export const SUBGRAPH_URLS = { - [CHAINS.LINEA_ID]: { + [CHAINS.LINEA]: { [PROTOCOLS.SECTA]: { - [AMM_TYPES.SECTAV3]: "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v3/version/latest", + [AMM_TYPES.SECTAV3]: + "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v3/version/latest", // "https://gateway-arbitrum.network.thegraph.com/api/3700f7806f624898da7631bb01f5253f/subgraphs/id/DQz9g5ZRSiprkXXCRwRSTjh6J5gsRMuhr8TymEo1pZe6", - [AMM_TYPES.SECTAV2]: "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v2/version/latest" + [AMM_TYPES.SECTAV2]: + "https://api.studio.thegraph.com/query/66239/secta-linea-exchange-v2/version/latest", // "https://gateway-arbitrum.network.thegraph.com/api/3700f7806f624898da7631bb01f5253f/subgraphs/id/4YKqZQ3pH5wZ3seW2ojc1o5HxoJVYQ6UBdunW8ovJCBz", - } - } -} + }, + }, +}; export const RPC_URLS = { - [CHAINS.LINEA_ID]: "https://rpc.linea.build/" -} \ No newline at end of file + [CHAINS.LINEA]: "https://rpc.linea.build/", +}; \ No newline at end of file diff --git a/adapters/secta/src/sdk/subgraphDetails.ts b/adapters/secta/src/sdk/subgraphDetails.ts index 1a7c2e69..9aaac4df 100644 --- a/adapters/secta/src/sdk/subgraphDetails.ts +++ b/adapters/secta/src/sdk/subgraphDetails.ts @@ -4,9 +4,7 @@ import { PositionMath } from "./utils/positionMath"; import { LiquidityMap, TokenLiquidityInfo, LiquidityInfo, getOrCreateTokenLiquidityInfo } from "./liquidityTypes"; - - -export interface V3Position{ +export interface V3Position { id: string; liquidity: bigint; owner: string; @@ -35,11 +33,10 @@ export interface V3Position{ derivedUSD: number; name: string; symbol: string; - } -}; - + }; +} -export interface V3PositionWithUSDValue extends V3Position{ +export interface V3PositionWithUSDValue extends V3Position { token0USDValue: string; token1USDValue: string; token0AmountsInWei: bigint; @@ -48,7 +45,7 @@ export interface V3PositionWithUSDValue extends V3Position{ token1DecimalValue: number; } -export interface V2Pair{ +export interface V2Pair { id: string; token0: { id: string; @@ -57,13 +54,13 @@ export interface V2Pair{ token1: { id: string; decimals: number; - } + }; reserve0: number; reserve1: number; totalSupply: number; } -export interface V2MintedUserAddresses{ +export interface V2MintedUserAddresses { [token: string]: Set; } @@ -71,20 +68,29 @@ export const getPositionsForAddressByPoolAtBlock = async ( blockNumber: number, address: string, poolId: string, - chainId: CHAINS, + chain: 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 subgraphUrl = SUBGRAPH_URLS[chain][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 whereQuery = + ownerQuery !== "" && poolQuery !== "" + ? `where: {${ownerQuery} , ${poolQuery}}` + : ownerQuery !== "" + ? `where: {${ownerQuery}}` + : poolQuery !== "" + ? `where: {${poolQuery}}` + : ``; let skip = 0; let fetchNext = true; let result: V3Position[] = []; - while(fetchNext){ + while (fetchNext) { let query = `{ positions(${whereQuery} ${blockQuery} orderBy: transaction__timestamp, first:1000,skip:${skip}) { id @@ -124,7 +130,7 @@ export const getPositionsForAddressByPoolAtBlock = async ( } }`; - // console.log(query) + // console.log(query) let response = await fetch(subgraphUrl, { method: "POST", @@ -166,27 +172,26 @@ export const getPositionsForAddressByPoolAtBlock = async ( }, }; result.push(transformedPosition); - } - if(positions.length < 1000){ + if (positions.length < 1000) { fetchNext = false; - }else{ + } else { skip += 1000; } } return result; -} - +}; export const getPositionAtBlock = async ( blockNumber: number, positionId: number, - chainId: CHAINS, + chain: CHAINS, protocol: PROTOCOLS, ammType: AMM_TYPES ): Promise => { - let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; - let blockQuery = blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; + let subgraphUrl = SUBGRAPH_URLS[chain][protocol][ammType]; + let blockQuery = + blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; let query = `{ position(id: "${positionId}" ${blockQuery}) { id @@ -230,42 +235,41 @@ export const getPositionAtBlock = async ( let data = await response.json(); let position = data.data.position; + 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: position.token0.derivedUSD, + name: position.token0.name, + symbol: position.token0.symbol, + }, + token1: { + id: position.token1.id, + decimals: position.token1.decimals, + derivedUSD: position.token1.derivedUSD, + name: position.token1.name, + symbol: position.token1.symbol, + }, + }; +}; - 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: position.token0.derivedUSD, - name: position.token0.name, - symbol: position.token0.symbol, - }, - token1: { - id: position.token1.id, - decimals: position.token1.decimals, - derivedUSD: position.token1.derivedUSD, - name: position.token1.name, - symbol: position.token1.symbol, - }, - }; -} - -export const getPositionDetailsFromPosition = ( +export const getPositionDetailsFromPosition = ( position: V3Position -):V3PositionWithUSDValue => { +): V3PositionWithUSDValue => { let tickLow = position.tickLower.tickIdx; let tickHigh = position.tickUpper.tickIdx; let liquidity = position.liquidity; @@ -275,19 +279,43 @@ export const getPositionDetailsFromPosition = ( 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 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}; -} + 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: V3Position[] @@ -302,26 +330,40 @@ export const getLPValueByUserAndPoolFromPositions = ( let token1 = position.token1.id; let token1Decimals = position.token1.decimals; - let token0LiquidityInfo = getOrCreateTokenLiquidityInfo(result, owner, token0, token0Decimals) - let token1LiquidityInfo = getOrCreateTokenLiquidityInfo(result, owner, token1, token1Decimals) + let token0LiquidityInfo = getOrCreateTokenLiquidityInfo( + result, + owner, + token0, + token0Decimals + ); + let token1LiquidityInfo = getOrCreateTokenLiquidityInfo( + result, + owner, + token1, + token1Decimals + ); let positionWithUSDValue = getPositionDetailsFromPosition(position); - token0LiquidityInfo.amount = BigNumber(token0LiquidityInfo.amount).plus(BigNumber(positionWithUSDValue.token0DecimalValue)).toNumber() - token1LiquidityInfo.amount = BigNumber(token1LiquidityInfo.amount).plus(BigNumber(positionWithUSDValue.token1DecimalValue)).toNumber() - + token0LiquidityInfo.amount = BigNumber(token0LiquidityInfo.amount) + .plus(BigNumber(positionWithUSDValue.token0DecimalValue)) + .toNumber(); + token1LiquidityInfo.amount = BigNumber(token1LiquidityInfo.amount) + .plus(BigNumber(positionWithUSDValue.token1DecimalValue)) + .toNumber(); } return result; -} +}; export const getV2Pairs = async ( blockNumber: number, - chainId: CHAINS, + chain: CHAINS, protocol: PROTOCOLS, ammType: AMM_TYPES ): Promise => { - let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; - let blockQuery = blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; + let subgraphUrl = SUBGRAPH_URLS[chain][protocol][ammType]; + let blockQuery = + blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; let query = `{ pairs (block: {number: ${blockNumber}}){ id @@ -347,7 +389,7 @@ export const getV2Pairs = async ( let data = await response.json(); let pairs: any[] = data.data.pairs; - let rv: V2Pair[] = [] + let rv: V2Pair[] = []; for (let i = 0; i < pairs.length; i++) { rv.push({ @@ -363,20 +405,20 @@ export const getV2Pairs = async ( totalSupply: pairs[i].totalSupply, reserve0: pairs[i].reserve0, reserve1: pairs[i].reserve1, - - }) + }); } return rv; -} +}; export const getMintedAddresses = async ( blockNumber: number, - chainId: CHAINS, + chain: CHAINS, protocol: PROTOCOLS, ammType: AMM_TYPES ): Promise => { - let subgraphUrl = SUBGRAPH_URLS[chainId][protocol][ammType]; - let blockQuery = blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; + let subgraphUrl = SUBGRAPH_URLS[chain][protocol][ammType]; + let blockQuery = + blockNumber !== 0 ? `, block: {number: ${blockNumber}}` : ``; let query = `{ mints { pair { @@ -394,13 +436,13 @@ export const getMintedAddresses = async ( let data = await response.json(); let mints: any[] = data.data.mints; - let rv: V2MintedUserAddresses = {} + let rv: V2MintedUserAddresses = {}; - for(let i = 0; i < mints.length; i++) { + for (let i = 0; i < mints.length; i++) { const tokenAddress = mints[i].pair.id; const userAddress = mints[i].to; - if(!(tokenAddress in rv)){ + if (!(tokenAddress in rv)) { rv[tokenAddress] = new Set(); } @@ -408,4 +450,4 @@ export const getMintedAddresses = async ( } return rv; -} +}; From 72e306bd588e9695cfad628978162d9741555323 Mon Sep 17 00:00:00 2001 From: Jac <39693487+lijac@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:38:05 -0400 Subject: [PATCH 3/3] fix: Remove inactive liquidity --- adapters/secta/src/sdk/positionDetails.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/adapters/secta/src/sdk/positionDetails.ts b/adapters/secta/src/sdk/positionDetails.ts index a51d248a..d165c607 100644 --- a/adapters/secta/src/sdk/positionDetails.ts +++ b/adapters/secta/src/sdk/positionDetails.ts @@ -32,13 +32,16 @@ export const getTokenAmounts = async( 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))); + 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);