@moonwell-fi/moonwell-sdk 0.11.0 → 0.12.0

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/_cjs/actions/core/markets/common.js +26 -12
  3. package/_cjs/actions/core/markets/common.js.map +1 -1
  4. package/_cjs/actions/core/markets/getMarketSnapshots.js +173 -321
  5. package/_cjs/actions/core/markets/getMarketSnapshots.js.map +1 -1
  6. package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js +31 -26
  7. package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
  8. package/_cjs/actions/governance/getCirculatingSupplySnapshots.js +35 -29
  9. package/_cjs/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
  10. package/_cjs/actions/governance/getStakingSnapshots.js +16 -12
  11. package/_cjs/actions/governance/getStakingSnapshots.js.map +1 -1
  12. package/_cjs/actions/governance/proposals/common.js +247 -211
  13. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  14. package/_cjs/actions/morpho/markets/common.js +26 -29
  15. package/_cjs/actions/morpho/markets/common.js.map +1 -1
  16. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.js +12 -69
  17. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.js.map +1 -1
  18. package/_cjs/actions/morpho/vaults/common.js +7 -4
  19. package/_cjs/actions/morpho/vaults/common.js.map +1 -1
  20. package/_cjs/actions/morpho/vaults/getMorphoVaultSnapshots.js +21 -78
  21. package/_cjs/actions/morpho/vaults/getMorphoVaultSnapshots.js.map +1 -1
  22. package/_cjs/actions/morpho/vaults/getMorphoVaultStakingSnapshots.js +13 -66
  23. package/_cjs/actions/morpho/vaults/getMorphoVaultStakingSnapshots.js.map +1 -1
  24. package/_cjs/client/createMoonwellClient.js +9 -3
  25. package/_cjs/client/createMoonwellClient.js.map +1 -1
  26. package/_cjs/environments/definitions/arbitrum/environment.js +1 -2
  27. package/_cjs/environments/definitions/arbitrum/environment.js.map +1 -1
  28. package/_cjs/environments/definitions/avalanche/environment.js +1 -2
  29. package/_cjs/environments/definitions/avalanche/environment.js.map +1 -1
  30. package/_cjs/environments/definitions/base/custom.js +0 -1
  31. package/_cjs/environments/definitions/base/custom.js.map +1 -1
  32. package/_cjs/environments/definitions/base/environment.js +1 -2
  33. package/_cjs/environments/definitions/base/environment.js.map +1 -1
  34. package/_cjs/environments/definitions/ethereum/environment.js +1 -2
  35. package/_cjs/environments/definitions/ethereum/environment.js.map +1 -1
  36. package/_cjs/environments/definitions/moonbeam/environment.js +1 -2
  37. package/_cjs/environments/definitions/moonbeam/environment.js.map +1 -1
  38. package/_cjs/environments/definitions/optimism/custom.js +0 -1
  39. package/_cjs/environments/definitions/optimism/custom.js.map +1 -1
  40. package/_cjs/environments/definitions/optimism/environment.js +1 -2
  41. package/_cjs/environments/definitions/optimism/environment.js.map +1 -1
  42. package/_cjs/environments/definitions/polygon/environment.js +1 -2
  43. package/_cjs/environments/definitions/polygon/environment.js.map +1 -1
  44. package/_cjs/environments/index.js +8 -8
  45. package/_cjs/environments/index.js.map +1 -1
  46. package/_cjs/environments/types/config.js +1 -0
  47. package/_cjs/environments/types/config.js.map +1 -1
  48. package/_cjs/errors/version.js +1 -1
  49. package/_esm/actions/core/markets/common.js +26 -13
  50. package/_esm/actions/core/markets/common.js.map +1 -1
  51. package/_esm/actions/core/markets/getMarketSnapshots.js +194 -346
  52. package/_esm/actions/core/markets/getMarketSnapshots.js.map +1 -1
  53. package/_esm/actions/core/user-positions/getUserPositionSnapshots.js +32 -29
  54. package/_esm/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
  55. package/_esm/actions/governance/getCirculatingSupplySnapshots.js +35 -30
  56. package/_esm/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
  57. package/_esm/actions/governance/getStakingSnapshots.js +17 -13
  58. package/_esm/actions/governance/getStakingSnapshots.js.map +1 -1
  59. package/_esm/actions/governance/proposals/common.js +246 -216
  60. package/_esm/actions/governance/proposals/common.js.map +1 -1
  61. package/_esm/actions/morpho/markets/common.js +27 -34
  62. package/_esm/actions/morpho/markets/common.js.map +1 -1
  63. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.js +14 -68
  64. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.js.map +1 -1
  65. package/_esm/actions/morpho/vaults/common.js +7 -4
  66. package/_esm/actions/morpho/vaults/common.js.map +1 -1
  67. package/_esm/actions/morpho/vaults/getMorphoVaultSnapshots.js +24 -78
  68. package/_esm/actions/morpho/vaults/getMorphoVaultSnapshots.js.map +1 -1
  69. package/_esm/actions/morpho/vaults/getMorphoVaultStakingSnapshots.js +15 -65
  70. package/_esm/actions/morpho/vaults/getMorphoVaultStakingSnapshots.js.map +1 -1
  71. package/_esm/client/createMoonwellClient.js +9 -3
  72. package/_esm/client/createMoonwellClient.js.map +1 -1
  73. package/_esm/environments/definitions/arbitrum/environment.js +1 -2
  74. package/_esm/environments/definitions/arbitrum/environment.js.map +1 -1
  75. package/_esm/environments/definitions/avalanche/environment.js +1 -2
  76. package/_esm/environments/definitions/avalanche/environment.js.map +1 -1
  77. package/_esm/environments/definitions/base/custom.js +0 -1
  78. package/_esm/environments/definitions/base/custom.js.map +1 -1
  79. package/_esm/environments/definitions/base/environment.js +1 -2
  80. package/_esm/environments/definitions/base/environment.js.map +1 -1
  81. package/_esm/environments/definitions/ethereum/environment.js +1 -2
  82. package/_esm/environments/definitions/ethereum/environment.js.map +1 -1
  83. package/_esm/environments/definitions/moonbeam/environment.js +1 -2
  84. package/_esm/environments/definitions/moonbeam/environment.js.map +1 -1
  85. package/_esm/environments/definitions/optimism/custom.js +0 -1
  86. package/_esm/environments/definitions/optimism/custom.js.map +1 -1
  87. package/_esm/environments/definitions/optimism/environment.js +1 -2
  88. package/_esm/environments/definitions/optimism/environment.js.map +1 -1
  89. package/_esm/environments/definitions/polygon/environment.js +1 -2
  90. package/_esm/environments/definitions/polygon/environment.js.map +1 -1
  91. package/_esm/environments/index.js +8 -8
  92. package/_esm/environments/index.js.map +1 -1
  93. package/_esm/environments/types/config.js +1 -0
  94. package/_esm/environments/types/config.js.map +1 -1
  95. package/_esm/errors/version.js +1 -1
  96. package/_types/actions/core/markets/common.d.ts.map +1 -1
  97. package/_types/actions/core/markets/getMarketSnapshots.d.ts.map +1 -1
  98. package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts +0 -1
  99. package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts.map +1 -1
  100. package/_types/actions/governance/getCirculatingSupplySnapshots.d.ts.map +1 -1
  101. package/_types/actions/governance/getStakingSnapshots.d.ts.map +1 -1
  102. package/_types/actions/governance/proposals/common.d.ts +2 -8
  103. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  104. package/_types/actions/morpho/markets/common.d.ts.map +1 -1
  105. package/_types/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.d.ts.map +1 -1
  106. package/_types/actions/morpho/vaults/common.d.ts.map +1 -1
  107. package/_types/actions/morpho/vaults/getMorphoVaultSnapshots.d.ts.map +1 -1
  108. package/_types/actions/morpho/vaults/getMorphoVaultStakingSnapshots.d.ts.map +1 -1
  109. package/_types/client/createMoonwellClient.d.ts +4 -4
  110. package/_types/client/createMoonwellClient.d.ts.map +1 -1
  111. package/_types/environments/definitions/arbitrum/environment.d.ts +1 -1
  112. package/_types/environments/definitions/arbitrum/environment.d.ts.map +1 -1
  113. package/_types/environments/definitions/avalanche/environment.d.ts +1 -1
  114. package/_types/environments/definitions/avalanche/environment.d.ts.map +1 -1
  115. package/_types/environments/definitions/base/custom.d.ts +0 -1
  116. package/_types/environments/definitions/base/custom.d.ts.map +1 -1
  117. package/_types/environments/definitions/base/environment.d.ts +1 -1
  118. package/_types/environments/definitions/base/environment.d.ts.map +1 -1
  119. package/_types/environments/definitions/ethereum/environment.d.ts +1 -1
  120. package/_types/environments/definitions/ethereum/environment.d.ts.map +1 -1
  121. package/_types/environments/definitions/moonbeam/environment.d.ts +1 -1
  122. package/_types/environments/definitions/moonbeam/environment.d.ts.map +1 -1
  123. package/_types/environments/definitions/optimism/custom.d.ts +0 -1
  124. package/_types/environments/definitions/optimism/custom.d.ts.map +1 -1
  125. package/_types/environments/definitions/optimism/environment.d.ts +1 -2
  126. package/_types/environments/definitions/optimism/environment.d.ts.map +1 -1
  127. package/_types/environments/definitions/polygon/environment.d.ts +1 -1
  128. package/_types/environments/definitions/polygon/environment.d.ts.map +1 -1
  129. package/_types/environments/index.d.ts +0 -3
  130. package/_types/environments/index.d.ts.map +1 -1
  131. package/_types/environments/types/config.d.ts +10 -3
  132. package/_types/environments/types/config.d.ts.map +1 -1
  133. package/_types/errors/version.d.ts +1 -1
  134. package/actions/core/markets/common.ts +33 -18
  135. package/actions/core/markets/getMarketSnapshots.ts +261 -511
  136. package/actions/core/user-positions/getUserPositionSnapshots.ts +44 -46
  137. package/actions/governance/getCirculatingSupplySnapshots.ts +43 -35
  138. package/actions/governance/getStakingSnapshots.ts +32 -29
  139. package/actions/governance/proposals/common.ts +369 -330
  140. package/actions/morpho/markets/common.ts +32 -43
  141. package/actions/morpho/user-positions/getMorphoVaultUserPositionSnapshots.ts +20 -109
  142. package/actions/morpho/vaults/common.ts +7 -6
  143. package/actions/morpho/vaults/getMorphoVaultSnapshots.ts +36 -120
  144. package/actions/morpho/vaults/getMorphoVaultStakingSnapshots.ts +24 -111
  145. package/client/createMoonwellClient.ts +17 -5
  146. package/environments/definitions/arbitrum/environment.ts +0 -2
  147. package/environments/definitions/avalanche/environment.ts +0 -2
  148. package/environments/definitions/base/custom.ts +0 -1
  149. package/environments/definitions/base/environment.ts +0 -2
  150. package/environments/definitions/ethereum/environment.ts +0 -2
  151. package/environments/definitions/moonbeam/environment.ts +0 -2
  152. package/environments/definitions/optimism/custom.ts +0 -1
  153. package/environments/definitions/optimism/environment.ts +0 -2
  154. package/environments/definitions/polygon/environment.ts +0 -2
  155. package/environments/index.ts +6 -27
  156. package/environments/types/config.ts +11 -3
  157. package/errors/version.ts +1 -1
  158. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  import axios from "axios";
