@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
136 lines • 6.94 kB
JavaScript
import { Amount } from "../../../common/index.js";
import { findMarketByAddress } from "../../../environments/utils/index.js";
export const getUserPositionData = async (params) => {
const viewsContract = params.environment.contracts.views;
if (!viewsContract) {
return [];
}
try {
const [allMarketsResult, balancesResult, borrowsResult, membershipsResult] = await Promise.allSettled([
viewsContract.read.getAllMarketsInfo(),
viewsContract.read.getUserBalances([params.account]),
viewsContract.read.getUserBorrowsBalances([params.account]),
viewsContract.read.getUserMarketsMemberships([params.account]),
]);
const balances = balancesResult.status === "fulfilled" ? balancesResult.value : [];
const borrows = borrowsResult.status === "fulfilled" ? borrowsResult.value : [];
const memberships = membershipsResult.status === "fulfilled" ? membershipsResult.value : [];
// If getAllMarketsInfo failed (e.g. broken on-chain oracle), fall back to
// per-mToken exchange rate calls. The user balance/borrow/membership calls
// don't touch the oracle so they can still succeed.
if (allMarketsResult.status === "rejected") {
return getUserPositionsFromMTokenFallback(params, balances, borrows, memberships);
}
const allMarkets = allMarketsResult.value;
const markets = allMarkets
?.map((marketInfo) => {
const market = findMarketByAddress(params.environment, marketInfo.market);
if (market) {
const { marketToken, underlyingToken } = market;
const underlyingPrice = new Amount(marketInfo.underlyingPrice, 36 - underlyingToken.decimals).value;
const collateralFactor = new Amount(marketInfo.collateralFactor, 18)
.value;
const exchangeRate = new Amount(marketInfo.exchangeRate, 10 + underlyingToken.decimals).value;
const marketCollateralEnabled = memberships?.find((r) => r.token === marketInfo.market)
?.membership === true;
const marketBorrowedRaw = borrows?.find((r) => r.token === marketInfo.market)?.amount || 0n;
const marketSuppliedRaw = balances?.find((r) => r.token === marketInfo.market)?.amount || 0n;
const borrowed = new Amount(marketBorrowedRaw, market.underlyingToken.decimals);
const borrowedUsd = borrowed.value * underlyingPrice;
const marketSupplied = new Amount(marketSuppliedRaw, marketToken.decimals);
const supplied = new Amount(marketSupplied.value * exchangeRate, underlyingToken.decimals);
const suppliedUsd = supplied.value * underlyingPrice;
const collateral = marketCollateralEnabled
? new Amount(supplied.value * collateralFactor, underlyingToken.decimals)
: new Amount(0n, underlyingToken.decimals);
const collateralUsd = collateral.value * underlyingPrice;
const result = {
chainId: params.environment.chainId,
account: params.account,
market: market.marketToken,
collateralEnabled: marketCollateralEnabled,
borrowed,
borrowedUsd,
collateral,
collateralUsd,
supplied,
suppliedUsd,
};
return result;
}
else {
return;
}
})
.filter((r) => r !== undefined)
.filter((r) => params.markets ? params.markets.includes(r.market.address) : true);
return markets;
}
catch {
return [];
}
};
/**
* Fallback for chains whose on-chain price oracle is non-functional (e.g.
* deprecated Moonriver). getUserBalances/getUserBorrowsBalances/getUserMarketsMemberships
* don't require the oracle, so we use those results directly. We fetch each
* mToken's exchangeRate individually to convert mToken balances to underlying.
* All USD values are set to 0 since oracle prices are unavailable.
*/
async function getUserPositionsFromMTokenFallback(params, balances, borrows, memberships) {
const positions = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const envAny = params.environment;
for (const marketKey of Object.keys(params.environment.config.markets)) {
const marketConfig = envAny.config.markets[marketKey];
if (!marketConfig)
continue;
const underlyingToken = envAny.config.tokens[marketConfig.underlyingToken];
const marketToken = envAny.config.tokens[marketConfig.marketToken];
if (!underlyingToken || !marketToken)
continue;
const mTokenAddress = marketToken.address.toLowerCase();
const marketSuppliedRaw = balances.find((r) => r.token.toLowerCase() === mTokenAddress)?.amount ??
0n;
const marketBorrowedRaw = borrows.find((r) => r.token.toLowerCase() === mTokenAddress)?.amount ??
0n;
// Skip markets where the user has no position
if (marketSuppliedRaw === 0n && marketBorrowedRaw === 0n)
continue;
const marketCollateralEnabled = memberships.find((r) => r.token.toLowerCase() === mTokenAddress)
?.membership === true;
// Fetch exchange rate individually (not oracle-dependent)
const mTokenContract = envAny.markets[marketKey];
const defaultExchangeRate = 10n ** BigInt(10 + underlyingToken.decimals);
let exchangeRateRaw;
try {
exchangeRateRaw =
(await mTokenContract?.read.exchangeRateStored()) ??
defaultExchangeRate;
}
catch {
exchangeRateRaw = defaultExchangeRate;
}
const exchangeRate = new Amount(exchangeRateRaw, 10 + underlyingToken.decimals).value;
const borrowed = new Amount(marketBorrowedRaw, underlyingToken.decimals);
const marketSupplied = new Amount(marketSuppliedRaw, marketToken.decimals);
const supplied = new Amount(marketSupplied.value * exchangeRate, underlyingToken.decimals);
if (params.markets && !params.markets.includes(marketToken.address)) {
continue;
}
positions.push({
chainId: params.environment.chainId,
account: params.account,
market: marketToken,
collateralEnabled: marketCollateralEnabled,
borrowed,
borrowedUsd: 0,
collateral: new Amount(0n, underlyingToken.decimals),
collateralUsd: 0,
supplied,
suppliedUsd: 0,
});
}
return positions;
}
//# sourceMappingURL=common.js.map