@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
171 lines (155 loc) • 4.46 kB
text/typescript
import type { MoonwellClient } from "../../client/createMoonwellClient.js";
import {
type SnapshotPeriod,
applyGranularity,
calculateTimeRange,
getEnvironmentFromArgs,
toApiGranularity,
} from "../../common/index.js";
import type { NetworkParameterType } from "../../common/types.js";
import type { Chain } from "../../environments/index.js";
import type { StakingSnapshot } from "../../types/staking.js";
import { postWithRetry } from "../axiosWithRetry.js";
import {
DEFAULT_LUNAR_TIMEOUT_MS,
createLunarIndexerClient,
} from "../lunar-indexer-client.js";
import { transformStakingSnapshots } from "../lunar-indexer-transformers.js";
export type GetStakingSnapshotsParameters<
environments,
network extends Chain | undefined,
> = NetworkParameterType<environments, network> & {
period?: SnapshotPeriod;
startTime?: number;
endTime?: number;
};
export type GetStakingSnapshotsReturnType = Promise<StakingSnapshot[]>;
export async function getStakingSnapshots<
environments,
Network extends Chain | undefined,
>(
client: MoonwellClient,
args?: GetStakingSnapshotsParameters<environments, Network>,
): GetStakingSnapshotsReturnType {
const environment = getEnvironmentFromArgs(client, args);
if (!environment) {
return [];
}
const {
period,
startTime: customStartTime,
endTime: customEndTime,
} = (args ?? {}) as GetStakingSnapshotsParameters<environments, undefined>;
if (!environment.lunarIndexerUrl) {
const { startTime } = calculateTimeRange(
period,
customStartTime,
customEndTime,
);
return environment.indexerUrl
? fetchStakingSnapshotsFromPonder(
environment.chainId,
environment.indexerUrl,
startTime,
)
: [];
}
try {
return await fetchStakingSnapshotsFromLunar(
environment.chainId,
environment.lunarIndexerUrl,
period,
customStartTime,
customEndTime,
);
} catch (error) {
console.warn(
`[getStakingSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`,
error,
);
environment.onError?.(error, {
source: "staking-snapshots",
chainId: environment.chainId,
});
return [];
}
}
async function fetchStakingSnapshotsFromLunar(
chainId: number,
lunarIndexerUrl: string,
period?: SnapshotPeriod,
customStartTime?: number,
customEndTime?: number,
): Promise<StakingSnapshot[]> {
const lunarClient = createLunarIndexerClient({
baseUrl: lunarIndexerUrl,
timeout: DEFAULT_LUNAR_TIMEOUT_MS,
});
const { startTime, endTime, granularity } = calculateTimeRange(
period,
customStartTime,
customEndTime,
);
const allSnapshots: StakingSnapshot[] = [];
let cursor: string | null = null;
const MAX_PAGES = 100;
let page = 0;
do {
const response = await lunarClient.getStakingSnapshots(chainId, {
limit: 1000,
granularity: toApiGranularity(granularity),
startTime,
endTime,
...(cursor && { cursor }),
});
allSnapshots.push(...transformStakingSnapshots(response.results));
cursor = response.nextCursor;
page++;
} while (cursor !== null && page < MAX_PAGES);
allSnapshots.sort((a, b) => a.timestamp - b.timestamp);
return applyGranularity(allSnapshots, granularity);
}
async function fetchStakingSnapshotsFromPonder(
chainId: number,
indexerUrl: string,
startTime?: number,
): Promise<StakingSnapshot[]> {
try {
const response = await postWithRetry<{
data: {
stakingDailySnapshots: {
items: StakingSnapshot[];
};
};
}>(indexerUrl, {
query: `
query {
stakingDailySnapshots(
limit: 365,
orderBy: "timestamp"
orderDirection: "desc"
where: {chainId: ${chainId}}
) {
items {
chainId
totalStaked
totalStakedUSD
timestamp
}
}
}
`,
});
if (response.status === 200 && response.data?.data?.stakingDailySnapshots) {
const items = response.data.data.stakingDailySnapshots.items;
return startTime
? items.filter((item) => item.timestamp >= startTime)
: items;
} else {
return [];
}
} catch (ex) {
console.error("An error occured while fetching getStakingSnapshots...", ex);
return [];
}
}