|
| 1 | +const sdk = require('@defillama/sdk'); |
| 2 | +const axios = require('axios'); |
| 3 | +const { sumTokens2, addUniV3LikePosition } = require('../helper/unwrapLPs'); |
| 4 | +const { nullAddress } = require('../helper/tokenMapping'); |
| 5 | + |
| 6 | +const config = { |
| 7 | + arbitrum: { |
| 8 | + factories: [ |
| 9 | + '0x4FF3262Ba2983Ee8950d9d082f03277a58BF7eb1' |
| 10 | + ], |
| 11 | + positionsApi: 'https://arbitrum-nftlp-uniswapv3.up.railway.app/positions' |
| 12 | + }, |
| 13 | + base: { |
| 14 | + factories: [ |
| 15 | + '0x175712cD666FbcfE8B69866a3088D7bf17a47685', |
| 16 | + ], |
| 17 | + positionsApi: 'https://base-nftlp-uniswapv3.up.railway.app/positions' |
| 18 | + }, |
| 19 | +}; |
| 20 | + |
| 21 | +const blacklistedPools = { |
| 22 | + arbitrum: [], |
| 23 | + base: [] |
| 24 | +}; |
| 25 | + |
| 26 | +const abi = { |
| 27 | + // Borrowables |
| 28 | + underlying: "address:underlying", |
| 29 | + totalBalance: "uint256:totalBalance", |
| 30 | + totalBorrows: "function totalBorrows() view returns (uint112)", |
| 31 | + |
| 32 | + // Lending Pools |
| 33 | + allLendingPoolsLength: "uint256:allLendingPoolsLength", |
| 34 | + allLendingPools: "function allLendingPools(uint256) view returns (address)", |
| 35 | + getLendingPool: "function getLendingPool(address) view returns (bool initialized, uint24 lendingPoolId, address collateral, address borrowable0, address borrowable1)", |
| 36 | + |
| 37 | + // UniV3 pools |
| 38 | + slot0: "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)", |
| 39 | +}; |
| 40 | + |
| 41 | +async function tvl(api) { |
| 42 | + const { factories, positionsApi } = config[api.chain]; |
| 43 | + const blacklist = blacklistedPools[api.chain] || []; |
| 44 | + |
| 45 | + // All NFTLP positions |
| 46 | + if (positionsApi) { |
| 47 | + await processUniV3Positions(api, positionsApi); |
| 48 | + } |
| 49 | + |
| 50 | + await processBorrowables(api, factories, blacklist); |
| 51 | + |
| 52 | + return api.getBalances(); |
| 53 | +} |
| 54 | + |
| 55 | +async function processUniV3Positions(api, positionsApiUrl) { |
| 56 | + console.log(`Fetching positions from API: ${positionsApiUrl}`); |
| 57 | + const response = await axios.get(positionsApiUrl); |
| 58 | + const positions = response.data; |
| 59 | + |
| 60 | + if (!Array.isArray(positions) || positions.length === 0) { |
| 61 | + console.log(`No positions found with api ${positionsApiUrl}`); |
| 62 | + return; |
| 63 | + } |
| 64 | + |
| 65 | + const poolsMap = {}; |
| 66 | + positions.forEach(position => { |
| 67 | + if (!position.uniswapV3PoolId || position.liquidity === '0') return; |
| 68 | + |
| 69 | + const poolId = position.uniswapV3PoolId.toLowerCase(); |
| 70 | + if (!poolsMap[poolId]) { |
| 71 | + poolsMap[poolId] = { |
| 72 | + token0: position.token0Id, |
| 73 | + token1: position.token1Id, |
| 74 | + positions: [] |
| 75 | + }; |
| 76 | + } |
| 77 | + |
| 78 | + poolsMap[poolId].positions.push({ |
| 79 | + liquidity: position.liquidity, |
| 80 | + tickLower: position.tickLower, |
| 81 | + tickUpper: position.tickUpper |
| 82 | + }); |
| 83 | + }); |
| 84 | + |
| 85 | + // Get tick of all unique pools |
| 86 | + const poolAddresses = Object.keys(poolsMap); |
| 87 | + const slot0 = await api.multiCall({ |
| 88 | + abi: abi.slot0, |
| 89 | + calls: poolAddresses, |
| 90 | + permitFailure: true |
| 91 | + }); |
| 92 | + |
| 93 | + poolAddresses.forEach((poolAddress, i) => { |
| 94 | + const poolData = poolsMap[poolAddress]; |
| 95 | + const slotData = slot0[i]; |
| 96 | + |
| 97 | + poolData.positions.forEach(position => { |
| 98 | + addUniV3LikePosition({ |
| 99 | + api, |
| 100 | + token0: poolData.token0, |
| 101 | + token1: poolData.token1, |
| 102 | + tick: slotData.tick, |
| 103 | + liquidity: position.liquidity, |
| 104 | + tickLower: position.tickLower, |
| 105 | + tickUpper: position.tickUpper |
| 106 | + }); |
| 107 | + }); |
| 108 | + }); |
| 109 | +} |
| 110 | + |
| 111 | +// Same as impermax-v2 adapter |
| 112 | +async function processBorrowables(api, factories, blacklist) { |
| 113 | + const pools = []; |
| 114 | + |
| 115 | + await Promise.all(factories.map(async (factory) => { |
| 116 | + const lendingPools = await api.fetchList({ |
| 117 | + lengthAbi: abi.allLendingPoolsLength, |
| 118 | + itemAbi: abi.allLendingPools, |
| 119 | + target: factory |
| 120 | + }); |
| 121 | + |
| 122 | + const filteredPools = lendingPools.filter(pool => !blacklist.includes(pool.toLowerCase())); |
| 123 | + |
| 124 | + const poolData = await api.multiCall({ |
| 125 | + target: factory, |
| 126 | + abi: abi.getLendingPool, |
| 127 | + calls: filteredPools, |
| 128 | + permitFailure: true, |
| 129 | + }); |
| 130 | + |
| 131 | + const initializedPools = poolData.filter(pool => pool.initialized); |
| 132 | + initializedPools.forEach(i => { |
| 133 | + pools.push(i.borrowable0, i.borrowable1); |
| 134 | + }); |
| 135 | + })); |
| 136 | + |
| 137 | + const underlyings = await api.multiCall({ |
| 138 | + abi: abi.underlying, |
| 139 | + calls: pools, |
| 140 | + permitFailure: true, |
| 141 | + }); |
| 142 | + |
| 143 | + const tokensAndOwners = pools |
| 144 | + .map((owner, i) => [underlyings[i], owner]) |
| 145 | + .filter(([token]) => token !== nullAddress); |
| 146 | + |
| 147 | + return sumTokens2({ api, tokensAndOwners }); |
| 148 | +} |
| 149 | + |
| 150 | +// Same as impermax-v2 adapter |
| 151 | +async function borrowed(api) { |
| 152 | + const { factories } = config[api.chain] |
| 153 | + const blacklist = blacklistedPools[api.chain] |
| 154 | + const balances = {} |
| 155 | + const borrowables = [] |
| 156 | + await Promise.all(factories.map(async (factory) => { |
| 157 | + const lendingPools = await api.fetchList({ lengthAbi: abi.allLendingPoolsLength, itemAbi: abi.allLendingPools, target: factory }) |
| 158 | + |
| 159 | + const filteredPoolData = lendingPools.filter(pool => { |
| 160 | + return !blacklist.includes(pool.toLowerCase()) |
| 161 | + }) |
| 162 | + |
| 163 | + const poolData = await api.multiCall({ |
| 164 | + target: factory, |
| 165 | + abi: abi.getLendingPool, |
| 166 | + calls: filteredPoolData, |
| 167 | + permitFailure: true, |
| 168 | + }) |
| 169 | + |
| 170 | + poolData.forEach(i => { |
| 171 | + borrowables.push(i.borrowable0, i.borrowable1) |
| 172 | + }) |
| 173 | + })) |
| 174 | + |
| 175 | + const underlyings = await api.multiCall({ |
| 176 | + abi: abi.underlying, |
| 177 | + calls: borrowables, |
| 178 | + permitFailure: true, |
| 179 | + }) |
| 180 | + |
| 181 | + const borrowed = await api.multiCall({ |
| 182 | + abi: abi.totalBorrows, |
| 183 | + calls: borrowables, |
| 184 | + permitFailure: true, |
| 185 | + }) |
| 186 | + |
| 187 | + underlyings.forEach((v, i) => { |
| 188 | + sdk.util.sumSingleBalance(balances, v, borrowed[i], api.chain) |
| 189 | + }) |
| 190 | + return balances |
| 191 | +} |
| 192 | + |
| 193 | + |
| 194 | +const module_exports = {}; |
| 195 | + |
| 196 | +Object.keys(config).forEach(chain => { |
| 197 | + module_exports[chain] = { |
| 198 | + tvl, |
| 199 | + borrowed |
| 200 | + }; |
| 201 | +}); |
| 202 | + |
| 203 | +module.exports = module_exports; |
0 commit comments