2
2
  import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
3
3
  import {
4
- Amount,
5
4
  type SnapshotPeriod,
6
5
  applyGranularity,
7
6
  calculateTimeRange,
@@ -16,7 +15,6 @@ import type { MarketSnapshot } from "../../../types/market.js";
16
15
  import {
17
16
  DEFAULT_LUNAR_TIMEOUT_MS,
18
17
  createLunarIndexerClient,
19
- shouldFallback,
20
18
  } from "../../lunar-indexer-client.js";
21
19
  import { transformMarketSnapshots } from "../../lunar-indexer-transformers.js";
22
20
  import {
@@ -93,9 +91,7 @@ export async function getMarketSnapshots<
93
91
  args.endTime,
94
92
  );
95
93
 
96
- // Isolated markets can receive snapshots from multiple code paths (lunar indexer,
97
- // Morpho Blue API, subgraph, or a mix). Apply a final sanity check to ensure
98
- // totalSupply is always in loan token units.
94
+ // Apply a final sanity check to ensure totalSupply is always in loan token units.
99
95
  //
100
96
  // Some markets (e.g. USDC/ETH where collateral=USDC, loan=WETH) normalize
101
97
  // totalSupply to collateral units — skip the check for those because
@@ -144,103 +140,51 @@ async function fetchCoreMarketSnapshots(
144
140
  startTime?: number,
145
141
  endTime?: number,
146
142
  ): Promise<MarketSnapshot[]> {
147
- if (environment.lunarIndexerUrl) {
143
+ if (!environment.lunarIndexerUrl) {
144
+ if (!environment.indexerUrl) return [];
148
145
  try {
149
- const result = await fetchCoreMarketSnapshotsFromLunar(
146
+ return await fetchCoreMarketSnapshotsFromPonder(
150
147
  marketAddress,
151
148
  environment,
152
- period,
153
- startTime,
154
- endTime,
155
149
  );
156
- return result;
157
150
  } catch (error) {
158
- if (!shouldFallback(error)) {
159
- throw error;
160
- }
161
- console.debug(
162
- "[Lunar fallback] Falling back to Ponder for snapshots:",
151
+ console.warn(
152
+ `[getMarketSnapshots] Ponder failed for chain ${environment.chainId}:`,
163
153
  error,
164
154
  );
155
+ environment.onError?.(error, {
156
+ source: "market-snapshots-ponder",
157
+ chainId: environment.chainId,
158
+ });
159
+ return [];
165
160
  }
166
161
  }
167
-
168
- const result = await fetchCoreMarketSnapshotsFromPonder(
169
- marketAddress,
170
- environment,
171
- );
172
- return result;
173
- }
174
-
175
- async function fetchCoreMarketSnapshotsFromLunar(
176
- marketAddress: string,
177
- environment: Environment,
178
- period?: "1M" | "3M" | "1Y" | "ALL",
179
- customStartTime?: number,
180
- customEndTime?: number,
181
- ): Promise<MarketSnapshot[]> {
182
- if (!environment.lunarIndexerUrl) {
183
- throw new Error("Lunar Indexer URL not configured");
184
- }
185
-
186
- const client = createLunarIndexerClient({
187
- baseUrl: environment.lunarIndexerUrl,
188
- timeout: DEFAULT_LUNAR_TIMEOUT_MS,
189
- });
190
-
191
- const marketId = buildMarketId(environment.chainId, marketAddress);
192
- const { startTime, endTime, granularity } = calculateTimeRange(
193
- period,
194
- customStartTime,
195
- customEndTime,
196
- );
197
-
198
- const allSnapshots: MarketSnapshot[] = [];
199
- let cursor: string | null = null;
200
- const MAX_PAGES = 100;
201
- let page = 0;
202
-
203
- do {
204
- const response = await client.getMarketSnapshots(marketId, {
205
- limit: 1000,
206
- ...(cursor && { cursor }),
207
- granularity: toApiGranularity(granularity),
162
+ try {
163
+ return await fetchCoreMarketSnapshotsFromLunar(
164
+ marketAddress,
165
+ environment,
166
+ period,
208
167
  startTime,
209
168
  endTime,
210
- });
211
-
212
- const transformed = transformMarketSnapshots(
213
- response.results,
214
- environment.chainId,
215
169
  );
216
-
217
- allSnapshots.push(...transformed);
218
-
219
- cursor = response.nextCursor;
220
- page++;
221
- } while (cursor !== null && page < MAX_PAGES);
222
-
223
- allSnapshots.sort((a, b) => a.timestamp - b.timestamp);
224
- return applyGranularity(allSnapshots, granularity).map((snapshot) => {
225
- const supplied = snapshot.totalSupply;
226
- const suppliedUsd = snapshot.totalSupplyUsd;
227
- const price = supplied > 0 ? suppliedUsd / supplied : 0;
228
-
229
- return {
230
- ...snapshot,
231
- collateralTokenPrice: price,
232
- loanTokenPrice: price,
233
- };
234
- });
170
+ } catch (error) {
171
+ console.warn(
172
+ `[getMarketSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`,
173
+ error,
174
+ );
175
+ environment.onError?.(error, {
176
+ source: "market-snapshots",
177
+ chainId: environment.chainId,
178
+ });
179
+ return [];
180
+ }
235
181
  }
236
182
 
237
183
  async function fetchCoreMarketSnapshotsFromPonder(
238
184
  marketAddress: string,
239
185
  environment: Environment,
240
186
  ): Promise<MarketSnapshot[]> {
241
- const dailyData: MarketDailyData[] = [];
242
- let hasNextPage = true;
243
- let endCursor: string | undefined;
187
+ if (!environment.indexerUrl) return [];
244
188
 
245
189
  interface MarketDailyData {
246
190
  totalBorrows: number;
@@ -254,15 +198,16 @@ async function fetchCoreMarketSnapshotsFromPonder(
254
198
  timestamp: number;
255
199
  }
256
200
 
201
+ const dailyData: MarketDailyData[] = [];
202
+ let hasNextPage = true;
203
+ let endCursor: string | undefined;
204
+
257
205
  while (hasNextPage) {
258
206
  const result = await axios.post<{
259
207
  data: {
260
208
  marketDailySnapshots: {
261
209
  items: MarketDailyData[];
262
- pageInfo: {
263
- hasNextPage: boolean;
264
- endCursor: string;
265
- };
210
+ pageInfo: { hasNextPage: boolean; endCursor: string };
266
211
  };
267
212
  };
268
213
  }>(environment.indexerUrl, {
@@ -304,78 +249,97 @@ async function fetchCoreMarketSnapshotsFromPonder(
304
249
  endCursor = result.data.data.marketDailySnapshots.pageInfo.endCursor;
305
250
  }
306
251
 
307
- if (dailyData.length > 0) {
308
- return dailyData.map((point: any) => {
309
- const supplied = Number(point.totalSupplies);
310
- const borrow = Number(point.totalBorrows);
252
+ if (dailyData.length === 0) return [];
311
253
 
312
- const borrowUsd = Number(point.totalBorrowsUSD);
313
- const suppliedUsd = Number(point.totalSuppliesUSD);
314
- const liquidity = Math.max(point.totalLiquidity, 0);
315
- const liquidityUsd = Math.max(point.totalLiquidityUSD, 0);
254
+ return dailyData.map((point) => {
255
+ const supplied = Number(point.totalSupplies);
256
+ const borrow = Number(point.totalBorrows);
257
+ const borrowUsd = Number(point.totalBorrowsUSD);
258
+ const suppliedUsd = Number(point.totalSuppliesUSD);
259
+ const liquidity = Math.max(point.totalLiquidity, 0);
260
+ const liquidityUsd = Math.max(point.totalLiquidityUSD, 0);
261
+ const price = suppliedUsd / supplied;
316
262
 
317
- const price = suppliedUsd / supplied;
263
+ return {
264
+ marketId: marketAddress.toLowerCase(),
265
+ chainId: environment.chainId,
266
+ timestamp: point.timestamp * 1000,
267
+ totalSupply: supplied,
268
+ totalSupplyUsd: suppliedUsd,
269
+ totalBorrows: borrow,
270
+ totalBorrowsUsd: borrowUsd,
271
+ totalLiquidity: liquidity,
272
+ totalLiquidityUsd: liquidityUsd,
273
+ totalReallocatableLiquidity: 0,
274
+ totalReallocatableLiquidityUsd: 0,
275
+ baseSupplyApy: point.baseSupplyApy,
276
+ baseBorrowApy: point.baseBorrowApy,
277
+ collateralTokenPrice: price,
278
+ loanTokenPrice: price,
279
+ };
280
+ });
281
+ }
318
282
 
319
- const result: MarketSnapshot = {
320
- marketId: marketAddress.toLowerCase(),
321
- chainId: environment.chainId,
322
- timestamp: point.timestamp * 1000,
323
- totalSupply: supplied,
324
- totalSupplyUsd: suppliedUsd,
325
- totalBorrows: borrow,
326
- totalBorrowsUsd: borrowUsd,
327
- totalLiquidity: liquidity,
328
- totalLiquidityUsd: liquidityUsd,
329
- totalReallocatableLiquidity: 0,
330
- totalReallocatableLiquidityUsd: 0,
331
- baseSupplyApy: point.baseSupplyApy,
332
- baseBorrowApy: point.baseBorrowApy,
333
- collateralTokenPrice: price,
334
- loanTokenPrice: price,
335
- };
283
+ async function fetchCoreMarketSnapshotsFromLunar(
284
+ marketAddress: string,
285
+ environment: Environment,
286
+ period?: "1M" | "3M" | "1Y" | "ALL",
287
+ customStartTime?: number,
288
+ customEndTime?: number,
289
+ ): Promise<MarketSnapshot[]> {
290
+ if (!environment.lunarIndexerUrl) {
291
+ throw new Error("Lunar Indexer URL not configured");
292
+ }
336
293
 
337
- return result;
294
+ const client = createLunarIndexerClient({
295
+ baseUrl: environment.lunarIndexerUrl,
296
+ timeout: DEFAULT_LUNAR_TIMEOUT_MS,
297
+ });
298
+
299
+ const marketId = buildMarketId(environment.chainId, marketAddress);
300
+ const { startTime, endTime, granularity } = calculateTimeRange(
301
+ period,
302
+ customStartTime,
303
+ customEndTime,
304
+ );
305
+
306
+ const allSnapshots: MarketSnapshot[] = [];
307
+ let cursor: string | null = null;
308
+ const MAX_PAGES = 100;
309
+ let page = 0;
310
+
311
+ do {
312
+ const response = await client.getMarketSnapshots(marketId, {
313
+ limit: 1000,
314
+ ...(cursor && { cursor }),
315
+ granularity: toApiGranularity(granularity),
316
+ startTime,
317
+ endTime,
338
318
  });
339
- } else {
340
- return [];
341
- }
342
- }
343
319
 
344
- async function fetchMorphoGraphQL(
345
- environment: Environment,
346
- query: string,
347
- operationName?: string,
348
- variables?: any,
349
- ) {
350
- try {
351
- const url =
352
- environment.custom.morpho?.apiUrl || "https://api.morpho.org/graphql";
353
- const response = await axios.post(
354
- url,
355
- { query: query, operationName, variables },
356
- { timeout: 5000 },
320
+ const transformed = transformMarketSnapshots(
321
+ response.results,
322
+ environment.chainId,
357
323
  );
358
324
 
359
- if (response.status !== 200 || response.data.errors) {
360
- if (typeof window !== "undefined") {
361
- console.debug(
362
- `[Morpho GraphQL Market Snapshots] Non-200 (${response.statusText}) or errors:`,
363
- response.data.errors,
364
- );
365
- }
366
- return undefined;
367
- }
325
+ allSnapshots.push(...transformed);
368
326
 
369
- return response.data.data;
370
- } catch (error) {
371
- if (typeof window !== "undefined") {
372
- console.debug(
373
- "[Morpho GraphQL Market Snapshots] Error fetching data:",
374
- error,
375
- );
376
- }
377
- return undefined;
378
- }
327
+ cursor = response.nextCursor;
328
+ page++;
329
+ } while (cursor !== null && page < MAX_PAGES);
330
+
331
+ allSnapshots.sort((a, b) => a.timestamp - b.timestamp);
332
+ return applyGranularity(allSnapshots, granularity).map((snapshot) => {
333
+ const supplied = snapshot.totalSupply;
334
+ const suppliedUsd = snapshot.totalSupplyUsd;
335
+ const price = supplied > 0 ? suppliedUsd / supplied : 0;
336
+
337
+ return {
338
+ ...snapshot,
339
+ collateralTokenPrice: price,
340
+ loanTokenPrice: price,
341
+ };
342
+ });
379
343
  }
380
344
 
381
345
  // Fetches a timestamp-ms → WELL price map from the WELL market in the indexer.
@@ -432,386 +396,172 @@ export async function fetchIsolatedMarketSnapshots(
432
396
  ): Promise<MarketSnapshot[]> {
433
397
  const lunarIndexerUrl = environment.lunarIndexerUrl;
434
398
 
435
- if (lunarIndexerUrl) {
436
- try {
437
- const { startTime } = calculateTimeRange(
438
- period,
439
- customStartTime,
440
- customEndTime,
441
- );
442
-
443
- // The USDC/ETH market (collateral = USDC, loan = WETH) needs normalization:
444
- // the indexer returns totalSupplyAssets in WETH units but the chart needs
445
- // USDC-equivalent units (totalSupplyAssetsUsd / collateralTokenPrice).
446
- const marketConfig = Object.values(environment.config.morphoMarkets).find(
447
- (m) => m.id.toLowerCase() === marketAddress.toLowerCase(),
448
- );
449
- const loanSymbol = marketConfig
450
- ? environment.config.tokens[marketConfig.loanToken]?.symbol
451
- : undefined;
452
- const collateralSymbol = marketConfig
453
- ? environment.config.tokens[marketConfig.collateralToken]?.symbol
454
- : undefined;
455
- const normalizeToCollateral =
456
- loanSymbol === "ETH" && collateralSymbol === "USDC";
457
-
458
- // stkWELL is not priced by the indexer (same oracle as WELL but unrecognized).
459
- // Fetch the WELL market snapshots concurrently and use their prices to correct
460
- // any stkWELL snapshots where collateralTokenPrice is 0.
461
- const isStkWellMarket = collateralSymbol === "stkWELL";
462
- const wellMarketConfig = isStkWellMarket
463
- ? Object.values(environment.config.morphoMarkets).find(
464
- (m) => m.collateralToken === "WELL" || m.loanToken === "WELL",
465
- )
466
- : undefined;
467
-
468
- const wellPricesPromise = wellMarketConfig
469
- ? fetchWellPricesByTimestamp(
470
- lunarIndexerUrl,
471
- environment.chainId,
472
- wellMarketConfig,
473
- startTime,
474
- )
475
- : Promise.resolve(new Map<number, number>());
476
-
477
- const allSnapshots: MarketSnapshot[] = [];
478
- let cursor: string | undefined;
479
- const MAX_PAGES = 100;
480
- let page = 0;
481
-
482
- do {
483
- const response = await fetchMarketSnapshotsFromIndexer(
484
- lunarIndexerUrl,
485
- environment.chainId,
486
- marketAddress,
487
- {
488
- startTime,
489
- granularity: "1d",
490
- limit: 1000,
491
- ...(cursor && { cursor }),
492
- },
493
- );
494
-
495
- allSnapshots.push(
496
- ...response.results
497
- .filter((s) => isStartOfDay(s.timestamp))
498
- .map((s) =>
499
- transformIsolatedMarketSnapshotFromIndexer(s, {
500
- normalizeToCollateral,
501
- }),
502
- ),
503
- );
504
-
505
- cursor = response.nextCursor ?? undefined;
506
- page++;
507
- } while (cursor !== undefined && page < MAX_PAGES);
508
-
509
- // Sanity check for non-normalized markets: detect when totalSupplyAssets was
510
- // stored in collateral units instead of loan token units (an indexer bug seen on
511
- // the stkWELL/USDC market). When this happens, totalSupplyUsd / totalSupply is
512
- // close to collateralTokenPrice rather than loanTokenPrice. Recover the correct
513
- // loan-token amount from the USD value instead of discarding the data point.
514
- // We skip normalized markets (e.g. USDC/ETH where totalSupply is already in
515
- // collateral units after normalization) to avoid false corrections.
516
- const sanitizedSnapshots = normalizeToCollateral
517
- ? allSnapshots
518
- : allSnapshots.map((snapshot) => {
519
- if (snapshot.totalSupply === 0 || snapshot.loanTokenPrice === 0)
520
- return snapshot;
521
- const impliedLoanPrice =
522
- snapshot.totalSupplyUsd / snapshot.totalSupply;
523
- if (impliedLoanPrice < snapshot.loanTokenPrice * 0.1) {
524
- return {
525
- ...snapshot,
526
- totalSupply: snapshot.totalSupplyUsd / snapshot.loanTokenPrice,
527
- totalLiquidity:
528
- snapshot.totalLiquidityUsd / snapshot.loanTokenPrice,
529
- };
530
- }
531
- return snapshot;
532
- });
533
-
534
- if (isStkWellMarket) {
535
- const wellPriceByTimestampMs = await wellPricesPromise;
536
- return sanitizedSnapshots.map((snapshot) => {
537
- // Resolve the collateral (stkWELL) price: use indexed price when available,
538
- // fall back to the WELL market price fetched concurrently.
539
- let collateralTokenPrice = snapshot.collateralTokenPrice;
540
- if (collateralTokenPrice === 0) {
541
- const wellPrice = wellPriceByTimestampMs.get(snapshot.timestamp);
542
- if (wellPrice) collateralTokenPrice = wellPrice;
543
- }
544
-
545
- // The lunar indexer stores totalSupplyAssets in loan-token (USDC) units.
546
- // All fallback paths (BlueAPI, subgraph) convert to collateral (stkWELL) units:
547
- // totalSupply = totalSupplyUsd / collateralTokenPrice
548
- // Apply the same conversion here so the chart axis is consistent.
549
- const totalSupply =
550
- collateralTokenPrice > 0
551
- ? snapshot.totalSupplyUsd / collateralTokenPrice
552
- : snapshot.totalSupply;
553
- const totalLiquidity =
554
- collateralTokenPrice > 0
555
- ? snapshot.totalLiquidityUsd / collateralTokenPrice
556
- : snapshot.totalLiquidity;
557
- const totalReallocatableLiquidity =
558
- collateralTokenPrice > 0
559
- ? snapshot.totalReallocatableLiquidityUsd / collateralTokenPrice
560
- : snapshot.totalReallocatableLiquidity;
561
-
562
- return {
563
- ...snapshot,
564
- collateralTokenPrice,
565
- totalSupply,
566
- totalLiquidity,
567
- totalReallocatableLiquidity,
568
- };
569
- });
570
- }
571
-
572
- return sanitizedSnapshots;
573
- } catch (error) {
574
- if (!shouldFallback(error)) {
575
- throw error;
576
- }
577
- console.debug(
578
- "[Lunar fallback] Falling back for isolated market snapshots:",
579
- error,
580
- );
581
- }
399
+ if (!lunarIndexerUrl) {
400
+ return [];
582
401
  }
583
402
 
584
- return fetchIsolatedMarketSnapshotsFromBlueApi(
585
- marketAddress,
586
- environment,
587
- period,
588
- customStartTime,
589
- customEndTime,
590
- );
403
+ try {
404
+ return await fetchIsolatedMarketSnapshotsFromLunar(
405
+ marketAddress,
406
+ environment,
407
+ lunarIndexerUrl,
408
+ period,
409
+ customStartTime,
410
+ customEndTime,
411
+ );
412
+ } catch (error) {
413
+ console.warn(
414
+ `[getMarketSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`,
415
+ error,
416
+ );
417
+ environment.onError?.(error, {
418
+ source: "market-snapshots",
419
+ chainId: environment.chainId,
420
+ });
421
+ return [];
422
+ }
591
423
  }
592
424
 
593
- async function fetchIsolatedMarketSnapshotsFromBlueApi(
425
+ async function fetchIsolatedMarketSnapshotsFromLunar(
594
426
  marketAddress: string,
595
427
  environment: Environment,
428
+ lunarIndexerUrl: string,
596
429
  period?: "1M" | "3M" | "1Y" | "ALL",
597
430
  customStartTime?: number,
598
431
  customEndTime?: number,
599
432
  ): Promise<MarketSnapshot[]> {
433
+ const { startTime } = calculateTimeRange(
434
+ period,
435
+ customStartTime,
436
+ customEndTime,
437
+ );
438
+
439
+ // The USDC/ETH market (collateral = USDC, loan = WETH) needs normalization:
440
+ // the indexer returns totalSupplyAssets in WETH units but the chart needs
441
+ // USDC-equivalent units (totalSupplyAssetsUsd / collateralTokenPrice).
600
442
  const marketConfig = Object.values(environment.config.morphoMarkets).find(
601
- (market) => market.id.toLowerCase() === marketAddress.toLowerCase(),
443
+ (m) => m.id.toLowerCase() === marketAddress.toLowerCase(),
602
444
  );
445
+ const loanSymbol = marketConfig
446
+ ? environment.config.tokens[marketConfig.loanToken]?.symbol
447
+ : undefined;
448
+ const collateralSymbol = marketConfig
449
+ ? environment.config.tokens[marketConfig.collateralToken]?.symbol
450
+ : undefined;
451
+ const normalizeToCollateral =
452
+ loanSymbol === "ETH" && collateralSymbol === "USDC";
453
+
454
+ // stkWELL is not priced by the indexer (same oracle as WELL but unrecognized).
455
+ // Fetch the WELL market snapshots concurrently and use their prices to correct
456
+ // any stkWELL snapshots where collateralTokenPrice is 0.
457
+ const isStkWellMarket = collateralSymbol === "stkWELL";
458
+ const wellMarketConfig = isStkWellMarket
459
+ ? Object.values(environment.config.morphoMarkets).find(
460
+ (m) => m.collateralToken === "WELL" || m.loanToken === "WELL",
461
+ )
462
+ : undefined;
603
463
 
604
- const isStkWellMarket =
605
- marketConfig &&
606
- environment.config.tokens[marketConfig.collateralToken]?.symbol ===
607
- "stkWELL";
464
+ const wellPricesPromise = wellMarketConfig
465
+ ? fetchWellPricesByTimestamp(
466
+ lunarIndexerUrl,
467
+ environment.chainId,
468
+ wellMarketConfig,
469
+ startTime,
470
+ )
471
+ : Promise.resolve(new Map<number, number>());
608
472
 
609
- // If stkWELL market, fetch WELL price from subgraph. stkWELL and WELL have the same price
610
- let wellPrice: number | null = null;
611
- if (isStkWellMarket) {
612
- const wellMarketConfig = Object.values(
613
- environment.config.morphoMarkets,
614
- ).find(
615
- (market) =>
616
- market.collateralToken === "WELL" || market.loanToken === "WELL",
617
- );
473
+ const allSnapshots: MarketSnapshot[] = [];
474
+ let cursor: string | undefined;
475
+ const MAX_PAGES = 100;
476
+ let page = 0;
618
477
 
619
- if (wellMarketConfig) {
620
- const wellQuery = `
621
- query getWellPrice {
622
- wellMarket: marketByUniqueKey(
623
- chainId: ${environment.chainId}
624
- uniqueKey: "${wellMarketConfig.id.toLowerCase()}"
625
- ) {
626
- loanAsset {
627
- priceUsd
628
- }
629
- collateralAsset {
630
- priceUsd
631
- }
632
- }
633
- }`;
478
+ do {
479
+ const response = await fetchMarketSnapshotsFromIndexer(
480
+ lunarIndexerUrl,
481
+ environment.chainId,
482
+ marketAddress,
483
+ {
484
+ startTime,
485
+ granularity: "1d",
486
+ limit: 1000,
487
+ ...(cursor && { cursor }),
488
+ },
489
+ );
634
490
 
635
- const wellResult = await fetchMorphoGraphQL(
636
- environment,
637
- wellQuery,
638
- "getWellPrice",
639
- );
640
- if (wellResult?.wellMarket) {
641
- wellPrice =
642
- wellMarketConfig.collateralToken === "WELL"
643
- ? wellResult.wellMarket.collateralAsset.priceUsd
644
- : wellResult.wellMarket.loanAsset.priceUsd;
645
- }
646
- }
647
- }
491
+ allSnapshots.push(
492
+ ...response.results
493
+ .filter((s) => isStartOfDay(s.timestamp))
494
+ .map((s) =>
495
+ transformIsolatedMarketSnapshotFromIndexer(s, {
496
+ normalizeToCollateral,
497
+ }),
498
+ ),
499
+ );
648
500
 
649
- const operationName = "getMarketTotalTimeseries";
501
+ cursor = response.nextCursor ?? undefined;
502
+ page++;
503
+ } while (cursor !== undefined && page < MAX_PAGES);
650
504
 
651
- const { startTime } = calculateTimeRange(
652
- period,
653
- customStartTime,
654
- customEndTime,
655
- );
656
- const variables = {
657
- options: {
658
- startTimestamp: startTime,
659
- interval: "DAY",
660
- },
661
- };
662
-
663
- const query = `
664
- query getMarketTotalTimeseries($options: TimeseriesOptions) {
665
- marketTotalTimeseries: marketByUniqueKey(
666
- chainId: ${environment.chainId}
667
- uniqueKey: "${marketAddress.toLowerCase()}"
668
- ) {
669
- uniqueKey
670
- loanAsset {
671
- priceUsd
672
- decimals
673
- }
674
- collateralAsset {
675
- priceUsd
676
- decimals
677
- }
678
- historicalState {
679
- supplyApy(options: $options) {
680
- x
681
- y
682
- }
683
- borrowApy(options: $options) {
684
- x
685
- y
686
- }
687
- borrowAssets(options: $options) {
688
- x
689
- y
690
- }
691
- borrowAssetsUsd(options: $options) {
692
- x
693
- y
694
- }
695
- supplyAssets(options: $options) {
696
- x
697
- y
698
- }
699
- supplyAssetsUsd(options: $options) {
700
- x
701
- y
702
- }
703
- liquidityAssets(options: $options) {
704
- x
705
- y
706
- }
707
- liquidityAssetsUsd(options: $options) {
708
- x
709
- y
710
- }
505
+ // Sanity check for non-normalized markets: detect when totalSupplyAssets was
506
+ // stored in collateral units instead of loan token units (an indexer bug seen on
507
+ // the stkWELL/USDC market). When this happens, totalSupplyUsd / totalSupply is
508
+ // close to collateralTokenPrice rather than loanTokenPrice. Recover the correct
509
+ // loan-token amount from the USD value instead of discarding the data point.
510
+ // We skip normalized markets (e.g. USDC/ETH where totalSupply is already in
511
+ // collateral units after normalization) to avoid false corrections.
512
+ const sanitizedSnapshots = normalizeToCollateral
513
+ ? allSnapshots
514
+ : allSnapshots.map((snapshot) => {
515
+ if (snapshot.totalSupply === 0 || snapshot.loanTokenPrice === 0)
516
+ return snapshot;
517
+ const impliedLoanPrice = snapshot.totalSupplyUsd / snapshot.totalSupply;
518
+ if (impliedLoanPrice < snapshot.loanTokenPrice * 0.1) {
519
+ return {
520
+ ...snapshot,
521
+ totalSupply: snapshot.totalSupplyUsd / snapshot.loanTokenPrice,
522
+ totalLiquidity:
523
+ snapshot.totalLiquidityUsd / snapshot.loanTokenPrice,
524
+ };
711
525
  }
712
- }
713
- }`;
714
-
715
- const result = await fetchMorphoGraphQL(
716
- environment,
717
- query,
718
- operationName,
719
- variables,
720
- );
526
+ return snapshot;
527
+ });
721
528
 
722
- if (result) {
723
- try {
724
- const markets: MarketSnapshot[] =
725
- result.marketTotalTimeseries.historicalState.borrowAssets.map(
726
- (
727
- borrowAssets: {
728
- x: number;
729
- y: number;
730
- },
731
- index: number,
732
- ): MarketSnapshot => {
733
- const loanDecimals =
734
- result.marketTotalTimeseries.loanAsset.decimals;
735
-
736
- const borrowAssetsUsd =
737
- result.marketTotalTimeseries.historicalState.borrowAssetsUsd[
738
- index
739
- ];
740
-
741
- const supplyAssets =
742
- result.marketTotalTimeseries.historicalState.supplyAssets[index];
743
-
744
- const supplyAssetsUsd =
745
- result.marketTotalTimeseries.historicalState.supplyAssetsUsd[
746
- index
747
- ];
748
-
749
- const liquidityAssets =
750
- result.marketTotalTimeseries.historicalState.liquidityAssets[
751
- index
752
- ];
753
-
754
- const liquidityAssetsUsd =
755
- result.marketTotalTimeseries.historicalState.liquidityAssetsUsd[
756
- index
757
- ];
758
-
759
- const supplyApy =
760
- result.marketTotalTimeseries.historicalState.supplyApy[index];
761
-
762
- const borrowApy =
763
- result.marketTotalTimeseries.historicalState.borrowApy[index];
764
-
765
- let collateralTokenPrice =
766
- result.marketTotalTimeseries.collateralAsset.priceUsd;
767
- const loanTokenPrice =
768
- result.marketTotalTimeseries.loanAsset.priceUsd;
769
-
770
- if (isStkWellMarket && wellPrice !== null) {
771
- collateralTokenPrice = wellPrice;
772
- }
529
+ if (isStkWellMarket) {
530
+ const wellPriceByTimestampMs = await wellPricesPromise;
531
+ return sanitizedSnapshots.map((snapshot) => {
532
+ // Resolve the collateral (stkWELL) price: use indexed price when available,
533
+ // fall back to the WELL market price fetched concurrently.
534
+ let collateralTokenPrice = snapshot.collateralTokenPrice;
535
+ if (collateralTokenPrice === 0) {
536
+ const wellPrice = wellPriceByTimestampMs.get(snapshot.timestamp);
537
+ if (wellPrice) collateralTokenPrice = wellPrice;
538
+ }
773
539
 
774
- const totalSupply =
775
- (new Amount(BigInt(supplyAssets.y), Number(loanDecimals)).value *
776
- loanTokenPrice) /
777
- collateralTokenPrice;
778
-
779
- const totalBorrows = new Amount(
780
- BigInt(borrowAssets.y),
781
- Number(loanDecimals),
782
- ).value;
783
-
784
- const totalLiquidity =
785
- (new Amount(BigInt(liquidityAssets.y), Number(loanDecimals))
786
- .value *
787
- loanTokenPrice) /
788
- collateralTokenPrice;
789
-
790
- return {
791
- chainId: environment.chainId,
792
- timestamp: borrowAssets.x * 1000,
793
- marketId: marketAddress.toLowerCase(),
794
- totalBorrows,
795
- totalBorrowsUsd: Number(borrowAssetsUsd.y),
796
- totalSupply,
797
- totalSupplyUsd: Number(supplyAssetsUsd.y),
798
- totalLiquidity,
799
- totalLiquidityUsd: Number(liquidityAssetsUsd.y),
800
- totalReallocatableLiquidity: 0,
801
- totalReallocatableLiquidityUsd: 0,
802
- baseSupplyApy: supplyApy.y,
803
- baseBorrowApy: borrowApy.y,
804
- loanTokenPrice,
805
- collateralTokenPrice,
806
- };
807
- },
808
- );
809
-
810
- return markets;
811
- } catch (ex) {
812
- return [];
813
- }
814
- } else {
815
- return [];
540
+ // The lunar indexer stores totalSupplyAssets in loan-token (USDC) units.
541
+ // Convert to collateral (stkWELL) units for a consistent chart axis:
542
+ // totalSupply = totalSupplyUsd / collateralTokenPrice
543
+ const totalSupply =
544
+ collateralTokenPrice > 0
545
+ ? snapshot.totalSupplyUsd / collateralTokenPrice
546
+ : snapshot.totalSupply;
547
+ const totalLiquidity =
548
+ collateralTokenPrice > 0
549
+ ? snapshot.totalLiquidityUsd / collateralTokenPrice
550
+ : snapshot.totalLiquidity;
551
+ const totalReallocatableLiquidity =
552
+ collateralTokenPrice > 0
553
+ ? snapshot.totalReallocatableLiquidityUsd / collateralTokenPrice
554
+ : snapshot.totalReallocatableLiquidity;
555
+
556
+ return {
557
+ ...snapshot,
558
+ collateralTokenPrice,
559
+ totalSupply,
560
+ totalLiquidity,
561
+ totalReallocatableLiquidity,
562
+ };
563
+ });
816
564
  }
565
+
566
+ return sanitizedSnapshots;
817
567
  }