diff --git a/projects/aera/index.js b/projects/aera/index.js index 06d3e4592e..344b5b3721 100644 --- a/projects/aera/index.js +++ b/projects/aera/index.js @@ -9,11 +9,13 @@ const GEARBOX_TOKEN_PREFIX = 'Farming of' const ARRAKIS_TOKEN_PREFIX = 'Arrakis Vault V2' const ESXAI_POSITION_ORACLE_NAME = 'EsXai Position Oracle' const SYMBIOTIC_TOKEN_PREFIX = 'Symbiotic Vault' +const MORPHO_BLUE_POSITION_ORACLE_NAME = 'MorphoBluePositionOracle' const config = { polygon: { aavePool: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', aavePoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654', + morphoBlue: '0x1bF0c2541F820E775182832f06c0B7Fc27A25f67', comets: [ { address: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', @@ -44,6 +46,7 @@ const config = { ethereum: { aavePool: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', aavePoolDataProvider: '0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3', + morphoBlue: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb', comets: [ { address: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', @@ -82,6 +85,7 @@ const config = { arbitrum: { aavePool: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', aavePoolDataProvider: '0x6b4E260b765B3cA1514e618C0215A6B7839fF93e', + morphoBlue: '0x6c247b1F6182318877311737BaC0844bAa518F5e', comets: [ { address: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', @@ -111,6 +115,7 @@ const config = { base: { aavePool: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', aavePoolDataProvider: '0x2d8A3C5677189723C4cB8873CfC9C8976FDF38Ac', + morphoBlue: '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb', comets: [ { address: '0x46e6b214b524310239732D51387075E0e70970bf', @@ -148,6 +153,7 @@ Object.keys(config).forEach(chain => { tvl: async (api) => { const AAVE_POOL = config[chain].aavePool const AAVE_POOL_DATA_PROVIDER = config[chain].aavePoolDataProvider + const MORPHO_BLUE = config[chain].morphoBlue const COMETS = config[chain].comets const COMET_REWARD = config[chain].cometReward const ARRAKIS_HELPER = config[chain].arrakisHelper @@ -192,7 +198,7 @@ Object.keys(config).forEach(chain => { const xaiPositionVaults = [] const esXaiVaults = [] const symbioticVaults = [] - + const morphoBlueVaults = [] for (let i = 0; i < vaults.length; ++i) { const vault = vaults[i] for (let j = 0; j < assets[i].length; ++j) { @@ -228,6 +234,10 @@ Object.keys(config).forEach(chain => { symbioticVaults.push([vault, assetInfo.asset]) continue } + if (assetName === MORPHO_BLUE_POSITION_ORACLE_NAME) { + morphoBlueVaults.push([vault, assetInfo.asset]) + continue + } } @@ -263,7 +273,8 @@ Object.keys(config).forEach(chain => { processGearboxTvl(gearboxFarmingPools, api), processArrakisTvl(arrakisVaults, api, ARRAKIS_HELPER), processXaiTvl(xaiPositionVaults, api, ESXAI_POOL_FACTORY, XAI), - processSymbioticTvl(symbioticVaults, api) + processSymbioticTvl(symbioticVaults, api), + processMorphoBlueTvl(morphoBlueVaults, api, MORPHO_BLUE) ]) Object.keys(erc4626UnderylingMap).forEach((erc4626Asset, i) => erc4626UnderylingMap[erc4626Asset] = underlyingTokens[i]) @@ -282,7 +293,7 @@ Object.keys(config).forEach(chain => { async function processXaiTvl(xaiPositionVaults, api, ESXAI_POOL_FACTORY, XAI) { if (xaiPositionVaults.length === 0) return - + const pools = await api.multiCall({ abi: abi.getPoolIndicesOfUser, calls: xaiPositionVaults.map(x => ({target: ESXAI_POOL_FACTORY, params: [x]}))}) const vaultPools = xaiPositionVaults.flatMap((vault, i) => pools[i].map(pool => ([vault, pool]))) @@ -310,7 +321,7 @@ async function processArrakisTvl(arrakisVaults, api, arrakisHelper) { api.multiCall({ abi: abi.token1, calls: arrakisVaults}), api.multiCall({ abi: abi.totalUnderlying, calls: arrakisVaults.map(x => ({target: arrakisHelper, params: [x]}))}) ]) - + totalUnderlyings.forEach((v, i) => { api.addToken(tokens0[i], v.totalAmount0) api.addToken(tokens1[i], v.totalAmount1) @@ -413,7 +424,7 @@ async function processAaveTvl(aaveVaults, api, AAVE_POOL, AAVE_POOL_DATA_PROVIDE api.addToken(aaveReserveDetails[reserveIdx].stableDebtTokenAddress, aavePosition.currentStableDebt); api.addToken(aaveReserveDetails[reserveIdx].variableDebtTokenAddress, aavePosition.currentVariableDebt); } - + } async function processSymbioticTvl(symbioticVaults, api) { @@ -428,6 +439,100 @@ async function processSymbioticTvl(symbioticVaults, api) { }) } +async function processMorphoBlueTvl(morphoBlueVaults, api, MORPHO_BLUE) { + if (morphoBlueVaults.length === 0) return; + + // Get the number of markets for each oracle + const marketLengths = await api.multiCall({ + abi: abi.getMarketsLength, + calls: morphoBlueVaults.map(x => ({target: x[1], params: []})) + }); + + + // Build calls to get each market by index + const marketCalls = []; + morphoBlueVaults.forEach(([vault, oracle], oracleIndex) => { + const length = marketLengths[oracleIndex]; + for (let i = 0; i < length; i++) { + marketCalls.push({ + target: oracle, + params: [i], + vault: vault, + oracleIndex: oracleIndex + }); + } + }); + + if (marketCalls.length === 0) return; + + // Get all markets from the Morpho Blue position oracles + const markets = await api.multiCall({ abi: abi.markets, calls: marketCalls }); + + const [marketInfos, marketParams] = await Promise.all( + [ + api.multiCall({ + abi: abi.market, + calls: markets.map(market => ({target: MORPHO_BLUE, params: [market[0]]})) + }), + api.multiCall({ + abi: abi.idToMarketParams, + calls: markets.map(market => ({ target: MORPHO_BLUE, params: [market[0]] })) + }) + ] + ); + + // Get positions for each vault across all markets + const positionCalls = []; + marketCalls.forEach((call, index) => { + const marketId = markets[index][0]; // Extract the market ID (bytes32) + positionCalls.push({ + target: MORPHO_BLUE, + params: [marketId, call.vault] + }); + }); + + const positions = await api.multiCall({ + abi: abi.position, + calls: positionCalls + }); + + // Process positions and add to TVL + await Promise.all(positionCalls.map(async (call, index) => { + const [_, vault] = call.params; + const position = positions[index]; + const marketParam = marketParams[index]; + const marketInfo = marketInfos[index]; + + if (position.collateral > 0) { + try { + // If the collateral token is an ERC4626, we need to convert it to the underlying asset + const [underlyingAsset, collateralAssets] = await Promise.all([ + api.call({ abi: 'address:asset', target: marketParam.collateralToken }), + api.call({ abi: abi.convertToAssets, target: marketParam.collateralToken, params: [position.collateral] }) + ]); + api.addToken(underlyingAsset, collateralAssets, vault); + } catch (e) { + api.addToken(marketParam.collateralToken, position.collateral, vault); + } + } + + if (position.supplyShares > 0) { + const supplyAssets = ( + BigInt(position.supplyShares) * + BigInt(marketInfo.totalSupplyAssets) / BigInt(marketInfo.totalSupplyShares) + ); + api.addToken(marketParam.loanToken, supplyAssets, vault); + } + if (position.borrowShares > 0) { + const borrowAssets = ( + BigInt(position.borrowShares) * + BigInt(marketInfo.totalBorrowAssets) / BigInt(marketInfo.totalBorrowShares) + ); + api.addToken(marketParam.loanToken, -borrowAssets, vault); + } + })); +} + const abi = { "collateralBalanceOf": "function collateralBalanceOf(address account, address asset) view returns (uint128)", "getReserveData": "function getReserveData(address asset) view returns (((uint256 data) configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt))", @@ -458,5 +563,10 @@ const abi = { "esXaiStakeBucket": "function esXaiStakeBucket() returns (address)", "getStakedAmounts": "function getStakedAmounts(address) returns (uint256)", "withdrawableDividendOf": "function withdrawableDividendOf(address) returns (uint256)", - "collateral": "function collateral() returns (address)" + "collateral": "function collateral() returns (address)", + "getMarketsLength": "function getMarketsLength() view returns (uint256)", + "markets": "function markets(uint256) view returns ((bytes32, address, uint80, uint8, bool))", + "idToMarketParams": "function idToMarketParams(bytes32) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", + "position": "function position(bytes32, address) view returns (uint256 supplyShares, uint256 borrowShares, uint256 collateral)", + "market": "function market(bytes32) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares)" } \ No newline at end of file