@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
319 lines • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.trimLeadingEmptySnapshots = trimLeadingEmptySnapshots;
exports.getMarketSnapshots = getMarketSnapshots;
exports.fetchIsolatedMarketSnapshots = fetchIsolatedMarketSnapshots;
const index_js_1 = require("../../../common/index.js");
const lunar_indexer_helpers_js_1 = require("../../../common/lunar-indexer-helpers.js");
const axiosWithRetry_js_1 = require("../../axiosWithRetry.js");
const lunar_indexer_client_js_1 = require("../../lunar-indexer-client.js");
const lunar_indexer_transformers_js_1 = require("../../lunar-indexer-transformers.js");
const lunarIndexerTransform_js_1 = require("../../morpho/markets/lunarIndexerTransform.js");
function trimLeadingEmptySnapshots(snapshots) {
let firstActiveTimestamp = Number.POSITIVE_INFINITY;
for (const s of snapshots) {
if ((s.totalSupply > 0 || s.totalBorrows > 0) &&
s.timestamp < firstActiveTimestamp) {
firstActiveTimestamp = s.timestamp;
}
}
if (firstActiveTimestamp === Number.POSITIVE_INFINITY)
return [];
return snapshots.filter((s) => s.timestamp >= firstActiveTimestamp);
}
async function getMarketSnapshots(client, args) {
const environment = (0, index_js_1.getEnvironmentFromArgs)(client, args);
if (!environment) {
return [];
}
if (args?.type === "core") {
const snapshots = await fetchCoreMarketSnapshots(args.marketId, environment, args.period, args.startTime, args.endTime);
return trimLeadingEmptySnapshots(snapshots);
}
const snapshots = await fetchIsolatedMarketSnapshots(args.marketId, environment, args.period, args.startTime, args.endTime);
const marketConfig = Object.values(environment.config.morphoMarkets).find((m) => m.id.toLowerCase() === args.marketId.toLowerCase());
const loanSymbol = marketConfig
? environment.config.tokens[marketConfig.loanToken]?.symbol
: undefined;
const collateralSymbol = marketConfig
? environment.config.tokens[marketConfig.collateralToken]?.symbol
: undefined;
const isNormalizedMarket = (loanSymbol === "ETH" && collateralSymbol === "USDC") ||
collateralSymbol === "stkWELL";
const result = isNormalizedMarket
? snapshots
: snapshots.map((snapshot) => {
if (snapshot.totalSupply === 0 || snapshot.loanTokenPrice === 0)
return snapshot;
const impliedLoanPrice = snapshot.totalSupplyUsd / snapshot.totalSupply;
if (impliedLoanPrice < snapshot.loanTokenPrice * 0.1) {
return {
...snapshot,
totalSupply: snapshot.totalSupplyUsd / snapshot.loanTokenPrice,
totalLiquidity: snapshot.totalLiquidityUsd / snapshot.loanTokenPrice,
};
}
return snapshot;
});
return trimLeadingEmptySnapshots(result);
}
async function fetchCoreMarketSnapshots(marketAddress, environment, period, startTime, endTime) {
if (!environment.lunarIndexerUrl) {
if (!environment.indexerUrl)
return [];
try {
return await fetchCoreMarketSnapshotsFromPonder(marketAddress, environment);
}
catch (error) {
console.warn(`[getMarketSnapshots] Ponder failed for chain ${environment.chainId}:`, error);
environment.onError?.(error, {
source: "market-snapshots-ponder",
chainId: environment.chainId,
});
return [];
}
}
try {
return await fetchCoreMarketSnapshotsFromLunar(marketAddress, environment, period, startTime, endTime);
}
catch (error) {
console.warn(`[getMarketSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`, error);
environment.onError?.(error, {
source: "market-snapshots",
chainId: environment.chainId,
});
return [];
}
}
async function fetchCoreMarketSnapshotsFromPonder(marketAddress, environment) {
if (!environment.indexerUrl)
return [];
const dailyData = [];
let hasNextPage = true;
let endCursor;
while (hasNextPage) {
const result = await (0, axiosWithRetry_js_1.postWithRetry)(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) => (0, index_js_1.isStartOfDay)(f.timestamp)));
hasNextPage = result.data.data.marketDailySnapshots.pageInfo.hasNextPage;
endCursor = result.data.data.marketDailySnapshots.pageInfo.endCursor;
}
if (dailyData.length === 0)
return [];
return dailyData.map((point) => {
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;
return {
marketId: marketAddress.toLowerCase(),
chainId: environment.chainId,
timestamp: point.timestamp * 1000,
totalSupply: supplied,
totalSupplyUsd: suppliedUsd,
totalBorrows: borrow,
totalBorrowsUsd: borrowUsd,
totalLiquidity: liquidity,
totalLiquidityUsd: liquidityUsd,
totalReallocatableLiquidity: 0,
totalReallocatableLiquidityUsd: 0,
baseSupplyApy: point.baseSupplyApy,
baseBorrowApy: point.baseBorrowApy,
collateralTokenPrice: price,
loanTokenPrice: price,
};
});
}
async function fetchCoreMarketSnapshotsFromLunar(marketAddress, environment, period, customStartTime, customEndTime) {
if (!environment.lunarIndexerUrl) {
throw new Error("Lunar Indexer URL not configured");
}
const client = (0, lunar_indexer_client_js_1.createLunarIndexerClient)({
baseUrl: environment.lunarIndexerUrl,
timeout: lunar_indexer_client_js_1.DEFAULT_LUNAR_TIMEOUT_MS,
});
const marketId = (0, lunar_indexer_helpers_js_1.buildMarketId)(environment.chainId, marketAddress);
const { startTime, endTime, granularity } = (0, index_js_1.calculateTimeRange)(period, customStartTime, customEndTime);
const allSnapshots = [];
let cursor = null;
const MAX_PAGES = 100;
let page = 0;
do {
const response = await client.getMarketSnapshots(marketId, {
limit: 1000,
...(cursor && { cursor }),
granularity: (0, index_js_1.toApiGranularity)(granularity),
startTime,
endTime,
});
const transformed = (0, lunar_indexer_transformers_js_1.transformMarketSnapshots)(response.results, environment.chainId);
allSnapshots.push(...transformed);
cursor = response.nextCursor;
page++;
} while (cursor !== null && page < MAX_PAGES);
allSnapshots.sort((a, b) => a.timestamp - b.timestamp);
return (0, index_js_1.applyGranularity)(allSnapshots, granularity).map((snapshot) => {
const supplied = snapshot.totalSupply;
const suppliedUsd = snapshot.totalSupplyUsd;
const price = supplied > 0 ? suppliedUsd / supplied : 0;
return {
...snapshot,
collateralTokenPrice: price,
loanTokenPrice: price,
};
});
}
async function fetchWellPricesByTimestamp(lunarIndexerUrl, chainId, wellMarketConfig, startTime) {
const wellIsCollateral = wellMarketConfig.collateralToken === "WELL";
const priceMap = new Map();
let cursor;
const MAX_PAGES = 100;
let page = 0;
do {
const response = await (0, lunarIndexerTransform_js_1.fetchMarketSnapshotsFromIndexer)(lunarIndexerUrl, chainId, wellMarketConfig.id, {
startTime,
granularity: "1d",
limit: 1000,
...(cursor && { cursor }),
});
for (const snapshot of response.results) {
const price = Number.parseFloat(wellIsCollateral
? snapshot.collateralTokenPrice
: snapshot.loanTokenPrice);
if (price > 0) {
priceMap.set(snapshot.timestamp * 1000, price);
}
}
cursor = response.nextCursor ?? undefined;
page++;
} while (cursor !== undefined && page < MAX_PAGES);
return priceMap;
}
async function fetchIsolatedMarketSnapshots(marketAddress, environment, period, customStartTime, customEndTime) {
const lunarIndexerUrl = environment.lunarIndexerUrl;
if (!lunarIndexerUrl) {
return [];
}
try {
return await fetchIsolatedMarketSnapshotsFromLunar(marketAddress, environment, lunarIndexerUrl, period, customStartTime, customEndTime);
}
catch (error) {
console.warn(`[getMarketSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`, error);
environment.onError?.(error, {
source: "market-snapshots",
chainId: environment.chainId,
});
return [];
}
}
async function fetchIsolatedMarketSnapshotsFromLunar(marketAddress, environment, lunarIndexerUrl, period, customStartTime, customEndTime) {
const { startTime } = (0, index_js_1.calculateTimeRange)(period, customStartTime, customEndTime);
const marketConfig = Object.values(environment.config.morphoMarkets).find((m) => m.id.toLowerCase() === marketAddress.toLowerCase());
const loanSymbol = marketConfig
? environment.config.tokens[marketConfig.loanToken]?.symbol
: undefined;
const collateralSymbol = marketConfig
? environment.config.tokens[marketConfig.collateralToken]?.symbol
: undefined;
const normalizeToCollateral = loanSymbol === "ETH" && collateralSymbol === "USDC";
const isStkWellMarket = collateralSymbol === "stkWELL";
const wellMarketConfig = isStkWellMarket
? Object.values(environment.config.morphoMarkets).find((m) => m.collateralToken === "WELL" || m.loanToken === "WELL")
: undefined;
const wellPricesPromise = wellMarketConfig
? fetchWellPricesByTimestamp(lunarIndexerUrl, environment.chainId, wellMarketConfig, startTime)
: Promise.resolve(new Map());
const allSnapshots = [];
let cursor;
const MAX_PAGES = 100;
let page = 0;
do {
const response = await (0, lunarIndexerTransform_js_1.fetchMarketSnapshotsFromIndexer)(lunarIndexerUrl, environment.chainId, marketAddress, {
startTime,
granularity: "1d",
limit: 1000,
...(cursor && { cursor }),
});
allSnapshots.push(...response.results
.filter((s) => (0, index_js_1.isStartOfDay)(s.timestamp))
.map((s) => (0, lunarIndexerTransform_js_1.transformIsolatedMarketSnapshotFromIndexer)(s, {
normalizeToCollateral,
})));
cursor = response.nextCursor ?? undefined;
page++;
} while (cursor !== undefined && page < MAX_PAGES);
const sanitizedSnapshots = normalizeToCollateral
? allSnapshots
: allSnapshots.map((snapshot) => {
if (snapshot.totalSupply === 0 || snapshot.loanTokenPrice === 0)
return snapshot;
const impliedLoanPrice = snapshot.totalSupplyUsd / snapshot.totalSupply;
if (impliedLoanPrice < snapshot.loanTokenPrice * 0.1) {
return {
...snapshot,
totalSupply: snapshot.totalSupplyUsd / snapshot.loanTokenPrice,
totalLiquidity: snapshot.totalLiquidityUsd / snapshot.loanTokenPrice,
};
}
return snapshot;
});
if (isStkWellMarket) {
const wellPriceByTimestampMs = await wellPricesPromise;
return sanitizedSnapshots.map((snapshot) => {
let collateralTokenPrice = snapshot.collateralTokenPrice;
if (collateralTokenPrice === 0) {
const wellPrice = wellPriceByTimestampMs.get(snapshot.timestamp);
if (wellPrice)
collateralTokenPrice = wellPrice;
}
const totalSupply = collateralTokenPrice > 0
? snapshot.totalSupplyUsd / collateralTokenPrice
: snapshot.totalSupply;
const totalLiquidity = collateralTokenPrice > 0
? snapshot.totalLiquidityUsd / collateralTokenPrice
: snapshot.totalLiquidity;
const totalReallocatableLiquidity = collateralTokenPrice > 0
? snapshot.totalReallocatableLiquidityUsd / collateralTokenPrice
: snapshot.totalReallocatableLiquidity;
return {
...snapshot,
collateralTokenPrice,
totalSupply,
totalLiquidity,
totalReallocatableLiquidity,
};
});
}
return sanitizedSnapshots;
}
//# sourceMappingURL=getMarketSnapshots.js.map