@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
495 lines • 21.5 kB
JavaScript
;
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