@moonwell-fi/moonwell-sdk 0.9.26 → 0.9.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/_cjs/actions/core/markets/common.js +291 -5
- package/_cjs/actions/core/markets/common.js.map +1 -1
- package/_cjs/actions/core/markets/getMarketSnapshots.js +83 -3
- package/_cjs/actions/core/markets/getMarketSnapshots.js.map +1 -1
- package/_cjs/actions/core/user-positions/common.js +61 -1
- package/_cjs/actions/core/user-positions/common.js.map +1 -1
- package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js +70 -2
- package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
- package/_cjs/actions/lunar-indexer-client.js +164 -0
- package/_cjs/actions/lunar-indexer-client.js.map +1 -0
- package/_cjs/actions/lunar-indexer-transformers.js +47 -0
- package/_cjs/actions/lunar-indexer-transformers.js.map +1 -0
- package/_cjs/environments/definitions/base/environment.js +2 -1
- package/_cjs/environments/definitions/base/environment.js.map +1 -1
- package/_cjs/environments/definitions/moonbeam/environment.js +2 -1
- package/_cjs/environments/definitions/moonbeam/environment.js.map +1 -1
- package/_cjs/environments/definitions/optimism/environment.js +2 -1
- package/_cjs/environments/definitions/optimism/environment.js.map +1 -1
- package/_cjs/environments/types/config.js +1 -0
- package/_cjs/environments/types/config.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_cjs/utils/lunar-indexer-helpers.js +27 -0
- package/_cjs/utils/lunar-indexer-helpers.js.map +1 -0
- package/_esm/actions/core/markets/common.js +302 -5
- package/_esm/actions/core/markets/common.js.map +1 -1
- package/_esm/actions/core/markets/getMarketSnapshots.js +87 -3
- package/_esm/actions/core/markets/getMarketSnapshots.js.map +1 -1
- package/_esm/actions/core/user-positions/common.js +74 -1
- package/_esm/actions/core/user-positions/common.js.map +1 -1
- package/_esm/actions/core/user-positions/getUserPositionSnapshots.js +100 -2
- package/_esm/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
- package/_esm/actions/lunar-indexer-client.js +201 -0
- package/_esm/actions/lunar-indexer-client.js.map +1 -0
- package/_esm/actions/lunar-indexer-transformers.js +80 -0
- package/_esm/actions/lunar-indexer-transformers.js.map +1 -0
- package/_esm/environments/definitions/base/environment.js +2 -1
- package/_esm/environments/definitions/base/environment.js.map +1 -1
- package/_esm/environments/definitions/moonbeam/environment.js +2 -1
- package/_esm/environments/definitions/moonbeam/environment.js.map +1 -1
- package/_esm/environments/definitions/optimism/environment.js +2 -1
- package/_esm/environments/definitions/optimism/environment.js.map +1 -1
- package/_esm/environments/types/config.js +1 -0
- package/_esm/environments/types/config.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/utils/lunar-indexer-helpers.js +48 -0
- package/_esm/utils/lunar-indexer-helpers.js.map +1 -0
- package/_types/actions/core/markets/common.d.ts.map +1 -1
- package/_types/actions/core/markets/getMarketSnapshots.d.ts +4 -0
- package/_types/actions/core/markets/getMarketSnapshots.d.ts.map +1 -1
- package/_types/actions/core/user-positions/common.d.ts.map +1 -1
- package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts +28 -0
- package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts.map +1 -1
- package/_types/actions/lunar-indexer-client.d.ts +197 -0
- package/_types/actions/lunar-indexer-client.d.ts.map +1 -0
- package/_types/actions/lunar-indexer-transformers.d.ts +40 -0
- package/_types/actions/lunar-indexer-transformers.d.ts.map +1 -0
- package/_types/environments/definitions/base/environment.d.ts +1 -1
- package/_types/environments/definitions/base/environment.d.ts.map +1 -1
- package/_types/environments/definitions/moonbeam/environment.d.ts +1 -1
- package/_types/environments/definitions/moonbeam/environment.d.ts.map +1 -1
- package/_types/environments/definitions/optimism/environment.d.ts +1 -1
- package/_types/environments/definitions/optimism/environment.d.ts.map +1 -1
- package/_types/environments/types/config.d.ts +2 -0
- package/_types/environments/types/config.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/utils/lunar-indexer-helpers.d.ts +38 -0
- package/_types/utils/lunar-indexer-helpers.d.ts.map +1 -0
- package/actions/core/markets/common.ts +500 -5
- package/actions/core/markets/getMarketSnapshots.ts +153 -2
- package/actions/core/user-positions/common.ts +139 -6
- package/actions/core/user-positions/getUserPositionSnapshots.ts +175 -1
- package/actions/lunar-indexer-client.ts +409 -0
- package/actions/lunar-indexer-transformers.ts +113 -0
- package/environments/definitions/base/environment.ts +3 -0
- package/environments/definitions/moonbeam/environment.ts +3 -0
- package/environments/definitions/optimism/environment.ts +3 -0
- package/environments/types/config.ts +3 -0
- package/errors/version.ts +1 -1
- package/package.json +1 -1
- package/utils/lunar-indexer-helpers.ts +57 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lunar Indexer API Client
|
|
3
|
+
*
|
|
4
|
+
* Client for interacting with the Lunar Indexer REST API endpoints.
|
|
5
|
+
* Provides functions for fetching comptroller, market, token, and portfolio data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import axios, { type AxiosInstance, type AxiosError } from "axios";
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type Definitions
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export interface LunarIndexerConfig {
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LunarPaginatedResponse<T> {
|
|
20
|
+
results: T[];
|
|
21
|
+
nextCursor: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LunarSnapshotOptions {
|
|
25
|
+
limit?: number;
|
|
26
|
+
cursor?: string;
|
|
27
|
+
granularity?: "15m" | "1h" | "6h" | "1d";
|
|
28
|
+
startTime?: number;
|
|
29
|
+
endTime?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LunarPortfolioOptions {
|
|
33
|
+
startTime: number;
|
|
34
|
+
endTime: number;
|
|
35
|
+
granularity?: "1h" | "6h" | "1d";
|
|
36
|
+
chainId?: number;
|
|
37
|
+
market?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LunarComptroller {
|
|
41
|
+
id: string;
|
|
42
|
+
chainId: number;
|
|
43
|
+
address: string;
|
|
44
|
+
priceOracleAddress: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LunarMarket {
|
|
48
|
+
id: string;
|
|
49
|
+
chainId: number;
|
|
50
|
+
address: string;
|
|
51
|
+
underlyingTokenAddress: string;
|
|
52
|
+
collateralFactor: number;
|
|
53
|
+
interestRateModelAddress: string;
|
|
54
|
+
priceFeedAddress: string;
|
|
55
|
+
reserveFactor: string;
|
|
56
|
+
blockNumber: string;
|
|
57
|
+
timestamp: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Full market data with all real-time fields from Lunar Indexer
|
|
62
|
+
* Based on actual API responses from /markets/:chainId and /market/:marketId
|
|
63
|
+
*/
|
|
64
|
+
export interface LunarMarketFull {
|
|
65
|
+
id: string;
|
|
66
|
+
chainId: number;
|
|
67
|
+
address: string;
|
|
68
|
+
underlyingTokenAddress: string;
|
|
69
|
+
comptrollerAddress: string;
|
|
70
|
+
|
|
71
|
+
totalBorrows: number;
|
|
72
|
+
totalBorrowsUsd: number;
|
|
73
|
+
totalSupply: number;
|
|
74
|
+
totalSupplyUsd: number;
|
|
75
|
+
totalReserves: number;
|
|
76
|
+
totalReservesUsd: number;
|
|
77
|
+
cash: number;
|
|
78
|
+
cashUsd: number;
|
|
79
|
+
badDebt: number;
|
|
80
|
+
badDebtUsd: number;
|
|
81
|
+
|
|
82
|
+
exchangeRate: number;
|
|
83
|
+
priceUsd: number;
|
|
84
|
+
baseSupplyApy: number;
|
|
85
|
+
baseBorrowApy: number;
|
|
86
|
+
|
|
87
|
+
mintPaused: boolean;
|
|
88
|
+
borrowPaused: boolean;
|
|
89
|
+
seizePaused: boolean;
|
|
90
|
+
transferPaused: boolean;
|
|
91
|
+
|
|
92
|
+
borrowCap: number;
|
|
93
|
+
supplyCap: number;
|
|
94
|
+
|
|
95
|
+
collateralFactor: number;
|
|
96
|
+
reserveFactor: string;
|
|
97
|
+
|
|
98
|
+
incentives: Array<{
|
|
99
|
+
token: string;
|
|
100
|
+
supplyIncentivesPerSec: string | number;
|
|
101
|
+
borrowIncentivesPerSec: string | number;
|
|
102
|
+
priceUsd: number | null;
|
|
103
|
+
supplyApr: number | null;
|
|
104
|
+
borrowApr: number | null;
|
|
105
|
+
}>;
|
|
106
|
+
|
|
107
|
+
underlyingToken: {
|
|
108
|
+
id: string;
|
|
109
|
+
chainId: number;
|
|
110
|
+
address: string;
|
|
111
|
+
name: string;
|
|
112
|
+
symbol: string;
|
|
113
|
+
decimals: number;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
blockNumber: string;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface LunarMarketWithToken extends LunarMarket {
|
|
121
|
+
underlyingToken: LunarToken;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface LunarToken {
|
|
125
|
+
id: string;
|
|
126
|
+
chainId: number;
|
|
127
|
+
address: string;
|
|
128
|
+
name: string;
|
|
129
|
+
symbol: string;
|
|
130
|
+
decimals: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface LunarMarketSnapshot {
|
|
134
|
+
id: string;
|
|
135
|
+
chainId: number;
|
|
136
|
+
marketAddress: string;
|
|
137
|
+
timestamp: number;
|
|
138
|
+
blockNumber: string;
|
|
139
|
+
totalBorrows: number;
|
|
140
|
+
totalBorrowsUSD: number;
|
|
141
|
+
totalSupplies: number;
|
|
142
|
+
totalSuppliesUSD: number;
|
|
143
|
+
totalLiquidity: number;
|
|
144
|
+
totalLiquidityUSD: number;
|
|
145
|
+
totalReserves: number;
|
|
146
|
+
totalReservesUSD: number;
|
|
147
|
+
baseSupplyApy: number;
|
|
148
|
+
baseBorrowApy: number;
|
|
149
|
+
timeInterval: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface LunarPortfolio {
|
|
153
|
+
account: string;
|
|
154
|
+
positions: Array<{
|
|
155
|
+
timestamp: number;
|
|
156
|
+
markets: Array<{
|
|
157
|
+
chainId: number;
|
|
158
|
+
marketAddress: string;
|
|
159
|
+
supplyBalance: string;
|
|
160
|
+
supplyBalanceUsd: string;
|
|
161
|
+
borrowBalance: string;
|
|
162
|
+
borrowBalanceUsd: string;
|
|
163
|
+
}>;
|
|
164
|
+
}>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Error Handling
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
export class LunarIndexerError extends Error {
|
|
172
|
+
constructor(
|
|
173
|
+
message: string,
|
|
174
|
+
public readonly statusCode?: number,
|
|
175
|
+
public readonly endpoint?: string,
|
|
176
|
+
public readonly originalError?: Error,
|
|
177
|
+
) {
|
|
178
|
+
super(message);
|
|
179
|
+
this.name = "LunarIndexerError";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Determine if an error should trigger fallback to Ponder/on-chain
|
|
185
|
+
*/
|
|
186
|
+
export function shouldFallback(error: unknown): boolean {
|
|
187
|
+
if (axios.isAxiosError(error)) {
|
|
188
|
+
const axiosError = error as AxiosError;
|
|
189
|
+
|
|
190
|
+
const isNetworkError = !axiosError.response;
|
|
191
|
+
const is5xxError =
|
|
192
|
+
!!axiosError.response && axiosError.response.status >= 500;
|
|
193
|
+
const is404Error =
|
|
194
|
+
!!axiosError.response && axiosError.response.status === 404;
|
|
195
|
+
|
|
196
|
+
if (isNetworkError || is5xxError || is404Error) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 4xx errors (except 404) should NOT fallback - fail fast
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unknown errors should fallback
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const DEFAULT_LUNAR_TIMEOUT_MS = 10_000;
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Lunar Indexer Client Class
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
export class LunarIndexerClient {
|
|
215
|
+
private client: AxiosInstance;
|
|
216
|
+
|
|
217
|
+
constructor(config: LunarIndexerConfig) {
|
|
218
|
+
this.client = axios.create({
|
|
219
|
+
baseURL: `${config.baseUrl}/api/v1/core`,
|
|
220
|
+
timeout: config.timeout || DEFAULT_LUNAR_TIMEOUT_MS,
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "application/json",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get comptroller data for a specific chain
|
|
229
|
+
*/
|
|
230
|
+
async getComptroller(chainId: number): Promise<LunarComptroller> {
|
|
231
|
+
try {
|
|
232
|
+
const response = await this.client.get<LunarComptroller>(
|
|
233
|
+
`/comptroller/${chainId}`,
|
|
234
|
+
);
|
|
235
|
+
return response.data;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw new LunarIndexerError(
|
|
238
|
+
`Failed to fetch comptroller for chain ${chainId}`,
|
|
239
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
240
|
+
`/comptroller/${chainId}`,
|
|
241
|
+
error as Error,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* List all markets for a specific chain with pagination
|
|
248
|
+
* Returns full market data with real-time values, APYs, and incentives
|
|
249
|
+
*/
|
|
250
|
+
async listMarkets(
|
|
251
|
+
chainId: number,
|
|
252
|
+
options?: { limit?: number; cursor?: string },
|
|
253
|
+
): Promise<LunarPaginatedResponse<LunarMarketFull>> {
|
|
254
|
+
try {
|
|
255
|
+
const params: Record<string, string> = {};
|
|
256
|
+
if (options?.limit) params.limit = options.limit.toString();
|
|
257
|
+
if (options?.cursor) params.cursor = options.cursor;
|
|
258
|
+
|
|
259
|
+
const response = await this.client.get<
|
|
260
|
+
LunarPaginatedResponse<LunarMarketFull>
|
|
261
|
+
>(`/markets/${chainId}`, { params });
|
|
262
|
+
return response.data;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw new LunarIndexerError(
|
|
265
|
+
`Failed to list markets for chain ${chainId}`,
|
|
266
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
267
|
+
`/markets/${chainId}`,
|
|
268
|
+
error as Error,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get a single market by marketId (format: chainId-marketAddress)
|
|
275
|
+
* Returns full market data with real-time values, APYs, and incentives
|
|
276
|
+
*/
|
|
277
|
+
async getMarket(marketId: string): Promise<LunarMarketFull> {
|
|
278
|
+
try {
|
|
279
|
+
const response = await this.client.get<LunarMarketFull>(
|
|
280
|
+
`/market/${marketId}`,
|
|
281
|
+
);
|
|
282
|
+
return response.data;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw new LunarIndexerError(
|
|
285
|
+
`Failed to fetch market ${marketId}`,
|
|
286
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
287
|
+
`/market/${marketId}`,
|
|
288
|
+
error as Error,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get market snapshots with optional time range and granularity
|
|
295
|
+
*/
|
|
296
|
+
async getMarketSnapshots(
|
|
297
|
+
marketId: string,
|
|
298
|
+
options?: LunarSnapshotOptions,
|
|
299
|
+
): Promise<LunarPaginatedResponse<LunarMarketSnapshot>> {
|
|
300
|
+
try {
|
|
301
|
+
const params: Record<string, string> = {};
|
|
302
|
+
if (options?.limit) params.limit = options.limit.toString();
|
|
303
|
+
if (options?.cursor) params.cursor = options.cursor;
|
|
304
|
+
if (options?.granularity) params.granularity = options.granularity;
|
|
305
|
+
if (options?.startTime) params.startTime = options.startTime.toString();
|
|
306
|
+
if (options?.endTime) params.endTime = options.endTime.toString();
|
|
307
|
+
|
|
308
|
+
const response = await this.client.get<
|
|
309
|
+
LunarPaginatedResponse<LunarMarketSnapshot>
|
|
310
|
+
>(`/market/${marketId}/snapshots`, { params });
|
|
311
|
+
return response.data;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
throw new LunarIndexerError(
|
|
314
|
+
`Failed to fetch market snapshots for ${marketId}`,
|
|
315
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
316
|
+
`/market/${marketId}/snapshots`,
|
|
317
|
+
error as Error,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* List all tokens for a specific chain with pagination
|
|
324
|
+
*/
|
|
325
|
+
async listTokens(
|
|
326
|
+
chainId: number,
|
|
327
|
+
options?: { limit?: number; cursor?: string },
|
|
328
|
+
): Promise<LunarPaginatedResponse<LunarToken>> {
|
|
329
|
+
try {
|
|
330
|
+
const params: Record<string, string> = {};
|
|
331
|
+
if (options?.limit) params.limit = options.limit.toString();
|
|
332
|
+
if (options?.cursor) params.cursor = options.cursor;
|
|
333
|
+
|
|
334
|
+
const response = await this.client.get<
|
|
335
|
+
LunarPaginatedResponse<LunarToken>
|
|
336
|
+
>(`/tokens/${chainId}`, { params });
|
|
337
|
+
return response.data;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
throw new LunarIndexerError(
|
|
340
|
+
`Failed to list tokens for chain ${chainId}`,
|
|
341
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
342
|
+
`/tokens/${chainId}`,
|
|
343
|
+
error as Error,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get a single token by tokenId (format: chainId-tokenAddress)
|
|
350
|
+
*/
|
|
351
|
+
async getToken(tokenId: string): Promise<LunarToken> {
|
|
352
|
+
try {
|
|
353
|
+
const response = await this.client.get<LunarToken>(`/token/${tokenId}`);
|
|
354
|
+
return response.data;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
throw new LunarIndexerError(
|
|
357
|
+
`Failed to fetch token ${tokenId}`,
|
|
358
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
359
|
+
`/token/${tokenId}`,
|
|
360
|
+
error as Error,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get account portfolio with historical positions
|
|
367
|
+
* NOTE: USD fields (supplyBalanceUsd, borrowBalanceUsd) are being added by Lunar team
|
|
368
|
+
*/
|
|
369
|
+
async getAccountPortfolio(
|
|
370
|
+
accountAddress: string,
|
|
371
|
+
options: LunarPortfolioOptions,
|
|
372
|
+
): Promise<LunarPortfolio> {
|
|
373
|
+
try {
|
|
374
|
+
const params: Record<string, string> = {
|
|
375
|
+
startTime: options.startTime.toString(),
|
|
376
|
+
endTime: options.endTime.toString(),
|
|
377
|
+
};
|
|
378
|
+
if (options.granularity) params.granularity = options.granularity;
|
|
379
|
+
if (options.chainId) params.chainId = options.chainId.toString();
|
|
380
|
+
if (options.market) params.market = options.market;
|
|
381
|
+
|
|
382
|
+
const response = await this.client.get<LunarPortfolio>(
|
|
383
|
+
`/account/${accountAddress.toLowerCase()}/portfolio`,
|
|
384
|
+
{ params },
|
|
385
|
+
);
|
|
386
|
+
return response.data;
|
|
387
|
+
} catch (error) {
|
|
388
|
+
throw new LunarIndexerError(
|
|
389
|
+
`Failed to fetch portfolio for account ${accountAddress}`,
|
|
390
|
+
axios.isAxiosError(error) ? error.response?.status : undefined,
|
|
391
|
+
`/account/${accountAddress}/portfolio`,
|
|
392
|
+
error as Error,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ============================================================================
|
|
399
|
+
// Factory Function
|
|
400
|
+
// ============================================================================
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Create a new Lunar Indexer client instance
|
|
404
|
+
*/
|
|
405
|
+
export function createLunarIndexerClient(
|
|
406
|
+
config: LunarIndexerConfig,
|
|
407
|
+
): LunarIndexerClient {
|
|
408
|
+
return new LunarIndexerClient(config);
|
|
409
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lunar Indexer Response Transformers
|
|
3
|
+
*
|
|
4
|
+
* Transforms Lunar Indexer API responses to match the SDK's internal types.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MarketSnapshot } from "../types/market.js";
|
|
8
|
+
import type { UserPositionSnapshot } from "../types/userPosition.js";
|
|
9
|
+
import type {
|
|
10
|
+
LunarMarketSnapshot,
|
|
11
|
+
LunarPortfolio,
|
|
12
|
+
} from "./lunar-indexer-client.js";
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Market Snapshot Transformation
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Transform a Lunar market snapshot to SDK MarketSnapshot format
|
|
20
|
+
*
|
|
21
|
+
* Key differences:
|
|
22
|
+
* - Lunar uses decimal numbers, SDK uses numbers
|
|
23
|
+
* - Lunar has timeInterval field (not needed in SDK)
|
|
24
|
+
* - Field name mapping: totalSupplies → totalSupply, totalBorrows → totalBorrows
|
|
25
|
+
*/
|
|
26
|
+
export function transformMarketSnapshot(
|
|
27
|
+
lunar: LunarMarketSnapshot,
|
|
28
|
+
chainId: number,
|
|
29
|
+
): MarketSnapshot {
|
|
30
|
+
return {
|
|
31
|
+
chainId,
|
|
32
|
+
marketId: lunar.marketAddress,
|
|
33
|
+
totalSupply: Number(lunar.totalSupplies),
|
|
34
|
+
totalSupplyUsd: Number(lunar.totalSuppliesUSD),
|
|
35
|
+
totalBorrows: Number(lunar.totalBorrows),
|
|
36
|
+
totalBorrowsUsd: Number(lunar.totalBorrowsUSD),
|
|
37
|
+
totalLiquidity: Number(lunar.totalLiquidity),
|
|
38
|
+
totalLiquidityUsd: Number(lunar.totalLiquidityUSD),
|
|
39
|
+
baseSupplyApy: Number(lunar.baseSupplyApy),
|
|
40
|
+
baseBorrowApy: Number(lunar.baseBorrowApy),
|
|
41
|
+
timestamp: Number(lunar.timestamp) * 1000, // Convert unix timestamp to milliseconds
|
|
42
|
+
loanTokenPrice: 0, // Calculated in getMarketSnapshots
|
|
43
|
+
collateralTokenPrice: 0, // Calculated in getMarketSnapshots
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Transform an array of Lunar market snapshots
|
|
49
|
+
*/
|
|
50
|
+
export function transformMarketSnapshots(
|
|
51
|
+
snapshots: LunarMarketSnapshot[],
|
|
52
|
+
chainId: number,
|
|
53
|
+
): MarketSnapshot[] {
|
|
54
|
+
return snapshots.map((snapshot) =>
|
|
55
|
+
transformMarketSnapshot(snapshot, chainId),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Portfolio/User Position Transformation
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Transform Lunar portfolio to SDK UserPositionSnapshot array
|
|
65
|
+
*
|
|
66
|
+
* Key transformation:
|
|
67
|
+
* - Aggregate per-market USD balances across all markets per timestamp
|
|
68
|
+
* - Sum supplyBalanceUsd → totalSupplyUsd
|
|
69
|
+
* - Sum borrowBalanceUsd → totalBorrowsUsd
|
|
70
|
+
* - Assume totalCollateralUsd = totalSupplyUsd (all supplies are collateral)
|
|
71
|
+
*/
|
|
72
|
+
export function transformPortfolioToSnapshots(
|
|
73
|
+
lunarPortfolio: LunarPortfolio,
|
|
74
|
+
chainId: number,
|
|
75
|
+
): UserPositionSnapshot[] {
|
|
76
|
+
return lunarPortfolio.positions.map((position) => {
|
|
77
|
+
const totalSupplyUsd = position.markets.reduce(
|
|
78
|
+
(sum, market) => sum + Number.parseFloat(market.supplyBalanceUsd || "0"),
|
|
79
|
+
0,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const totalBorrowsUsd = position.markets.reduce(
|
|
83
|
+
(sum, market) => sum + Number.parseFloat(market.borrowBalanceUsd || "0"),
|
|
84
|
+
0,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
chainId,
|
|
89
|
+
timestamp: position.timestamp * 1000, // Convert unix timestamp (seconds) to milliseconds
|
|
90
|
+
totalSupplyUsd,
|
|
91
|
+
totalBorrowsUsd,
|
|
92
|
+
totalCollateralUsd: totalSupplyUsd, // Assuming all supplies are collateral
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Pagination Transformation
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if there are more pages available
|
|
103
|
+
*/
|
|
104
|
+
export function hasMorePages(nextCursor: string | null): boolean {
|
|
105
|
+
return nextCursor !== null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extract cursor for next page
|
|
110
|
+
*/
|
|
111
|
+
export function getNextCursor(nextCursor: string | null): string | undefined {
|
|
112
|
+
return nextCursor || undefined;
|
|
113
|
+
}
|
|
@@ -17,6 +17,7 @@ const createEnvironment = (
|
|
|
17
17
|
rpcUrls?: string[],
|
|
18
18
|
indexerUrl?: string,
|
|
19
19
|
governanceIndexerUrl?: string,
|
|
20
|
+
lunarIndexerUrl?: string,
|
|
20
21
|
): Environment<
|
|
21
22
|
typeof tokens,
|
|
22
23
|
typeof markets,
|
|
@@ -40,6 +41,8 @@ const createEnvironment = (
|
|
|
40
41
|
governanceIndexerUrl:
|
|
41
42
|
governanceIndexerUrl ||
|
|
42
43
|
"https://lunar-services-worker.moonwell.workers.dev",
|
|
44
|
+
lunarIndexerUrl:
|
|
45
|
+
lunarIndexerUrl || "https://lunar-services-worker.moonwell.workers.dev",
|
|
43
46
|
tokens,
|
|
44
47
|
markets,
|
|
45
48
|
vaults,
|
|
@@ -10,6 +10,7 @@ const createEnvironment = (
|
|
|
10
10
|
rpcUrls?: string[],
|
|
11
11
|
indexerUrl?: string,
|
|
12
12
|
governanceIndexerUrl?: string,
|
|
13
|
+
lunarIndexerUrl?: string,
|
|
13
14
|
) =>
|
|
14
15
|
createEnvironmentConfig({
|
|
15
16
|
key: "moonbeam",
|
|
@@ -24,6 +25,8 @@ const createEnvironment = (
|
|
|
24
25
|
? fallback(rpcUrls.map((url) => http(url)))
|
|
25
26
|
: http("https://rpc.moonwell.fi/main/evm/1284"),
|
|
26
27
|
indexerUrl: indexerUrl || "https://ponder.moonwell.fi",
|
|
28
|
+
lunarIndexerUrl:
|
|
29
|
+
lunarIndexerUrl || "https://lunar-services-worker.moonwell.workers.dev",
|
|
27
30
|
governanceIndexerUrl:
|
|
28
31
|
governanceIndexerUrl ||
|
|
29
32
|
"https://lunar-services-worker.moonwell.workers.dev",
|
|
@@ -13,6 +13,7 @@ const createEnvironment = (
|
|
|
13
13
|
rpcUrls?: string[],
|
|
14
14
|
indexerUrl?: string,
|
|
15
15
|
governanceIndexerUrl?: string,
|
|
16
|
+
lunarIndexerUrl?: string,
|
|
16
17
|
) =>
|
|
17
18
|
createEnvironmentConfig({
|
|
18
19
|
key: "optimism",
|
|
@@ -27,6 +28,8 @@ const createEnvironment = (
|
|
|
27
28
|
? fallback(rpcUrls.map((url) => http(url)))
|
|
28
29
|
: http("https://rpc.moonwell.fi/main/evm/10"),
|
|
29
30
|
indexerUrl: indexerUrl || "https://ponder.moonwell.fi",
|
|
31
|
+
lunarIndexerUrl:
|
|
32
|
+
lunarIndexerUrl || "https://lunar-services-worker.moonwell.workers.dev",
|
|
30
33
|
governanceIndexerUrl:
|
|
31
34
|
governanceIndexerUrl ||
|
|
32
35
|
"https://lunar-services-worker.moonwell.workers.dev",
|
|
@@ -231,6 +231,7 @@ export const createEnvironmentConfig = <
|
|
|
231
231
|
transport: Transport;
|
|
232
232
|
indexerUrl: string;
|
|
233
233
|
governanceIndexerUrl: string;
|
|
234
|
+
lunarIndexerUrl?: string;
|
|
234
235
|
tokens: TokensConfig<tokens>;
|
|
235
236
|
markets: MarketsConfig<markets, tokens>;
|
|
236
237
|
vaults: VaultsConfig<vaults, tokens>;
|
|
@@ -494,6 +495,7 @@ export const createEnvironmentConfig = <
|
|
|
494
495
|
chain: config.chain,
|
|
495
496
|
indexerUrl: config.indexerUrl,
|
|
496
497
|
governanceIndexerUrl: config.governanceIndexerUrl,
|
|
498
|
+
lunarIndexerUrl: config.lunarIndexerUrl,
|
|
497
499
|
tokens: tokenContracts,
|
|
498
500
|
markets: marketContracts,
|
|
499
501
|
vaults: vaultsContracts,
|
|
@@ -531,6 +533,7 @@ export type Environment<
|
|
|
531
533
|
chain: Chain;
|
|
532
534
|
indexerUrl: string;
|
|
533
535
|
governanceIndexerUrl: string;
|
|
536
|
+
lunarIndexerUrl?: string;
|
|
534
537
|
tokens: {
|
|
535
538
|
[name in keyof tokens]: TokenContractReturnType;
|
|
536
539
|
};
|
package/errors/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.9.
|
|
1
|
+
export const version = '0.9.28'
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for working with Lunar Indexer marketId and tokenId formats
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a marketId string from chainId and market address
|
|
7
|
+
* Format: {chainId}-{marketAddress}
|
|
8
|
+
* @param chainId - The chain ID
|
|
9
|
+
* @param marketAddress - The market contract address
|
|
10
|
+
* @returns Formatted marketId string
|
|
11
|
+
*/
|
|
12
|
+
export function buildMarketId(chainId: number, marketAddress: string): string {
|
|
13
|
+
return `${chainId}-${marketAddress.toLowerCase()}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a marketId string into chainId and marketAddress
|
|
18
|
+
* @param marketId - The marketId string to parse
|
|
19
|
+
* @returns Object containing chainId and marketAddress
|
|
20
|
+
*/
|
|
21
|
+
export function parseMarketId(marketId: string): {
|
|
22
|
+
chainId: number;
|
|
23
|
+
marketAddress: string;
|
|
24
|
+
} {
|
|
25
|
+
const [chainIdStr, ...addressParts] = marketId.split("-");
|
|
26
|
+
return {
|
|
27
|
+
chainId: Number.parseInt(chainIdStr, 10),
|
|
28
|
+
marketAddress: addressParts.join("-"), // Handle addresses that might contain dashes
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build a tokenId string from chainId and token address
|
|
34
|
+
* Format: {chainId}-{tokenAddress}
|
|
35
|
+
* @param chainId - The chain ID
|
|
36
|
+
* @param tokenAddress - The token contract address
|
|
37
|
+
* @returns Formatted tokenId string
|
|
38
|
+
*/
|
|
39
|
+
export function buildTokenId(chainId: number, tokenAddress: string): string {
|
|
40
|
+
return `${chainId}-${tokenAddress.toLowerCase()}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse a tokenId string into chainId and tokenAddress
|
|
45
|
+
* @param tokenId - The tokenId string to parse
|
|
46
|
+
* @returns Object containing chainId and tokenAddress
|
|
47
|
+
*/
|
|
48
|
+
export function parseTokenId(tokenId: string): {
|
|
49
|
+
chainId: number;
|
|
50
|
+
tokenAddress: string;
|
|
51
|
+
} {
|
|
52
|
+
const [chainIdStr, ...addressParts] = tokenId.split("-");
|
|
53
|
+
return {
|
|
54
|
+
chainId: Number.parseInt(chainIdStr, 10),
|
|
55
|
+
tokenAddress: addressParts.join("-"), // Handle addresses that might contain dashes
|
|
56
|
+
};
|
|
57
|
+
}
|