UNPKG

@moonwell-fi/moonwell-sdk

Version:

TypeScript Interface for Moonwell

444 lines (395 loc) 12.3 kB
import axios from "axios"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc.js"; import type { MoonwellClient } from "../../../client/createMoonwellClient.js"; import { Amount, getEnvironmentFromArgs, isStartOfDay, } from "../../../common/index.js"; import type { NetworkParameterType } from "../../../common/types.js"; import type { Chain, Environment } from "../../../environments/index.js"; import type { MarketSnapshot } from "../../../types/market.js"; import { getSubgraph } from "../../morpho/utils/graphql.js"; dayjs.extend(utc); export type GetMarketSnapshotsParameters< environments, network extends Chain | undefined, > = NetworkParameterType<environments, network> & { type: "core" | "isolated"; marketId: `0x${string}`; }; export type GetMarketSnapshotsReturnType = Promise<MarketSnapshot[]>; export async function getMarketSnapshots< environments, Network extends Chain | undefined, >( client: MoonwellClient, args: GetMarketSnapshotsParameters<environments, Network>, ): GetMarketSnapshotsReturnType { const environment = getEnvironmentFromArgs(client, args); if (!environment) { return []; } if (args?.type === "core") { return fetchCoreMarketSnapshots(args.marketId, environment); } else { if (environment.custom.morpho?.minimalDeployment === false) { return fetchIsolatedMarketSnapshots(args.marketId, environment); } else { return fetchIsolatedMarketSnapshotsSubgraph(args.marketId, environment); } } } async function fetchCoreMarketSnapshots( marketAddress: string, environment: Environment, ): Promise<MarketSnapshot[]> { const dailyData: MarketDailyData[] = []; let hasNextPage = true; let endCursor: string | undefined; interface MarketDailyData { totalBorrows: number; totalBorrowsUSD: number; totalSupplies: number; totalSuppliesUSD: number; totalLiquidity: number; totalLiquidityUSD: number; baseSupplyApy: number; baseBorrowApy: number; timestamp: number; } while (hasNextPage) { const result = await axios.post<{ data: { marketDailySnapshots: { items: MarketDailyData[]; pageInfo: { hasNextPage: boolean; endCursor: string; }; }; }; }>(environment.indexerUrl, { query: ` query { marketDailySnapshots ( limit: 1000, orderBy: "timestamp" orderDirection: "desc" where: {marketAddress: "${marketAddress.toLowerCase()}", chainId: ${environment.chainId}} ${endCursor ? `after: "${endCursor}"` : ""} ) { items { totalBorrows totalBorrowsUSD totalSupplies totalSuppliesUSD totalLiquidity totalLiquidityUSD baseSupplyApy baseBorrowApy timestamp } pageInfo { hasNextPage endCursor } } } `, }); dailyData.push( ...result.data.data.marketDailySnapshots.items.filter( (f: { timestamp: number }) => isStartOfDay(f.timestamp), ), ); hasNextPage = result.data.data.marketDailySnapshots.pageInfo.hasNextPage; endCursor = result.data.data.marketDailySnapshots.pageInfo.endCursor; } if (dailyData.length > 0) { return dailyData.map((point: any) => { const supplied = Number(point.totalSupplies); const borrow = Number(point.totalBorrows); const borrowUsd = Number(point.totalBorrowsUSD); const suppliedUsd = Number(point.totalSuppliesUSD); const liquidity = Math.max(point.totalLiquidity, 0); const liquidityUsd = Math.max(point.totalLiquidityUSD, 0); const price = suppliedUsd / supplied; const result: MarketSnapshot = { marketId: marketAddress.toLowerCase(), chainId: environment.chainId, timestamp: point.timestamp * 1000, totalSupply: supplied, totalSupplyUsd: suppliedUsd, totalBorrows: borrow, totalBorrowsUsd: borrowUsd, totalLiquidity: liquidity, totalLiquidityUsd: liquidityUsd, baseSupplyApy: point.baseSupplyApy, baseBorrowApy: point.baseBorrowApy, collateralTokenPrice: price, loanTokenPrice: price, }; return result; }); } else { return []; } } async function fetchMorphoGraphQL( query: string, operationName?: string, variables?: any, ) { try { const response = await axios.post( "https://blue-api.morpho.org/graphql", { query: query, operationName, variables }, { timeout: 5000 }, ); if (response.status !== 200 || response.data.errors) { console.log( `Non-200 (${response.statusText} }) or other error from Morpho GraphQL! - ${JSON.stringify(response.data)}`, ); return undefined; } return response.data.data; } catch (error) { return undefined; } } async function fetchIsolatedMarketSnapshots( marketAddress: string, environment: Environment, ): Promise<MarketSnapshot[]> { const operationName = "getMarketTotalTimeseries"; const variables = { options: { startTimestamp: dayjs.utc().subtract(1, "year").unix(), interval: "DAY", }, }; const query = ` query getMarketTotalTimeseries($options: TimeseriesOptions) { marketTotalTimeseries: marketByUniqueKey( chainId: ${environment.chainId} uniqueKey: "${marketAddress.toLowerCase()}" ) { uniqueKey loanAsset { priceUsd decimals } collateralAsset { priceUsd decimals } historicalState { supplyApy(options: $options) { x y } borrowApy(options: $options) { x y } borrowAssets(options: $options) { x y } borrowAssetsUsd(options: $options) { x y } supplyAssets(options: $options) { x y } supplyAssetsUsd(options: $options) { x y } liquidityAssets(options: $options) { x y } liquidityAssetsUsd(options: $options) { x y } } } }`; const result = await fetchMorphoGraphQL(query, operationName, variables); if (result) { try { const markets: MarketSnapshot[] = result.marketTotalTimeseries.historicalState.borrowAssets.map( ( borrowAssets: { x: number; y: number; }, index: number, ): MarketSnapshot => { const loanDecimals = result.marketTotalTimeseries.loanAsset.decimals; const borrowAssetsUsd = result.marketTotalTimeseries.historicalState.borrowAssetsUsd[ index ]; const supplyAssets = result.marketTotalTimeseries.historicalState.supplyAssets[index]; const supplyAssetsUsd = result.marketTotalTimeseries.historicalState.supplyAssetsUsd[ index ]; const liquidityAssets = result.marketTotalTimeseries.historicalState.liquidityAssets[ index ]; const liquidityAssetsUsd = result.marketTotalTimeseries.historicalState.liquidityAssetsUsd[ index ]; const supplyApy = result.marketTotalTimeseries.historicalState.supplyApy[index]; const borrowApy = result.marketTotalTimeseries.historicalState.borrowApy[index]; const collateralTokenPrice = result.marketTotalTimeseries.collateralAsset.priceUsd; const loanTokenPrice = result.marketTotalTimeseries.loanAsset.priceUsd; const totalSupply = (new Amount(BigInt(supplyAssets.y), Number(loanDecimals)).value * loanTokenPrice) / collateralTokenPrice; const totalBorrows = new Amount( BigInt(borrowAssets.y), Number(loanDecimals), ).value; const totalLiquidity = (new Amount(BigInt(liquidityAssets.y), Number(loanDecimals)) .value * loanTokenPrice) / collateralTokenPrice; return { chainId: environment.chainId, timestamp: borrowAssets.x * 1000, marketId: marketAddress.toLowerCase(), totalBorrows, totalBorrowsUsd: Number(borrowAssetsUsd.y), totalSupply, totalSupplyUsd: Number(supplyAssetsUsd.y), totalLiquidity, totalLiquidityUsd: Number(liquidityAssetsUsd.y), baseSupplyApy: supplyApy.y, baseBorrowApy: borrowApy.y, loanTokenPrice, collateralTokenPrice, }; }, ); return markets; } catch (ex) { return []; } } else { return []; } } async function fetchIsolatedMarketSnapshotsSubgraph( marketAddress: string, environment: Environment, ): Promise<MarketSnapshot[]> { const query = ` { marketDailySnapshots (where:{market:"${marketAddress.toLowerCase()}"}, orderBy:timestamp, orderDirection:desc, first: 365) { market { maximumLTV inputToken { decimals } borrowedToken { decimals } } variableBorrowedTokenBalance outputTokenPriceUSD inputTokenPriceUSD inputTokenBalance timestamp } } `; const result = await getSubgraph<{ marketDailySnapshots: Array<{ market: { maximumLTV: string; inputToken: { symbol: string; decimals: number; }; borrowedToken: { symbol: string; decimals: number; }; }; variableBorrowedTokenBalance: string; outputTokenPriceUSD: string; inputTokenPriceUSD: string; inputTokenBalance: string; timestamp: number; }>; }>(environment, query); if (result) { try { const markets: MarketSnapshot[] = result.marketDailySnapshots.map( (item) => { const supplyDecimals = item.market.borrowedToken.decimals; const supplyAssets = item.inputTokenBalance; const totalSupplyInLoanToken = new Amount( BigInt(supplyAssets), Number(supplyDecimals), ).value; const totalSupply = totalSupplyInLoanToken / Number(item.inputTokenPriceUSD); const borrowAssets = item.variableBorrowedTokenBalance; const totalBorrows = new Amount( BigInt(borrowAssets), Number(supplyDecimals), ).value; const totalLiquidityInLoanToken = Math.max( totalSupplyInLoanToken - totalBorrows, 0, ); const totalLiquidity = totalLiquidityInLoanToken / Number(item.inputTokenPriceUSD); return { chainId: environment.chainId, timestamp: item.timestamp * 1000, marketId: marketAddress.toLowerCase(), totalBorrows, totalBorrowsUsd: Number(item.outputTokenPriceUSD) * totalBorrows, totalSupply, totalSupplyUsd: Number(item.inputTokenPriceUSD) * totalSupply, totalLiquidity, totalLiquidityUsd: Number(item.outputTokenPriceUSD) * totalLiquidityInLoanToken, baseSupplyApy: 0, baseBorrowApy: 0, loanTokenPrice: Number(item.outputTokenPriceUSD), collateralTokenPrice: Number(item.inputTokenPriceUSD), }; }, ); return markets; } catch (ex) { return []; } } else { return []; } }