diff --git a/adapters/layerbank/hourly_blocks.csv b/adapters/layerbank/hourly_blocks.csv new file mode 100644 index 0000000..8ffabfa --- /dev/null +++ b/adapters/layerbank/hourly_blocks.csv @@ -0,0 +1,2 @@ +number,timestamp +4243360,1714773599 \ No newline at end of file diff --git a/adapters/layerbank/package.json b/adapters/layerbank/package.json new file mode 100644 index 0000000..fb94ec1 --- /dev/null +++ b/adapters/layerbank/package.json @@ -0,0 +1,35 @@ +{ + "name": "layerbank-tvl", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node dist/index.js", + "compile": "tsc", + "watch": "tsc -w", + "clear": "rm -rf dist" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/big.js": "^6.2.2", + "big.js": "^6.2.1", + "bignumber.js": "^9.1.2", + "csv-parser": "^3.0.0", + "decimal.js-light": "^2.5.1", + "fast-csv": "^5.0.1", + "jsbi": "^4.3.0", + "tiny-invariant": "^1.3.1", + "toformat": "^2.0.0", + "viem": "^2.13.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "typescript": "^5.3.3" + } +} diff --git a/adapters/layerbank/src/index.ts b/adapters/layerbank/src/index.ts new file mode 100644 index 0000000..7d82380 --- /dev/null +++ b/adapters/layerbank/src/index.ts @@ -0,0 +1,128 @@ +import { CHAINS, PROTOCOLS } from "./sdk/config"; +import { + getAccountStatesForAddressByPoolAtBlock, + getTimestampAtBlock, +} from "./sdk/subgraphDetails"; + +(BigInt.prototype as any).toJSON = function () { + return this.toString(); +}; + +import fs from "fs"; +import csv from "csv-parser"; +import { write } from "fast-csv"; +import { getMarketInfos, updateBorrowBalances } from "./sdk/marketDetails"; +import { bigMath } from "./sdk/abi/helpers"; +import { exit } from "process"; + +interface BlockData { + blockNumber: number; + blockTimestamp: number; +} + +type OutputDataSchemaRow = { + protocol: string; + date: number; + block_number: number; + user_address: string; + market: string; + supply_token: bigint; + borrow_token: bigint; +}; + +export const getUserTVLByBlock = async (blocks: BlockData) => { + const marketInfos = await getMarketInfos( + "0xEC53c830f4444a8A56455c6836b5D2aA794289Aa" + ); + + const csvRows: OutputDataSchemaRow[] = []; + const block = blocks.blockNumber; + + let states = await getAccountStatesForAddressByPoolAtBlock( + block, + "", + "", + CHAINS.SCROLL, + PROTOCOLS.LAYERBANK + ); + states = states.filter( + (s) => marketInfos.findIndex((lu) => lu.address == s.account) == -1 + ); + + console.log(`Block: ${block}`); + console.log("States: ", states.length); + + await updateBorrowBalances(states); + + states.forEach((state) => { + const marketInfo = marketInfos.find( + (mi) => mi.underlyingAddress == state.token.toLowerCase() + ); + + // Accumulate CSV row data + csvRows.push({ + protocol: "Layerbank", + date: blocks.blockTimestamp, + block_number: blocks.blockNumber, + user_address: state.account, + market: state.token, + supply_token: state.lentAmount, + borrow_token: state.borrowAmount, + }); + }); + + return csvRows; +}; + +const readBlocksFromCSV = async (filePath: string): Promise => { + const blocks: BlockData[] = []; + + await new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(csv()) // Specify the separator as '\t' for TSV files + .on("data", (row) => { + const blockNumber = parseInt(row.number, 10); + const blockTimestamp = parseInt(row.timestamp, 10); + if (!isNaN(blockNumber) && blockTimestamp) { + blocks.push({ blockNumber: blockNumber, blockTimestamp }); + } + }) + .on("end", () => { + resolve(); + }) + .on("error", (err) => { + reject(err); + }); + }); + + return blocks; +}; + +readBlocksFromCSV("hourly_blocks.csv") + .then(async (blocks: any[]) => { + console.log(blocks); + const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks + + for (const block of blocks) { + try { + const result = await getUserTVLByBlock(block); + for (let i = 0; i < result.length; i++) { + allCsvRows.push(result[i]); + } + } catch (error) { + console.error(`An error occurred for block ${block}:`, error); + } + } + await new Promise((resolve, reject) => { + const ws = fs.createWriteStream(`outputData.csv`, { flags: "w" }); + write(allCsvRows, { headers: true }) + .pipe(ws) + .on("finish", () => { + console.log(`CSV file has been written.`); + resolve; + }); + }); + }) + .catch((err) => { + console.error("Error reading CSV file:", err); + }); diff --git a/adapters/layerbank/src/sdk/abi/core.abi.ts b/adapters/layerbank/src/sdk/abi/core.abi.ts new file mode 100644 index 0000000..144fe2d --- /dev/null +++ b/adapters/layerbank/src/sdk/abi/core.abi.ts @@ -0,0 +1,812 @@ +export default [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "newBorrowCap", + type: "uint256", + }, + ], + name: "BorrowCapUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "newCloseFactor", + type: "uint256", + }, + ], + name: "CloseFactorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "newCollateralFactor", + type: "uint256", + }, + ], + name: "CollateralFactorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "target", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "initiator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "asset", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "premium", + type: "uint256", + }, + ], + name: "FlashLoan", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newKeeper", + type: "address", + }, + ], + name: "KeeperUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newLABDistributor", + type: "address", + }, + ], + name: "LABDistributorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newLeverager", + type: "address", + }, + ], + name: "LeveragerUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "newLiquidationIncentive", + type: "uint256", + }, + ], + name: "LiquidationIncentiveUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "MarketEntered", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "MarketExited", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + ], + name: "MarketListed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "uAmount", + type: "uint256", + }, + ], + name: "MarketRedeem", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "uAmount", + type: "uint256", + }, + ], + name: "MarketSupply", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newRebateDistributor", + type: "address", + }, + ], + name: "RebateDistributorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "lToken", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "newSupplyCap", + type: "uint256", + }, + ], + name: "SupplyCapUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newValidator", + type: "address", + }, + ], + name: "ValidatorUpdated", + type: "event", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "accountLiquidityOf", + outputs: [ + { internalType: "uint256", name: "collateralInUSD", type: "uint256" }, + { internalType: "uint256", name: "supplyInUSD", type: "uint256" }, + { internalType: "uint256", name: "borrowInUSD", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "allMarkets", + outputs: [{ internalType: "address[]", name: "", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "borrow", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "borrower", type: "address" }, + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "borrowBehalf", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "lToken", type: "address" }, + ], + name: "checkMembership", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "claimLab", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "market", type: "address" }], + name: "claimLab", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "accounts", type: "address[]" }, + ], + name: "claimLabBehalf", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "closeFactor", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "lockDuration", type: "uint256" }, + ], + name: "compoundLab", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address[]", name: "lTokens", type: "address[]" }], + name: "enterMarkets", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "lToken", type: "address" }], + name: "exitMarket", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_priceCalculator", type: "address" }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "initialized", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "keeper", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "labDistributor", + outputs: [ + { internalType: "contract ILABDistributor", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "leverager", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lTokenBorrowed", type: "address" }, + { internalType: "address", name: "lTokenCollateral", type: "address" }, + { internalType: "address", name: "borrower", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "liquidateBorrow", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "liquidationIncentive", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address payable", name: "lToken", type: "address" }, + { internalType: "uint256", name: "supplyCap", type: "uint256" }, + { internalType: "uint256", name: "borrowCap", type: "uint256" }, + { internalType: "uint256", name: "collateralFactor", type: "uint256" }, + ], + name: "listMarket", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "lToken", type: "address" }], + name: "marketInfoOf", + outputs: [ + { + components: [ + { internalType: "bool", name: "isListed", type: "bool" }, + { internalType: "uint256", name: "supplyCap", type: "uint256" }, + { internalType: "uint256", name: "borrowCap", type: "uint256" }, + { + internalType: "uint256", + name: "collateralFactor", + type: "uint256", + }, + ], + internalType: "struct Constant.MarketInfo", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "marketInfos", + outputs: [ + { internalType: "bool", name: "isListed", type: "bool" }, + { internalType: "uint256", name: "supplyCap", type: "uint256" }, + { internalType: "uint256", name: "borrowCap", type: "uint256" }, + { internalType: "uint256", name: "collateralFactor", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "marketListOf", + outputs: [{ internalType: "address[]", name: "", type: "address[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "marketListOfUsers", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "markets", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "priceCalculator", + outputs: [ + { internalType: "contract IPriceCalculator", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rebateDistributor", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "lAmount", type: "uint256" }, + ], + name: "redeemToken", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "redeemUnderlying", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address payable", name: "lToken", type: "address" }, + ], + name: "removeMarket", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "repayBorrow", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "newCloseFactor", type: "uint256" }, + ], + name: "setCloseFactor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "newCollateralFactor", type: "uint256" }, + ], + name: "setCollateralFactor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_keeper", type: "address" }], + name: "setKeeper", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_labDistributor", type: "address" }, + ], + name: "setLABDistributor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_leverager", type: "address" }], + name: "setLeverager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "newLiquidationIncentive", + type: "uint256", + }, + ], + name: "setLiquidationIncentive", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "lTokens", type: "address[]" }, + { internalType: "uint256[]", name: "newBorrowCaps", type: "uint256[]" }, + ], + name: "setMarketBorrowCaps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "lTokens", type: "address[]" }, + { internalType: "uint256[]", name: "newSupplyCaps", type: "uint256[]" }, + ], + name: "setMarketSupplyCaps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_priceCalculator", type: "address" }, + ], + name: "setPriceCalculator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_rebateDistributor", type: "address" }, + ], + name: "setRebateDistributor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_validator", type: "address" }], + name: "setValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "supply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "supplier", type: "address" }, + { internalType: "address", name: "lToken", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "supplyBehalf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "address", name: "src", type: "address" }, + { internalType: "address", name: "dst", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferTokens", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "usersOfMarket", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "validator", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/adapters/layerbank/src/sdk/abi/helpers.ts b/adapters/layerbank/src/sdk/abi/helpers.ts new file mode 100644 index 0000000..ab08dcc --- /dev/null +++ b/adapters/layerbank/src/sdk/abi/helpers.ts @@ -0,0 +1,20 @@ +export const bigMath = { + abs(x: bigint) { + return x < 0n ? -x : x; + }, + sign(x: bigint) { + if (x === 0n) return 0n; + return x < 0n ? -1n : 1n; + }, + pow(base: bigint, exponent: bigint) { + return base ** exponent; + }, + min(value: bigint, ...values: bigint[]) { + for (const v of values) if (v < value) value = v; + return value; + }, + max(value: bigint, ...values: bigint[]) { + for (const v of values) if (v > value) value = v; + return value; + }, +}; diff --git a/adapters/layerbank/src/sdk/abi/ltoken.abi.ts b/adapters/layerbank/src/sdk/abi/ltoken.abi.ts new file mode 100644 index 0000000..e1ddce6 --- /dev/null +++ b/adapters/layerbank/src/sdk/abi/ltoken.abi.ts @@ -0,0 +1,656 @@ +export default [ + { inputs: [], 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: "amount", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "ammount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "accountBorrow", + type: "uint256", + }, + ], + name: "Borrow", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "liquidator", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "borrower", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "lTokenCollateral", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "seizeAmount", + type: "uint256", + }, + ], + name: "LiquidateBorrow", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "minter", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "mintAmount", + type: "uint256", + }, + ], + name: "Mint", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "underlyingAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "lTokenAmount", + type: "uint256", + }, + ], + name: "Redeem", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "payer", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "borrower", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "accountBorrow", + type: "uint256", + }, + ], + name: "RepayBorrow", + 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: "amount", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "_totalBorrow", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "accInterestIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "accountSnapshot", + outputs: [ + { + components: [ + { internalType: "uint256", name: "lTokenBalance", type: "uint256" }, + { internalType: "uint256", name: "borrowBalance", type: "uint256" }, + { internalType: "uint256", name: "exchangeRate", type: "uint256" }, + ], + internalType: "struct Constant.AccountSnapshot", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "accruedAccountSnapshot", + outputs: [ + { + components: [ + { internalType: "uint256", name: "lTokenBalance", type: "uint256" }, + { internalType: "uint256", name: "borrowBalance", type: "uint256" }, + { internalType: "uint256", name: "exchangeRate", type: "uint256" }, + ], + internalType: "struct Constant.AccountSnapshot", + name: "", + type: "tuple", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "accruedBorrowBalanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "accruedExchangeRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "accruedTotalBorrow", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "borrow", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "borrowBalanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "borrower", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "borrowBehalf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "core", + outputs: [{ internalType: "contract ICore", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "exchangeRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAccInterestIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCash", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getOwner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRateModel", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "_name", type: "string" }, + { internalType: "string", name: "_symbol", type: "string" }, + { internalType: "uint8", name: "_decimals", type: "uint8" }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "initialized", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "lastAccruedTime", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "lTokenCollateral", type: "address" }, + { internalType: "address", name: "liquidator", type: "address" }, + { internalType: "address", name: "borrower", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "liquidateBorrow", + outputs: [ + { internalType: "uint256", name: "seizeLAmount", type: "uint256" }, + { internalType: "uint256", name: "rebateLAmount", type: "uint256" }, + { internalType: "uint256", name: "liquidatorLAmount", type: "uint256" }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rateModel", + outputs: [ + { internalType: "contract IRateModel", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rebateDistributor", + outputs: [ + { + internalType: "contract IRebateDistributor", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "redeemer", type: "address" }, + { internalType: "uint256", name: "lAmount", type: "uint256" }, + ], + name: "redeemToken", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "redeemer", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "redeemUnderlying", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "repayBorrow", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "reserveFactor", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "liquidator", type: "address" }, + { internalType: "address", name: "borrower", type: "address" }, + { internalType: "uint256", name: "lAmount", type: "uint256" }, + ], + name: "seize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_core", type: "address" }], + name: "setCore", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_rateModel", type: "address" }], + name: "setRateModel", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_rebateDistributor", type: "address" }, + ], + name: "setRebateDistributor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_reserveFactor", type: "uint256" }, + ], + name: "setReserveFactor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_underlying", type: "address" }], + name: "setUnderlying", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "supply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "supplier", type: "address" }, + { internalType: "uint256", name: "uAmount", type: "uint256" }, + ], + name: "supplyBehalf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalBorrow", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalReserve", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "dst", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "src", type: "address" }, + { internalType: "address", name: "dst", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "address", name: "src", type: "address" }, + { internalType: "address", name: "dst", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferTokensInternal", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "underlying", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "underlyingBalanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "withdrawReserves", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; diff --git a/adapters/layerbank/src/sdk/config.ts b/adapters/layerbank/src/sdk/config.ts new file mode 100644 index 0000000..7f10f4a --- /dev/null +++ b/adapters/layerbank/src/sdk/config.ts @@ -0,0 +1,24 @@ +export const enum CHAINS { + SCROLL = 534352, +} + +export const enum PROTOCOLS { + LAYERBANK = 1, +} + +export const SUBGRAPH_URLS = { + [CHAINS.SCROLL]: { + [PROTOCOLS.LAYERBANK]: { + url: "https://api.goldsky.com/api/public/project_clwadadu5rddf01xa3m0m8ep0/subgraphs/layerbank_scroll/1.0.0/gn", + }, + }, +}; + +export const RPC_URLS = { + [CHAINS.SCROLL]: + "https://scroll-mainnet.blastapi.io/0f113c5d-4147-4d60-b20f-5915468a24c8", +}; + +export const WETH_ADDRESS = { + [CHAINS.SCROLL]: "0x5300000000000000000000000000000000000004", +}; diff --git a/adapters/layerbank/src/sdk/marketDetails.ts b/adapters/layerbank/src/sdk/marketDetails.ts new file mode 100644 index 0000000..4efa606 --- /dev/null +++ b/adapters/layerbank/src/sdk/marketDetails.ts @@ -0,0 +1,150 @@ +import { createPublicClient, extractChain, http, getContract } from "viem"; +import { CHAINS, RPC_URLS, WETH_ADDRESS } from "./config"; +import { scroll } from "viem/chains"; +import coreAbi from "./abi/core.abi"; +import ltokenAbi from "./abi/ltoken.abi"; +import { AccountState } from "./subgraphDetails"; + +export interface MarketInfo { + address: string; + underlyingAddress: string; + underlyingSymbol: string; + exchangeRateStored: bigint; +} + +export const getMarketInfos = async ( + coreAddress: `0x${string}`, + blockNumber?: bigint +) => { + const publicClient = createPublicClient({ + chain: extractChain({ chains: [scroll], id: CHAINS.SCROLL }), + transport: http(RPC_URLS[CHAINS.SCROLL]), + }); + + const core = getContract({ + address: coreAddress, + abi: coreAbi, + client: publicClient, + }); + + const marketAddresses = await core.read.allMarkets(); + + const markets = marketAddresses.map((m) => + getContract({ + address: m, + abi: ltokenAbi, + client: publicClient, + }) + ); + + const underlyingResults = await publicClient.multicall({ + contracts: markets.map((m) => ({ + address: m.address, + abi: m.abi, + functionName: "underlying", + })) as any, + }); + + const underlyingAddresses = underlyingResults + .map((v) => v.result as `0x${string}`) + .map((m) => { + if (m === "0x0000000000000000000000000000000000000000") { + return WETH_ADDRESS[CHAINS.SCROLL].toLowerCase(); + } else { + return m.toLowerCase(); + } + }); + + const underlyings = underlyingAddresses.map((m) => + getContract({ + address: m as `0x${string}`, + abi: ltokenAbi, + client: publicClient, + }) + ); + + const underlyingSymbolResults = await publicClient.multicall({ + contracts: underlyings.map((m) => ({ + address: (m as any).address, + abi: (m as any).abi, + functionName: "symbol", + })) as any, + }); + + const exchangeRateResults = await publicClient.multicall({ + contracts: markets.map((m) => ({ + address: m.address, + abi: m.abi, + functionName: "exchangeRate", + })) as any, + blockNumber, + }); + + const marketInfos: MarketInfo[] = []; + + for (let i = 0; i < markets.length; i++) { + const marketAddress = markets[i].address.toLowerCase(); + const underlyingAddress = underlyingAddresses[i]; + + marketInfos.push({ + address: marketAddress, + underlyingAddress, + underlyingSymbol: underlyingSymbolResults[i].result as any, + exchangeRateStored: BigInt( + exchangeRateResults[i].status === "success" + ? (exchangeRateResults[i].result as any) + : 0 + ), + }); + } + + return marketInfos; +}; + +export const updateBorrowBalances = async ( + states: AccountState[], + blockNumber?: bigint +) => { + const marketInfos = await getMarketInfos( + "0xEC53c830f4444a8A56455c6836b5D2aA794289Aa" + ); + const marketsByUnderlying: any = {}; + for (let marketInfo of marketInfos) { + marketsByUnderlying[marketInfo.underlyingAddress] = marketInfo.address; + } + + const publicClient = createPublicClient({ + chain: extractChain({ chains: [scroll], id: CHAINS.SCROLL }), + transport: http(RPC_URLS[CHAINS.SCROLL]), + }); + + states = states.filter((x) => x.borrowAmount > 0); + + console.log(`Will update all borrow balances for ${states.length} states`); + for (var i = 0; i < states.length; i += 500) { + const start = i; + const end = i + 500; + var subStates = states.slice(start, end); + console.log(`Updating borrow balances for ${start} - ${end}`); + + const borrowBalanceResults = await publicClient.multicall({ + contracts: subStates + .map((m) => [ + { + address: marketsByUnderlying[m.token], + abi: ltokenAbi, + functionName: "borrowBalanceOf", + args: [m.account], + }, + ]) + .flat() as any, + blockNumber, + }); + + for (var j = 0; j < subStates.length; j++) { + subStates[j].borrowAmount = BigInt( + borrowBalanceResults[j].result?.toString() ?? 0 + ); + } + } +}; diff --git a/adapters/layerbank/src/sdk/subgraphDetails.ts b/adapters/layerbank/src/sdk/subgraphDetails.ts new file mode 100644 index 0000000..b4b56bf --- /dev/null +++ b/adapters/layerbank/src/sdk/subgraphDetails.ts @@ -0,0 +1,153 @@ +import { Account, createPublicClient, extractChain, http } from "viem"; +import { scroll } from "viem/chains"; +import { + CHAINS, + PROTOCOLS, + RPC_URLS, + SUBGRAPH_URLS, + WETH_ADDRESS, +} from "./config"; +import { getMarketInfos } from "./marketDetails"; + +// Define the zero address and dead address constants +const zeroAddress = "0x0000000000000000000000000000000000000000"; +const deadAddress = "0x000000000000000000000000000000000000dead"; + +export interface AccountState { + id: string; + account: string; + token: string; + lentAmount: bigint; + borrowAmount: bigint; +} + +export const getAccountStatesForAddressByPoolAtBlock = async ( + blockNumber: number, + address: string, + poolId: string, + chainId: CHAINS, + protocol: PROTOCOLS +): Promise => { + const marketInfos = await getMarketInfos( + "0xEC53c830f4444a8A56455c6836b5D2aA794289Aa", + BigInt(blockNumber) + ); + address = address?.toLowerCase(); + + const marketsToUnderlying: any = {}; + for (let marketInfo of marketInfos) { + marketsToUnderlying[marketInfo.address] = marketInfo.underlyingAddress; + } + + let accountWhereQuery = address ? `account: "${address}" \n` : ""; + let amountWhereQuery = orWhere("supplied_gt: 0", "borrowed_gt: 0"); + + let skip = 0; + let fetchNext = true; + + const pageSize = 1000; + const url = SUBGRAPH_URLS[chainId][protocol].url; + + let states: AccountState[] = []; + + while (fetchNext) { + let query = ` + query TVLQuery { + accountStates( + block: {number: ${blockNumber}} + where: { + ${andWhere(accountWhereQuery, amountWhereQuery)} + } + orderBy: id + first: ${pageSize} + skip: ${skip} + ) { + id + account + token + supplied + borrowed + } + } + `; + console.log(`Fetching ${skip} - ${skip + pageSize} / unknown`); + + let response = await fetch(url, { + method: "POST", + body: JSON.stringify({ query }), + headers: { "Content-Type": "application/json" }, + }); + let data = await response.json(); + + // Filter and map the account states + const filteredAccountStates = data.data.accountStates + .filter( + (m: any) => + m.account !== zeroAddress && m.account.toLowerCase() !== deadAddress + ) + .map((m: any) => ({ + id: m.id.toLowerCase(), + account: m.account.toLowerCase(), + token: + marketsToUnderlying[ + m.token === zeroAddress + ? WETH_ADDRESS[CHAINS.SCROLL].toLowerCase() + : m.token + ].toLowerCase(), + lentAmount: BigInt(m.supplied), + borrowAmount: BigInt(m.borrowed), + })); + + // Push the filtered and mapped states into the states array + states.push(...filteredAccountStates); + + if (data.data.accountStates.length == 0) { + fetchNext = false; + } else { + skip += pageSize; + } + } + + states = states + .map((state: AccountState) => { + const marketInfo = marketInfos.find( + (mi) => mi.underlyingAddress == state.token.toLowerCase() + ); + if (!marketInfo) { + console.log(`${state.token} not found`); + return undefined; + } + + return { + ...state, + lentAmount: + (state.lentAmount * marketInfo.exchangeRateStored) / BigInt(1e18), + }; + }) + .filter((x) => x !== undefined) as AccountState[]; + + return states; +}; + +export const getTimestampAtBlock = async (blockNumber: number) => { + const publicClient = createPublicClient({ + chain: extractChain({ chains: [scroll], id: CHAINS.SCROLL }), + transport: http(RPC_URLS[CHAINS.SCROLL]), + }); + + const block = await publicClient.getBlock({ + blockNumber: BigInt(blockNumber), + }); + return Number(block.timestamp * 1000n); +}; + +const andWhere = (...queries: string[]) => { + return `and: [ + ${queries.map((q) => `{ ${q} }`).join("\n")} + ]`; +}; +const orWhere = (...queries: string[]) => { + return `or: [ + ${queries.map((q) => `{ ${q} }`).join("\n")} +]`; +}; diff --git a/adapters/layerbank/tsconfig.json b/adapters/layerbank/tsconfig.json new file mode 100644 index 0000000..599cd43 --- /dev/null +++ b/adapters/layerbank/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "commonjs", + "rootDir": "src/", + "outDir": "dist/", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}