@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
303 lines • 12.4 kB
JavaScript
/**
* Lunar Indexer API Client
*
* Client for interacting with the Lunar Indexer REST API endpoints.
* Provides functions for fetching comptroller, market, token, and portfolio data.
*/
import axios, {} from "axios";
import { attachRetryInterceptor } from "./retry.js";
// ============================================================================
// Error Handling
// ============================================================================
export class LunarIndexerError extends Error {
constructor(message, statusCode, endpoint, originalError) {
super(message);
Object.defineProperty(this, "statusCode", {
enumerable: true,
configurable: true,
writable: true,
value: statusCode
});
Object.defineProperty(this, "endpoint", {
enumerable: true,
configurable: true,
writable: true,
value: endpoint
});
Object.defineProperty(this, "originalError", {
enumerable: true,
configurable: true,
writable: true,
value: originalError
});
this.name = "LunarIndexerError";
}
}
/**
* Determine if an error should trigger fallback to Ponder/on-chain
*/
export function shouldFallback(error) {
if (axios.isAxiosError(error)) {
const axiosError = error;
const isNetworkError = !axiosError.response;
const is5xxError = !!axiosError.response && axiosError.response.status >= 500;
const is404Error = !!axiosError.response && axiosError.response.status === 404;
if (isNetworkError || is5xxError || is404Error) {
return true;
}
// 4xx errors (except 404) should NOT fallback - fail fast
return false;
}
// Unknown errors should fallback
return true;
}
export const DEFAULT_LUNAR_TIMEOUT_MS = 10_000;
// ============================================================================
// Lunar Indexer Client Class
// ============================================================================
export class LunarIndexerClient {
constructor(config) {
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stakingClient", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "vaultsClient", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.client = axios.create({
baseURL: `${config.baseUrl}/api/v1/core`,
timeout: config.timeout || DEFAULT_LUNAR_TIMEOUT_MS,
headers: {
"Content-Type": "application/json",
},
});
this.stakingClient = axios.create({
baseURL: `${config.baseUrl}/api/v1/staking`,
timeout: config.timeout || DEFAULT_LUNAR_TIMEOUT_MS,
headers: {
"Content-Type": "application/json",
},
});
this.vaultsClient = axios.create({
baseURL: `${config.baseUrl}/api/v1/vaults`,
timeout: config.timeout || DEFAULT_LUNAR_TIMEOUT_MS,
headers: {
"Content-Type": "application/json",
},
});
// Retry transient failures (5xx, network errors, timeouts) silently before
// surfacing to callers. 4xx (incl. 404) bypasses retries — see ./retry.ts.
attachRetryInterceptor(this.client);
attachRetryInterceptor(this.stakingClient);
attachRetryInterceptor(this.vaultsClient);
}
/**
* Get comptroller data for a specific chain
*/
async getComptroller(chainId) {
try {
const response = await this.client.get(`/comptroller/${chainId}`);
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch comptroller for chain ${chainId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/comptroller/${chainId}`, error);
}
}
/**
* List all markets for a specific chain with pagination
* Returns full market data with real-time values, APYs, and incentives
*/
async listMarkets(chainId, options) {
try {
const params = {};
if (options?.limit)
params.limit = options.limit.toString();
if (options?.cursor)
params.cursor = options.cursor;
const response = await this.client.get(`/markets/${chainId}`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to list markets for chain ${chainId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/markets/${chainId}`, error);
}
}
/**
* Get a single market by marketId (format: chainId-marketAddress)
* Returns full market data with real-time values, APYs, and incentives
*/
async getMarket(marketId) {
try {
const response = await this.client.get(`/market/${marketId}`);
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch market ${marketId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/market/${marketId}`, error);
}
}
/**
* Get market snapshots with optional time range and granularity
*/
async getMarketSnapshots(marketId, options) {
try {
const params = {};
if (options?.limit)
params.limit = options.limit.toString();
if (options?.cursor)
params.cursor = options.cursor;
if (options?.granularity)
params.granularity = options.granularity;
if (options?.startTime)
params.startTime = options.startTime.toString();
if (options?.endTime)
params.endTime = options.endTime.toString();
const response = await this.client.get(`/market/${marketId}/snapshots`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch market snapshots for ${marketId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/market/${marketId}/snapshots`, error);
}
}
/**
* List all tokens for a specific chain with pagination
*/
async listTokens(chainId, options) {
try {
const params = {};
if (options?.limit)
params.limit = options.limit.toString();
if (options?.cursor)
params.cursor = options.cursor;
const response = await this.client.get(`/tokens/${chainId}`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to list tokens for chain ${chainId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/tokens/${chainId}`, error);
}
}
/**
* Get a single token by tokenId (format: chainId-tokenAddress)
*/
async getToken(tokenId) {
try {
const response = await this.client.get(`/token/${tokenId}`);
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch token ${tokenId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/token/${tokenId}`, error);
}
}
/**
* Get account portfolio with historical positions
* NOTE: USD fields (supplyBalanceUsd, borrowBalanceUsd) are being added by Lunar team
*/
async getAccountPortfolio(accountAddress, options) {
try {
const params = {
startTime: options.startTime.toString(),
endTime: options.endTime.toString(),
};
if (options.granularity)
params.granularity = options.granularity;
if (options.chainId)
params.chainId = options.chainId.toString();
if (options.market)
params.market = options.market;
const response = await this.client.get(`/account/${accountAddress.toLowerCase()}/portfolio`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch portfolio for account ${accountAddress}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/account/${accountAddress}/portfolio`, error);
}
}
/**
* Get staking snapshots for a specific chain (stkWELL staking)
*/
async getStakingSnapshots(chainId, options) {
try {
const params = {};
if (options?.limit)
params.limit = options.limit.toString();
if (options?.cursor)
params.cursor = options.cursor;
if (options?.granularity)
params.granularity = options.granularity;
if (options?.startTime)
params.startTime = options.startTime.toString();
if (options?.endTime)
params.endTime = options.endTime.toString();
const response = await this.stakingClient.get(`/snapshots/${chainId}`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch staking snapshots for chain ${chainId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/snapshots/${chainId}`, error);
}
}
/**
* Get vault account portfolio with historical positions
*/
async getVaultAccountPortfolio(accountAddress, options) {
try {
const params = {
startTime: options.startTime.toString(),
endTime: options.endTime.toString(),
};
if (options.granularity)
params.granularity = options.granularity;
if (options.chainId)
params.chainId = options.chainId.toString();
if (options.vault)
params.vault = options.vault;
const response = await this.vaultsClient.get(`/account/${accountAddress.toLowerCase()}/portfolio`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch vault portfolio for account ${accountAddress}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/account/${accountAddress}/portfolio`, error);
}
}
/**
* Get vault staking snapshots for a specific chain (MetaMorpho vault staking)
*/
async getVaultStakingSnapshots(chainId, options) {
try {
const params = {};
if (options?.limit)
params.limit = options.limit.toString();
if (options?.cursor)
params.cursor = options.cursor;
if (options?.granularity)
params.granularity = options.granularity;
if (options?.startTime)
params.startTime = options.startTime.toString();
if (options?.endTime)
params.endTime = options.endTime.toString();
if (options?.vaultAddress)
params.vaultAddress = options.vaultAddress;
const response = await this.stakingClient.get(`/vault-snapshots/${chainId}`, { params });
return response.data;
}
catch (error) {
throw new LunarIndexerError(`Failed to fetch vault staking snapshots for chain ${chainId}`, axios.isAxiosError(error) ? error.response?.status : undefined, `/vault-snapshots/${chainId}`, error);
}
}
}
// ============================================================================
// Factory Function
// ============================================================================
/**
* Create a new Lunar Indexer client instance
*/
export function createLunarIndexerClient(config) {
return new LunarIndexerClient(config);
}
//# sourceMappingURL=lunar-indexer-client.js.map