UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

726 lines (672 loc) 22.1 kB
import type { Address } from "viem"; import { Amount } from "../../../common/amount.js"; import type { MultichainReturnType } from "../../../common/types.js"; import type { Environment } from "../../../environments/index.js"; import type { MorphoMarket, PublicAllocatorSharedLiquidityType, } from "../../../types/morphoMarket.js"; import type { MorphoReward } from "../../../types/morphoReward.js"; import { getGraphQL, getSubgraph } from "../utils/graphql.js"; export async function getMorphoMarketsData(params: { environments: Environment[]; markets?: string[] | undefined; includeRewards?: boolean | undefined; }): Promise<MorphoMarket[]> { const { environments } = params; const environmentsWithMarkets = environments.filter( (environment) => Object.keys(environment.config.morphoMarkets).length > 0 && environment.contracts.morphoViews, ); const environmentsMarketsInfo = await Promise.all( environmentsWithMarkets.map((environment) => { const marketsIds = Object.values(environment.config.morphoMarkets) .map((item) => item.id as Address) .filter((id) => params.markets ? params.markets .map((id) => id.toLowerCase()) .includes(id.toLowerCase()) : true, ); return environment.contracts.morphoViews?.read.getMorphoBlueMarketsInfo([ marketsIds, ]); }), ); const environmentPublicAllocatorSharedLiquidity = await Promise.all( environmentsWithMarkets.map((environment) => { const marketsIds = Object.values(environment.config.morphoMarkets) .map((item) => item.id as Address) .filter((id) => params.markets ? params.markets .map((id) => id.toLowerCase()) .includes(id.toLowerCase()) : true, ); return getMorphoMarketPublicAllocatorSharedLiquidity( environment, marketsIds, ); }), ); const result = environmentsWithMarkets.reduce( (aggregator, environment, environmentIndex) => { const environmentMarketsInfo = environmentsMarketsInfo[environmentIndex]!; const markets = environmentMarketsInfo.map((marketInfo) => { const marketKey = Object.keys(environment.config.morphoMarkets).find( (item) => environment.config.morphoMarkets[item].id.toLowerCase() === marketInfo.marketId.toLowerCase(), )!; const marketConfig = Object.values( environment.config.morphoMarkets, ).find( (item) => item.id.toLowerCase() === marketInfo.marketId.toLowerCase(), )!; const loanToken = environment.config.tokens[marketConfig.loanToken]; const collateralToken = environment.config.tokens[marketConfig.collateralToken]; const oraclePrice = new Amount( BigInt(marketInfo.oraclePrice), 36 + loanToken.decimals - collateralToken.decimals, ).value; let collateralTokenPrice = new Amount(marketInfo.collateralPrice, 18) .value; let loanTokenPrice = new Amount(marketInfo.loanPrice, 18).value; if (collateralTokenPrice === 0 && loanTokenPrice > 0) { collateralTokenPrice = loanTokenPrice * oraclePrice; } if (loanTokenPrice === 0 && collateralTokenPrice > 0) { loanTokenPrice = collateralTokenPrice / oraclePrice; } const publicAllocatorSharedLiquidity = environmentPublicAllocatorSharedLiquidity[environmentIndex]?.find( (item: { marketId: string }) => item.marketId === marketInfo.marketId, ); const performanceFee = new Amount(marketInfo.fee, 18).value; const loanToValue = new Amount(marketInfo.lltv, 18).value; const totalSupplyInLoanToken = new Amount( BigInt(marketInfo.totalSupplyAssets), loanToken.decimals, ); const totalSupply = new Amount( Number(totalSupplyInLoanToken.value / oraclePrice), collateralToken.decimals, ); const totalBorrows = new Amount( marketInfo.totalBorrowAssets, loanToken.decimals, ); // Supply APR is used only for vaults, zeroing it for now to avoid confusion // const supplyApy = new Amount(marketInfo.supplyApy, 18).value * 100; const borrowApy = new Amount(marketInfo.borrowApy, 18).value * 100; const availableLiquidity = publicAllocatorSharedLiquidity?.reallocatableLiquidityAssets || new Amount(0, 18); const availableLiquidityUsd = availableLiquidity?.value * loanTokenPrice; const collateralAssets = publicAllocatorSharedLiquidity?.collateralAssets || new Amount(0, 18); const mapping: MorphoMarket = { chainId: environment.chainId, marketId: marketInfo.marketId, marketKey, deprecated: marketConfig.deprecated === true, loanToValue, performanceFee, loanToken, loanTokenPrice, collateralToken, collateralTokenPrice, collateralAssets, totalSupply, totalSupplyUsd: totalSupply.value * collateralTokenPrice, totalSupplyInLoanToken, totalBorrows, totalBorrowsUsd: totalBorrows.value * loanTokenPrice, baseBorrowApy: borrowApy, totalBorrowApr: borrowApy, baseSupplyApy: 0, //supplyApy, totalSupplyApr: 0, //supplyApy, rewardsSupplyApy: 0, rewardsBorrowApy: 0, availableLiquidity, availableLiquidityUsd, marketParams: { loanToken: marketInfo.loanToken, collateralToken: marketInfo.collateralToken, irm: marketInfo.irm, lltv: marketInfo.lltv, oracle: marketInfo.oracle, }, rewards: [], publicAllocatorSharedLiquidity: publicAllocatorSharedLiquidity?.publicAllocatorSharedLiquidity || [], }; return mapping; }); return { ...aggregator, [environment.chainId]: markets, }; }, {} as MultichainReturnType<MorphoMarket[]>, ); if (params.includeRewards) { const markets = Object.values(result) .flat() .filter((market) => { const environment = params.environments.find( (environment) => environment.chainId === market.chainId, ); return environment?.custom.morpho?.minimalDeployment === false; }); const rewards = await getMorphoMarketRewards(markets); markets.forEach((market) => { const marketReward = rewards.find( (reward) => reward.marketId === market.marketId && reward.chainId === market.chainId, ); if (marketReward) { market.rewards = marketReward.rewards; // market.collateralAssets = marketReward.collateralAssets; // market.publicAllocatorSharedLiquidity = // marketReward.publicAllocatorSharedLiquidity; // market.availableLiquidity = marketReward.reallocatableLiquidityAssets; // market.availableLiquidityUsd = // marketReward.reallocatableLiquidityAssets.value * // market.loanTokenPrice; } market.rewardsSupplyApy = market.rewards.reduce( (acc, curr) => acc + curr.supplyApr, 0, ); market.rewardsBorrowApy = market.rewards.reduce( (acc, curr) => acc + curr.borrowApr, 0, ); market.totalSupplyApr = market.rewardsSupplyApy + market.baseSupplyApy; market.totalBorrowApr = market.rewardsBorrowApy + market.baseBorrowApy; }); } return environments.flatMap((environment) => { return result[environment.chainId] || []; }); } type GetMorphoMarketsPublicAllocatorSharedLiquidityReturnType = { chainId: number; marketId: string; collateralAssets: Amount; reallocatableLiquidityAssets: Amount; publicAllocatorSharedLiquidity: PublicAllocatorSharedLiquidityType[]; }; async function getMorphoMarketPublicAllocatorSharedLiquidity( environment: Environment, markets: string[], ): Promise<GetMorphoMarketsPublicAllocatorSharedLiquidityReturnType[]> { if (markets.length === 0) { return []; } const query = ` { metaMorphos( where: {hasPublicAllocator: true, markets_: {market_in: [${markets.map((market) => `"${market.toLowerCase()}"`).join(",")}]}} ) { id name lastTotalAssets markets { market { id } enabled cap } publicAllocator { id fee markets { id flowCapIn flowCapOut market { market { id oracle { oracleAddress } irm lltv totalBorrow totalSupply totalCollateral inputToken { symbol decimals id } borrowedToken { symbol decimals id } } } } } account { positions(where:{side:SUPPLIER}) { market { id } balance } } } } `; const result = await getSubgraph<{ metaMorphos: Array<{ id: string; name: string; lastTotalAssets: string; markets: Array<{ market: { id: string; }; enabled: boolean; cap: string; }>; publicAllocator: { id: string; fee: string; markets: Array<{ id: string; flowCapIn: string; flowCapOut: string; market: { market: { id: string; oracle: { oracleAddress: string; }; irm: string; lltv: string; totalBorrow: string; totalSupply: string; totalCollateral: string; inputToken: { symbol: string; decimals: number; id: string; }; borrowedToken: { symbol: string; decimals: number; id: string; }; }; }; }>; }; account: { positions: Array<{ market: { id: string; }; balance: string; }>; }; }>; }>(environment, query); if (result) { const returnValue: GetMorphoMarketsPublicAllocatorSharedLiquidityReturnType[] = []; for (const market of markets) { let inputTokenDecimals = 0; let outputTokenDecimals = 0; let collateralAssets = 0; let reallocatableLiquidityAssets = 0; const r: PublicAllocatorSharedLiquidityType[] = []; const marketRemainingLiquidity: Record<string, number> = {}; const vaults = result.metaMorphos.filter( (item) => item.publicAllocator.markets.some( (vaultMarket) => vaultMarket.market.market.id.toLowerCase() === market.toLowerCase(), ) && item.account.positions.some( (position) => position.market.id.toLowerCase() === market.toLowerCase() && Number(position.balance) > 0, ), ); for (const vault of vaults) { const otherMarkets = vault.publicAllocator.markets.filter( (publicAllocatorMarket) => publicAllocatorMarket.market.market.id.toLowerCase() !== market.toLowerCase(), ); const thisMarket = vault.publicAllocator.markets.find( (publicAllocatorMarket) => publicAllocatorMarket.market.market.id.toLowerCase() === market.toLowerCase(), ); const thisMarketCaps = vault.markets.find( (marketCaps) => marketCaps.market.id.toLowerCase() === market.toLowerCase(), ); const thisMarketCap = Number(thisMarketCaps?.cap || 0); //Should I check if the cap is enabled? const suppliedToMarket = vault.account.positions.find( (position) => position.market.id.toLowerCase() === market.toLowerCase(), ); const suppliedToMarketAmount = Number(suppliedToMarket?.balance || 0); const thisMarketCapRemaining = thisMarketCap - suppliedToMarketAmount; let maxIn = Math.min( Number(thisMarket?.flowCapIn), thisMarketCapRemaining, ); collateralAssets = Number( thisMarket?.market.market.totalCollateral || 0, ); inputTokenDecimals = thisMarket?.market.market.inputToken.decimals || 0; outputTokenDecimals = thisMarket?.market.market.borrowedToken.decimals || 0; const otherMarketsLiquidity: { marketId: string; amount: number; liquidity: number; vault: any; allocationMarket: any; }[] = []; for (const otherMarket of otherMarkets) { const vaultSuppliedToMarket = vault.account.positions.find( (position) => position.market.id.toLowerCase() === otherMarket.market.market.id.toLowerCase(), ); if (vaultSuppliedToMarket) { const vaultSuppliedAmount = Number(vaultSuppliedToMarket.balance); const maxOut = Number(otherMarket.flowCapOut); const liquidity = Number(otherMarket.market.market.totalSupply) - Number(otherMarket.market.market.totalBorrow); if (vaultSuppliedAmount > 0 && maxOut > 0 && liquidity > 0) { otherMarketsLiquidity.push({ marketId: otherMarket.market.market.id, amount: Math.min(liquidity, vaultSuppliedAmount, maxOut), liquidity, vault: { address: vault.id, name: vault.name, publicAllocatorConfig: { fee: Number(vault.publicAllocator.fee), flowCaps: vault.publicAllocator.markets.map( (publicAllocatorMarket) => ({ maxIn: Number(publicAllocatorMarket.flowCapIn), maxOut: Number(publicAllocatorMarket.flowCapOut), market: { uniqueKey: publicAllocatorMarket.market.market.id, }, }), ), }, }, allocationMarket: { uniqueKey: otherMarket.market.market.id, loanAsset: { address: otherMarket.market.market.borrowedToken.id, }, collateralAsset: { address: otherMarket.market.market.inputToken.id, }, oracleAddress: otherMarket.market.market.oracle.oracleAddress, irmAddress: otherMarket.market.market.irm, lltv: otherMarket.market.market.lltv, }, }); } } } for (const otherMarket of otherMarketsLiquidity .filter((item) => item.amount > 0) .sort((a, b) => b.amount - a.amount)) { const marketLiquidity = marketRemainingLiquidity[otherMarket.marketId] || otherMarket.liquidity; if (maxIn > 0 && marketLiquidity > 0) { const assets = Math.min(marketLiquidity, otherMarket.amount, maxIn); maxIn = maxIn - assets; marketRemainingLiquidity[otherMarket.marketId] = marketLiquidity - assets; reallocatableLiquidityAssets += assets; r.push({ assets, vault: otherMarket.vault, allocationMarket: otherMarket.allocationMarket, }); } } } returnValue.push({ chainId: environment.chainId, marketId: market.toLowerCase(), collateralAssets: new Amount( BigInt(collateralAssets), inputTokenDecimals, ), reallocatableLiquidityAssets: new Amount( BigInt(reallocatableLiquidityAssets), outputTokenDecimals, ), publicAllocatorSharedLiquidity: r, }); } return returnValue; } else { return []; } } type GetMorphoMarketsRewardsReturnType = { chainId: number; marketId: string; collateralAssets: Amount; reallocatableLiquidityAssets: Amount; publicAllocatorSharedLiquidity: PublicAllocatorSharedLiquidityType[]; rewards: Required<MorphoReward>[]; }; async function getMorphoMarketRewards( markets: MorphoMarket[], ): Promise<GetMorphoMarketsRewardsReturnType[]> { if (markets.length === 0) { return []; } const query = ` { markets(where: { uniqueKey_in: [${markets.map((market) => `"${market.marketId.toLowerCase()}"`).join(",")}], chainId_in: [${markets.map((market) => market.chainId).join(",")}] }) { items { morphoBlue { chain { id } } reallocatableLiquidityAssets publicAllocatorSharedLiquidity { assets vault { address name publicAllocatorConfig { fee flowCaps { maxIn maxOut market { uniqueKey } } } } allocationMarket { uniqueKey loanAsset { address } collateralAsset { address } oracleAddress irmAddress lltv } } collateralAsset { decimals } loanAsset { decimals priceUsd } state { collateralAssets rewards { asset { address symbol decimals name } supplyApr borrowApr amountPerBorrowedToken amountPerSuppliedToken } } uniqueKey } } } `; const result = await getGraphQL<{ markets: { items: { morphoBlue: { chain: { id: number; }; }; uniqueKey: string; reallocatableLiquidityAssets: string; publicAllocatorSharedLiquidity: { assets: string; vault: { address: string; name: string; publicAllocatorConfig: { fee: number; flowCaps: { market: { uniqueKey: string; }; maxIn: number; maxOut: number; }[]; }; }; allocationMarket: { uniqueKey: string; loanAsset: { address: string; }; collateralAsset?: { address: string; }; oracleAddress: string; irmAddress: string; lltv: string; }; }[]; collateralAsset: { decimals: number; }; loanAsset: { decimals: number; priceUsd: number; }; state: { collateralAssets: string; rewards: { asset: { address: Address; symbol: string; decimals: number; name: string; }; supplyApr: number; amountPerSuppliedToken: string; borrowApr: number; amountPerBorrowedToken: string; }[]; }; }[]; }; }>(query); if (result) { const markets = result.markets.items.map((item) => { const mapping: GetMorphoMarketsRewardsReturnType = { chainId: item.morphoBlue.chain.id, marketId: item.uniqueKey, reallocatableLiquidityAssets: new Amount( BigInt(item.reallocatableLiquidityAssets), item.loanAsset.decimals, ), collateralAssets: new Amount( BigInt(item.state.collateralAssets), item.collateralAsset.decimals, ), publicAllocatorSharedLiquidity: item.publicAllocatorSharedLiquidity.map( (item) => ({ assets: Number(item.assets), vault: { address: item.vault.address, name: item.vault.name, publicAllocatorConfig: item.vault.publicAllocatorConfig, }, allocationMarket: item.allocationMarket, }), ), rewards: item.state?.rewards.map((reward) => { const tokenDecimals = 10 ** reward.asset.decimals; //Supply APR is used only for vaults, zeroing it for now to avoid confusion //const tokenAmountPer1000 = ((parseFloat(reward.amountPerSuppliedToken) / item.loanAsset.priceUsd) * 1000) || "0" //const amount = (Number(tokenAmountPer1000) / tokenDecimals) const borrowTokenAmountPer1000 = (Number.parseFloat(reward.amountPerBorrowedToken) / item.loanAsset.priceUsd) * 1000; const borrowAmount = borrowTokenAmountPer1000 / tokenDecimals; return { marketId: item.uniqueKey, asset: reward.asset, supplyApr: 0, //(reward.supplyApr || 0) * 100, supplyAmount: 0, //amount, borrowApr: (reward.borrowApr || 0) * 100 * -1, borrowAmount: borrowAmount, }; }), }; return mapping; }); return markets; } else { return []; } }