@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
168 lines (150 loc) • 5.01 kB
text/typescript
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc.js";
import type { MoonwellClient } from "../client/createMoonwellClient.js";
import type { Environment } from "../environments/index.js";
dayjs.extend(utc);
export { Amount } from "./amount.js";
export { BaseError, HttpRequestError } from "./error.js";
export type { HttpRequestErrorType } from "./error.js";
export { getBlockNumberAtTimestamp } from "./getBlockNumberAtTimestamp.js";
export type { MultichainReturnType } from "./types.js";
export const SECONDS_PER_DAY = 86400;
export const DAYS_PER_YEAR = 365;
export const perDay = (value: number) => value * SECONDS_PER_DAY;
export function isStartOfDay(timestamp: number): boolean {
const startOfDay = dayjs
.utc(timestamp * 1000)
.startOf("day")
.unix();
return startOfDay === timestamp;
}
export const calculateApy = (value: number) =>
((value * SECONDS_PER_DAY + 1) ** DAYS_PER_YEAR - 1) * 100;
export type SnapshotPeriod = "1M" | "3M" | "1Y" | "ALL";
export type SnapshotGranularity = "6h" | "1d" | "7d" | "14d" | "30d";
/**
* Filter snapshots to keep every Nth data point based on granularity.
* "6h" and "1d" are handled natively by the API — no client-side filtering applied.
* Coarser granularities (7d, 14d, 30d) fetch "1d" from the API and thin here.
*
* Callers must sort snapshots ascending by timestamp before calling this function.
* Always keeps index 0, N, 2N, … so the oldest data point is always retained.
*/
export function applyGranularity<T extends { timestamp: number }>(
snapshots: T[],
granularity: SnapshotGranularity,
): T[] {
const step: Record<SnapshotGranularity, number> = {
"6h": 1,
"1d": 1,
"7d": 7,
"14d": 14,
"30d": 30,
};
const n = step[granularity];
if (n <= 1) return snapshots;
return snapshots.filter((_, i) => i % n === 0);
}
/**
* Map a SnapshotGranularity to the value passed to the lunar indexer API.
* "6h" is natively supported; coarser granularities (7d, 14d, 30d) are not,
* so we fetch "1d" and thin client-side via applyGranularity.
*/
export function toApiGranularity(
granularity: SnapshotGranularity,
): "6h" | "1d" {
return granularity === "6h" ? "6h" : "1d";
}
/**
* Calculate start/end times and display granularity based on period or custom timestamps.
* Priority: custom timestamps > period > default (365 days)
*
* Granularity per period:
* 1M → 6h (4 data points per day, API-native)
* 3M → 1d (one data point per day)
* 1Y → 7d (one data point every 7 days, client-side filtered)
* ALL → 14d (one data point every 14 days, client-side filtered)
*/
export function calculateTimeRange(
period?: SnapshotPeriod,
startTime?: number,
endTime?: number,
): { startTime: number; endTime: number; granularity: SnapshotGranularity } {
const now = dayjs.utc();
const end = endTime ?? now.unix();
if (startTime !== undefined) {
return { startTime, endTime: end, granularity: "1d" };
}
let start: number;
let granularity: SnapshotGranularity;
switch (period) {
case "1M":
start = now.subtract(31, "days").unix();
granularity = "6h";
break;
case "3M":
start = now.subtract(91, "days").unix();
granularity = "1d";
break;
case "1Y":
start = now.subtract(366, "days").unix();
granularity = "7d";
break;
case "ALL":
start = now.subtract(3, "years").unix();
granularity = "14d";
break;
case undefined:
start = now.subtract(366, "days").unix();
granularity = "1d";
break;
}
return { startTime: start, endTime: end, granularity };
}
export const getEnvironmentFromArgs = (
client: MoonwellClient,
args?: { chainId?: number; network?: any },
) => {
if (args) {
const { chainId, network } = args as {
chainId?: number;
network?: keyof typeof client.environments;
};
if (chainId) {
return Object.values(client.environments).find(
(env) => env.chainId === chainId,
) as Environment;
}
if (network) {
return client.environments[network] as Environment;
}
}
return undefined;
};
export const getEnvironmentsFromArgs = (
client: MoonwellClient,
args?: { chainId?: number; network?: any },
onlyWithDeployment?: boolean,
): Environment[] => {
const onlyEnvironmentsWithDeployment =
onlyWithDeployment !== undefined ? onlyWithDeployment : true;
if (args) {
const { chainId, network } = args as {
chainId?: number;
network?: keyof typeof client.environments;
};
if (Number.isInteger(chainId)) {
return [
Object.values(client.environments).find(
(env) => env.chainId === chainId,
),
] as Environment[];
}
if (network) {
return [client.environments[network]] as Environment[];
}
}
return Object.values(client.environments as Environment[]).filter((r) =>
onlyEnvironmentsWithDeployment ? r.contracts.views !== undefined : true,
);
};