UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

495 lines 21.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMorphoVaultsData = getMorphoVaultsData; exports.getMorphoVaultsRewards = getMorphoVaultsRewards; const dayjs_1 = __importDefault(require("dayjs")); const utc_js_1 = __importDefault(require("dayjs/plugin/utc.js")); const viem_1 = require("viem"); dayjs_1.default.extend(utc_js_1.default); const amount_js_1 = require("../../../common/amount.js"); const index_js_1 = require("../../../environments/index.js"); const index_js_2 = require("../../../environments/utils/index.js"); const graphql_js_1 = require("../utils/graphql.js"); const math_js_1 = require("../utils/math.js"); async function getMorphoVaultsData(params) { const { environments } = params; const environmentsWithVaults = environments.filter((environment) => Object.keys(environment.vaults).length > 0 && environment.contracts.morphoViews); const environmentsVaultsInfo = await Promise.all(environmentsWithVaults.map((environment) => { const vaultsAddresses = Object.values(environment.vaults) .map((v) => v.address) .filter((address) => params.vaults ? params.vaults .map((id) => id.toLowerCase()) .includes(address.toLowerCase()) : true); return environment.contracts.morphoViews?.read.getVaultsInfo([ vaultsAddresses, ]); })); const result = await environmentsWithVaults.reduce(async (aggregatorPromise, environment, environmentIndex) => { const aggregator = await aggregatorPromise; const environmentVaultsInfo = environmentsVaultsInfo[environmentIndex]; const vaults = await Promise.all(environmentVaultsInfo.map(async (vaultInfo) => { const vaultKey = Object.keys(environment.config.tokens).find((key) => { return (environment.config.tokens[key].address.toLowerCase() === vaultInfo.vault.toLowerCase()); }); const vaultToken = environment.config.tokens[vaultKey]; const vaultConfig = environment.config.vaults[vaultKey]; const underlyingToken = environment.config.tokens[vaultConfig.underlyingToken]; const underlyingPrice = new amount_js_1.Amount(vaultInfo.underlyingPrice, 18); const vaultSupply = new amount_js_1.Amount(vaultInfo.totalSupply, vaultToken.decimals); const totalSupply = new amount_js_1.Amount(vaultInfo.totalAssets, underlyingToken.decimals); const totalSupplyUsd = totalSupply.value * underlyingPrice.value; const performanceFee = new amount_js_1.Amount(vaultInfo.fee, 18).value; const timelock = Number(vaultInfo.timelock) / (60 * 60); let ratio = 0n; const markets = vaultInfo.markets.map((vaultMarket) => { ratio += (0, math_js_1.wMulDown)(vaultMarket.marketApy, vaultMarket.vaultSupplied); const totalSupplied = new amount_js_1.Amount(vaultMarket.vaultSupplied, underlyingToken.decimals); const totalSuppliedUsd = totalSupplied.value * underlyingPrice.value; const allocation = totalSupplied.value / totalSupply.value; const marketLoanToValue = new amount_js_1.Amount(vaultMarket.marketLltv, 18).value * 100; const marketApy = new amount_js_1.Amount(vaultMarket.marketApy, 18).value * 100; let marketLiquidity = new amount_js_1.Amount(vaultMarket.marketLiquidity, underlyingToken.decimals); let marketLiquidityUsd = marketLiquidity.value * underlyingPrice.value; if (vaultMarket.marketCollateral === viem_1.zeroAddress) { marketLiquidity = totalSupplied; marketLiquidityUsd = totalSuppliedUsd; } const mapping = { marketId: vaultMarket.marketId, allocation, marketApy, marketCollateral: { address: vaultMarket.marketCollateral, decimals: 0, name: vaultMarket.marketCollateralName, symbol: vaultMarket.marketCollateralSymbol, }, marketLiquidity, marketLiquidityUsd, marketLoanToValue, totalSupplied, totalSuppliedUsd, rewards: [], }; return mapping; }); const avgSupplyApy = (0, math_js_1.mulDivDown)(ratio, math_js_1.WAD - vaultInfo.fee, vaultInfo.totalAssets === 0n ? 1n : vaultInfo.totalAssets); const baseApy = new amount_js_1.Amount(avgSupplyApy, 18).value * 100; let totalLiquidity = new amount_js_1.Amount(markets.reduce((acc, curr) => acc + curr.marketLiquidity.exponential, 0n), underlyingToken.decimals); let totalLiquidityUsd = markets.reduce((acc, curr) => acc + curr.marketLiquidityUsd, 0); if (totalLiquidity.value > totalSupply.value) { totalLiquidity = totalSupply; totalLiquidityUsd = totalSupplyUsd; } let totalStaked = new amount_js_1.Amount(0n, underlyingToken.decimals); let totalStakedUsd = 0; if (vaultConfig.multiReward) { const [distributorTotalSupply, vaultTotalSupply, vaultTotalAssets] = await Promise.all([ getTotalSupplyData(environment, vaultConfig.multiReward), getTotalSupplyData(environment, vaultToken.address), getTotalAssetsData(environment, vaultToken.address), ]); const stakedAssets = (0, math_js_1.toAssetsDown)(distributorTotalSupply, vaultTotalAssets, vaultTotalSupply); totalStaked = new amount_js_1.Amount(stakedAssets, underlyingToken.decimals); totalStakedUsd = totalStaked.value * underlyingPrice.value; } const mapping = { chainId: environment.chainId, vaultKey: vaultKey, vaultToken, underlyingToken, underlyingPrice: underlyingPrice.value, baseApy, totalApy: baseApy, rewardsApy: 0, stakingRewardsApr: 0, totalStakingApr: baseApy, curators: [], performanceFee, timelock, totalLiquidity, totalLiquidityUsd, totalSupplyUsd, totalSupply, vaultSupply, totalStaked, totalStakedUsd, markets: markets, rewards: [], stakingRewards: [], }; return mapping; })); return { ...(await aggregator), [environment.chainId]: vaults, }; }, Promise.resolve({})); if (params.includeRewards === true) { const flatList = Object.values(result).flat(); for (const vault of flatList) { const environment = params.environments.find((environment) => environment.chainId === vault.chainId); if (!environment) { continue; } const homeEnvironment = Object.values(index_js_1.publicEnvironments).find((e) => e.custom?.governance?.chainIds?.includes(environment.chainId)) || environment; const viewsContract = environment.contracts.views; const homeViewsContract = homeEnvironment.contracts.views; const data = await Promise.all([ viewsContract?.read.getAllMarketsInfo(), homeViewsContract?.read.getNativeTokenPrice(), homeViewsContract?.read.getGovernanceTokenPrice(), ]); const [allMarkets, nativeTokenPriceRaw, governanceTokenPriceRaw] = data; const governanceTokenPrice = new amount_js_1.Amount(governanceTokenPriceRaw || 0n, 18); const nativeTokenPrice = new amount_js_1.Amount(nativeTokenPriceRaw || 0n, 18); let tokenPrices = allMarkets ?.map((marketInfo) => { const marketFound = (0, index_js_2.findMarketByAddress)(environment, marketInfo.market); if (marketFound) { return { token: marketFound.underlyingToken, tokenPrice: new amount_js_1.Amount(marketInfo.underlyingPrice, 36 - marketFound.underlyingToken.decimals), }; } else { return; } }) .filter((token) => !!token) || []; if (environment.custom?.governance?.token) { tokenPrices = [ ...tokenPrices, { token: environment.config.tokens[environment.custom.governance.token], tokenPrice: governanceTokenPrice, }, ]; } tokenPrices = [ ...tokenPrices, { token: (0, index_js_2.findTokenByAddress)(environment, viem_1.zeroAddress), tokenPrice: nativeTokenPrice, }, ]; const vaultConfig = environment?.config.vaults[vault.vaultKey]; if (!environment || !vaultConfig || !vaultConfig.multiReward) { continue; } const rewards = await getRewardsData(environment, vaultConfig.multiReward); const distributorTotalSupply = await getTotalSupplyData(environment, vaultConfig.multiReward); rewards .filter((reward) => reward?.periodFinish && dayjs_1.default.utc().isBefore(dayjs_1.default.unix(Number(reward.periodFinish)))) .forEach((reward) => { const token = Object.values(environment.config.tokens).find((token) => token.address === reward?.token); if (!token || !reward?.rewardRate) return; const market = tokenPrices.find((m) => m?.token.address === reward.token); const rewardPriceUsd = market?.tokenPrice.value ?? 0; const rewardsPerYear = new amount_js_1.Amount(reward.rewardRate, market?.token.decimals ?? 18).value * math_js_1.SECONDS_PER_YEAR * rewardPriceUsd; vault.stakingRewards.push({ apr: (rewardsPerYear / (new amount_js_1.Amount(distributorTotalSupply, vault.vaultToken.decimals) .value * vault.underlyingPrice)) * 100, token: token, }); }); vault.stakingRewardsApr = vault.stakingRewards.reduce((acc, curr) => acc + curr.apr, 0); vault.totalStakingApr = vault.stakingRewardsApr + vault.baseApy; } const vaults = Object.values(result) .flat() .filter((vault) => { const environment = params.environments.find((environment) => environment.chainId === vault.chainId); return environment?.custom.morpho?.minimalDeployment === false; }); const rewards = await getMorphoVaultsRewards(vaults, params.currentChainRewardsOnly); vaults.forEach((vault) => { const vaultRewards = rewards.find((reward) => reward.vaultToken.address === vault.vaultToken.address && reward.chainId === vault.chainId); vault.rewards = vaultRewards?.rewards .filter((reward) => reward.marketId === undefined) .map((reward) => ({ asset: reward.asset, supplyApr: reward.supplyApr, supplyAmount: reward.supplyAmount, borrowApr: reward.borrowApr, borrowAmount: reward.borrowAmount, })) || []; vault.markets.forEach((market) => { const marketRewards = vaultRewards?.rewards .filter((reward) => reward.marketId === market.marketId) .map((reward) => ({ asset: reward.asset, supplyApr: reward.supplyApr, supplyAmount: reward.supplyAmount, borrowApr: reward.borrowApr, borrowAmount: reward.borrowAmount, })) || []; market.rewards = marketRewards; market.rewards.forEach((reward) => { const supplyApr = reward.supplyApr * market.allocation; const supplyAmount = reward.supplyAmount * market.allocation; const vaultReward = vault.rewards.find((r) => r.asset.address === reward.asset.address); if (vaultReward) { vaultReward.supplyApr += supplyApr; vaultReward.supplyAmount += supplyAmount; } else { vault.rewards.push({ asset: reward.asset, supplyApr, supplyAmount, borrowApr: 0, borrowAmount: 0, }); } }); }); vault.rewardsApy = vault.rewards.reduce((acc, curr) => acc + curr.supplyApr, 0); vault.totalApy = vault.rewardsApy + vault.baseApy; }); } return environments.flatMap((environment) => { return result[environment.chainId] || []; }); } const getRewardsData = async (environment, multiRewardsAddress) => { if (!environment.custom.multiRewarder) { return []; } const multiRewardAbi = (0, viem_1.parseAbi)([ "function rewardData(address token) view returns (address, uint256, uint256, uint256, uint256, uint256)", ]); const multiRewardContract = (0, viem_1.getContract)({ address: multiRewardsAddress, abi: multiRewardAbi, client: environment.publicClient, }); const rewards = await Promise.all(environment.custom.multiRewarder.map(async (multiRewarder) => { const tokenAddress = environment.tokens[multiRewarder.rewardToken].address; if (!tokenAddress) { return; } try { const rewardData = await multiRewardContract.read.rewardData([ tokenAddress, ]); return { rewardRate: BigInt(rewardData[3]), token: tokenAddress, periodFinish: BigInt(rewardData[2]), }; } catch { return { rewardRate: 0n, token: tokenAddress }; } })); return rewards.filter(Boolean); }; const getTotalSupplyData = async (environment, multiRewardsAddress) => { if (!environment.custom.multiRewarder) { return 0n; } const multiRewardAbi = (0, viem_1.parseAbi)([ "function totalSupply() view returns (uint256)", ]); const multiRewardContract = (0, viem_1.getContract)({ address: multiRewardsAddress, abi: multiRewardAbi, client: environment.publicClient, }); try { const totalSupply = await multiRewardContract.read.totalSupply(); return BigInt(totalSupply); } catch { return 0n; } }; const getTotalAssetsData = async (environment, multiRewardsAddress) => { if (!environment.custom.multiRewarder) { return 0n; } const multiRewardAbi = (0, viem_1.parseAbi)([ "function totalAssets() view returns (uint256)", ]); const multiRewardContract = (0, viem_1.getContract)({ address: multiRewardsAddress, abi: multiRewardAbi, client: environment.publicClient, }); try { const totalAssets = await multiRewardContract.read.totalAssets(); return BigInt(totalAssets); } catch { return 0n; } }; async function getMorphoVaultsRewards(vaults, currentChainRewardsOnly) { const query = ` { vaults( where: { address_in: [${vaults.map((vault) => `"${vault.vaultToken.address}"`).join(",")}], chainId_in: [${vaults.map((vault) => vault.chainId).join(",")}] } ) { items { chain { id } id address asset { priceUsd } state { rewards { asset { address symbol decimals name chain { id } } supplyApr amountPerSuppliedToken } } } } marketPositions( where: { userAddress_in: [${vaults.map((vault) => `"${vault.vaultToken.address}"`).join(",")}], chainId_in: [${vaults.map((vault) => vault.chainId).join(",")}] } ) { items { user { address } market { morphoBlue { chain { id } } uniqueKey loanAsset { priceUsd } state { rewards { asset { address symbol decimals name chain { id } } supplyApr amountPerSuppliedToken } } } } } } `; const result = await (0, graphql_js_1.getGraphQL)(query); if (result) { try { const marketsRewards = result.marketPositions.items.flatMap((item) => { const rewards = (item.market.state?.rewards || []).map((reward) => { const tokenAmountPer1000 = (Number.parseFloat(reward.amountPerSuppliedToken) / item.market.loanAsset.priceUsd) * 1000; const tokenDecimals = 10 ** reward.asset.decimals; const amount = Number(tokenAmountPer1000) / tokenDecimals; return { chainId: reward.asset.chain.id, vaultId: item.user.address, marketId: item.market.uniqueKey, asset: reward.asset, supplyApr: (reward.supplyApr || 0) * 100, supplyAmount: amount, borrowApr: 0, borrowAmount: 0, }; }); return rewards; }); const vaultsRewards = result.vaults.items.flatMap((item) => { return (item.state?.rewards || []).map((reward) => { const tokenAmountPer1000 = (Number.parseFloat(reward.amountPerSuppliedToken) / item.asset.priceUsd) * 1000; const tokenDecimals = 10 ** reward.asset.decimals; const amount = Number(tokenAmountPer1000) / tokenDecimals; return { chainId: reward.asset.chain.id, vaultId: item.address, marketId: undefined, asset: reward.asset, supplyApr: (reward.supplyApr || 0) * 100, supplyAmount: amount, borrowApr: 0, borrowAmount: 0, }; }); }); const rewards = [...marketsRewards, ...vaultsRewards]; return vaults.map((vault) => { return { chainId: vault.chainId, vaultToken: vault.vaultToken, rewards: rewards .filter((reward) => reward.vaultId === vault.vaultToken.address && (reward.chainId === vault.chainId || !currentChainRewardsOnly)) .map((reward) => { return { marketId: reward.marketId, asset: reward.asset, supplyApr: reward.supplyApr, supplyAmount: reward.supplyAmount, borrowApr: reward.borrowApr, borrowAmount: reward.borrowAmount, }; }), }; }); } catch (ex) { return vaults.map((vault) => { return { chainId: vault.chainId, vaultToken: vault.vaultToken, rewards: [], }; }); } } else { return vaults.map((vault) => { return { chainId: vault.chainId, vaultToken: vault.vaultToken, rewards: [], }; }); } } //# sourceMappingURL=common.js.map