@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
|
@@ -19,6 +19,24 @@ import {
|
|
|
19
19
|
import type { Market } from "../../../types/market.js";
|
|
20
20
|
|
|
21
21
|
export const getMarketsData = async (environment: Environment) => {
|
|
22
|
+
// Moonriver (chainId 1285) should always use on-chain data
|
|
23
|
+
const isMoonriver = environment.chainId === 1285;
|
|
24
|
+
|
|
25
|
+
if (environment.lunarIndexerUrl && !isMoonriver) {
|
|
26
|
+
try {
|
|
27
|
+
const result = await fetchMarketsFromLunar(environment);
|
|
28
|
+
return result;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// Import shouldFallback dynamically
|
|
31
|
+
const { shouldFallback } = await import("../../lunar-indexer-client.js");
|
|
32
|
+
|
|
33
|
+
if (!shouldFallback(error)) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
console.debug("[Lunar fallback] Falling back to RPC/Ponder:", error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
const homeEnvironment =
|
|
23
41
|
(Object.values(publicEnvironments) as Environment[]).find((e) =>
|
|
24
42
|
e.custom?.governance?.chainIds?.includes(environment.chainId),
|
|
@@ -27,17 +45,50 @@ export const getMarketsData = async (environment: Environment) => {
|
|
|
27
45
|
const viewsContract = environment.contracts.views;
|
|
28
46
|
const homeViewsContract = homeEnvironment.contracts.views;
|
|
29
47
|
|
|
30
|
-
const
|
|
48
|
+
const [
|
|
49
|
+
protocolInfoResult,
|
|
50
|
+
allMarketsInfoResult,
|
|
51
|
+
nativePriceResult,
|
|
52
|
+
govPriceResult,
|
|
53
|
+
] = await Promise.allSettled([
|
|
31
54
|
viewsContract?.read.getProtocolInfo(),
|
|
32
55
|
viewsContract?.read.getAllMarketsInfo(),
|
|
33
56
|
homeViewsContract?.read.getNativeTokenPrice(),
|
|
34
57
|
homeViewsContract?.read.getGovernanceTokenPrice(),
|
|
35
58
|
]);
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
// If getAllMarketsInfo failed (e.g. broken on-chain oracle), fall back to
|
|
61
|
+
// per-mToken RPC calls. This handles deprecated chains like Moonriver where
|
|
62
|
+
// the price oracle is non-functional but individual mToken data is readable.
|
|
63
|
+
if (allMarketsInfoResult.status === "rejected") {
|
|
64
|
+
console.debug(
|
|
65
|
+
"[mToken fallback] getAllMarketsInfo failed, using per-mToken fallback:",
|
|
66
|
+
allMarketsInfoResult.reason,
|
|
67
|
+
);
|
|
68
|
+
const seizePaused =
|
|
69
|
+
protocolInfoResult.status === "fulfilled"
|
|
70
|
+
? protocolInfoResult.value!.seizePaused
|
|
71
|
+
: false;
|
|
72
|
+
const transferPaused =
|
|
73
|
+
protocolInfoResult.status === "fulfilled"
|
|
74
|
+
? protocolInfoResult.value!.transferPaused
|
|
75
|
+
: false;
|
|
76
|
+
return await getMarketsFromMTokenFallback(
|
|
77
|
+
environment,
|
|
78
|
+
seizePaused,
|
|
79
|
+
transferPaused,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { seizePaused, transferPaused } =
|
|
84
|
+
protocolInfoResult.status === "fulfilled"
|
|
85
|
+
? protocolInfoResult.value!
|
|
86
|
+
: { seizePaused: false, transferPaused: false };
|
|
87
|
+
const allMarketsInfo = allMarketsInfoResult.value!;
|
|
88
|
+
const nativeTokenPriceRaw =
|
|
89
|
+
nativePriceResult.status === "fulfilled" ? nativePriceResult.value! : 0n;
|
|
90
|
+
const governanceTokenPriceRaw =
|
|
91
|
+
govPriceResult.status === "fulfilled" ? govPriceResult.value! : 0n;
|
|
41
92
|
|
|
42
93
|
const governanceTokenPrice = new Amount(governanceTokenPriceRaw, 18);
|
|
43
94
|
const nativeTokenPrice = new Amount(nativeTokenPriceRaw, 18);
|
|
@@ -185,6 +236,8 @@ export const getMarketsData = async (environment: Environment) => {
|
|
|
185
236
|
: tokenPrice;
|
|
186
237
|
|
|
187
238
|
if (price) {
|
|
239
|
+
// USDC on-chain returns borrowIncentivesPerSec=1 (1 wei) as a
|
|
240
|
+
// placeholder when there are no active borrow incentives. Treat as zero.
|
|
188
241
|
if (token.symbol === "USDC" && borrowIncentivesPerSec === 1n) {
|
|
189
242
|
borrowIncentivesPerSec = 0n;
|
|
190
243
|
}
|
|
@@ -202,6 +255,7 @@ export const getMarketsData = async (environment: Environment) => {
|
|
|
202
255
|
: (supplyRewardsPerDayUsd / totalSupplyUsd) *
|
|
203
256
|
DAYS_PER_YEAR *
|
|
204
257
|
100;
|
|
258
|
+
// Negative: borrow reward APR reduces the effective borrowing cost
|
|
205
259
|
const borrowApr =
|
|
206
260
|
totalBorrowsUsd === 0
|
|
207
261
|
? 0
|
|
@@ -236,6 +290,447 @@ export const getMarketsData = async (environment: Environment) => {
|
|
|
236
290
|
return markets;
|
|
237
291
|
};
|
|
238
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Fallback for chains whose on-chain price oracle is non-functional (e.g.
|
|
295
|
+
* deprecated Moonriver). Reads raw mToken contract data individually via
|
|
296
|
+
* Promise.allSettled so a single failed call does not abort the entire chain.
|
|
297
|
+
* All USD/price values are set to 0 since oracle prices are unavailable.
|
|
298
|
+
*/
|
|
299
|
+
async function getMarketsFromMTokenFallback(
|
|
300
|
+
environment: Environment,
|
|
301
|
+
seizePaused: boolean,
|
|
302
|
+
transferPaused: boolean,
|
|
303
|
+
): Promise<Market[]> {
|
|
304
|
+
const markets: Market[] = [];
|
|
305
|
+
|
|
306
|
+
for (const marketKey of Object.keys(environment.config.markets)) {
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
308
|
+
const envAny = environment as any;
|
|
309
|
+
const marketConfig = envAny.config.markets[marketKey] as
|
|
310
|
+
| { underlyingToken: string; marketToken: string; deprecated?: boolean }
|
|
311
|
+
| undefined;
|
|
312
|
+
if (!marketConfig) continue;
|
|
313
|
+
|
|
314
|
+
const underlyingToken = envAny.config.tokens[
|
|
315
|
+
marketConfig.underlyingToken
|
|
316
|
+
] as
|
|
317
|
+
| {
|
|
318
|
+
address: `0x${string}`;
|
|
319
|
+
decimals: number;
|
|
320
|
+
symbol: string;
|
|
321
|
+
name: string;
|
|
322
|
+
}
|
|
323
|
+
| undefined;
|
|
324
|
+
const marketToken = envAny.config.tokens[marketConfig.marketToken] as
|
|
325
|
+
| {
|
|
326
|
+
address: `0x${string}`;
|
|
327
|
+
decimals: number;
|
|
328
|
+
symbol: string;
|
|
329
|
+
name: string;
|
|
330
|
+
}
|
|
331
|
+
| undefined;
|
|
332
|
+
if (!underlyingToken || !marketToken) continue;
|
|
333
|
+
|
|
334
|
+
const mTokenContract = envAny.markets[marketKey] as
|
|
335
|
+
| { read: Record<string, (...args: unknown[]) => Promise<bigint>> }
|
|
336
|
+
| undefined;
|
|
337
|
+
if (!mTokenContract) continue;
|
|
338
|
+
|
|
339
|
+
const [
|
|
340
|
+
totalSupplyResult,
|
|
341
|
+
totalBorrowsResult,
|
|
342
|
+
totalReservesResult,
|
|
343
|
+
cashResult,
|
|
344
|
+
exchangeRateResult,
|
|
345
|
+
supplyRateResult,
|
|
346
|
+
borrowRateResult,
|
|
347
|
+
reserveFactorResult,
|
|
348
|
+
] = await Promise.allSettled([
|
|
349
|
+
mTokenContract.read.totalSupply(),
|
|
350
|
+
mTokenContract.read.totalBorrows(),
|
|
351
|
+
mTokenContract.read.totalReserves(),
|
|
352
|
+
mTokenContract.read.getCash(),
|
|
353
|
+
mTokenContract.read.exchangeRateStored(),
|
|
354
|
+
mTokenContract.read.supplyRatePerTimestamp(),
|
|
355
|
+
mTokenContract.read.borrowRatePerTimestamp(),
|
|
356
|
+
mTokenContract.read.reserveFactorMantissa(),
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
const totalSupplyRaw =
|
|
360
|
+
totalSupplyResult.status === "fulfilled" ? totalSupplyResult.value : 0n;
|
|
361
|
+
const totalBorrowsRaw =
|
|
362
|
+
totalBorrowsResult.status === "fulfilled" ? totalBorrowsResult.value : 0n;
|
|
363
|
+
const totalReservesRaw =
|
|
364
|
+
totalReservesResult.status === "fulfilled"
|
|
365
|
+
? totalReservesResult.value
|
|
366
|
+
: 0n;
|
|
367
|
+
const cashRaw = cashResult.status === "fulfilled" ? cashResult.value : 0n;
|
|
368
|
+
// Default exchange rate of 1.0: 10^(10 + underlyingDecimals) in raw form
|
|
369
|
+
const exchangeRateRaw =
|
|
370
|
+
exchangeRateResult.status === "fulfilled"
|
|
371
|
+
? exchangeRateResult.value
|
|
372
|
+
: 10n ** BigInt(10 + underlyingToken.decimals);
|
|
373
|
+
const supplyRateRaw =
|
|
374
|
+
supplyRateResult.status === "fulfilled" ? supplyRateResult.value : 0n;
|
|
375
|
+
const borrowRateRaw =
|
|
376
|
+
borrowRateResult.status === "fulfilled" ? borrowRateResult.value : 0n;
|
|
377
|
+
const reserveFactorRaw =
|
|
378
|
+
reserveFactorResult.status === "fulfilled"
|
|
379
|
+
? reserveFactorResult.value
|
|
380
|
+
: 0n;
|
|
381
|
+
|
|
382
|
+
const exchangeRate = new Amount(
|
|
383
|
+
exchangeRateRaw,
|
|
384
|
+
10 + underlyingToken.decimals,
|
|
385
|
+
).value;
|
|
386
|
+
const marketTotalSupply = new Amount(totalSupplyRaw, marketToken.decimals);
|
|
387
|
+
const totalSupply = new Amount(
|
|
388
|
+
marketTotalSupply.value * exchangeRate,
|
|
389
|
+
underlyingToken.decimals,
|
|
390
|
+
);
|
|
391
|
+
const totalBorrows = new Amount(totalBorrowsRaw, underlyingToken.decimals);
|
|
392
|
+
const totalReserves = new Amount(
|
|
393
|
+
totalReservesRaw,
|
|
394
|
+
underlyingToken.decimals,
|
|
395
|
+
);
|
|
396
|
+
const cash = new Amount(cashRaw, underlyingToken.decimals);
|
|
397
|
+
const supplyRate = new Amount(supplyRateRaw, 18);
|
|
398
|
+
const borrowRate = new Amount(borrowRateRaw, 18);
|
|
399
|
+
const reserveFactor = new Amount(reserveFactorRaw, 18).value;
|
|
400
|
+
|
|
401
|
+
const baseSupplyApy = calculateApy(supplyRate.value);
|
|
402
|
+
const baseBorrowApy = calculateApy(borrowRate.value);
|
|
403
|
+
|
|
404
|
+
const market: Market = {
|
|
405
|
+
marketKey,
|
|
406
|
+
chainId: environment.chainId,
|
|
407
|
+
seizePaused,
|
|
408
|
+
transferPaused,
|
|
409
|
+
// Oracle is non-functional so supply/borrow would fail on-chain; mark paused
|
|
410
|
+
mintPaused: true,
|
|
411
|
+
borrowPaused: true,
|
|
412
|
+
deprecated: marketConfig.deprecated === true,
|
|
413
|
+
borrowCaps: new Amount(0n, underlyingToken.decimals),
|
|
414
|
+
borrowCapsUsd: 0,
|
|
415
|
+
cash,
|
|
416
|
+
collateralFactor: 0,
|
|
417
|
+
exchangeRate,
|
|
418
|
+
marketToken,
|
|
419
|
+
reserveFactor,
|
|
420
|
+
supplyCaps: new Amount(0n, underlyingToken.decimals),
|
|
421
|
+
supplyCapsUsd: 0,
|
|
422
|
+
badDebt: new Amount(0n, underlyingToken.decimals),
|
|
423
|
+
badDebtUsd: 0,
|
|
424
|
+
totalBorrows,
|
|
425
|
+
totalBorrowsUsd: 0,
|
|
426
|
+
totalReserves,
|
|
427
|
+
totalReservesUsd: 0,
|
|
428
|
+
totalSupply,
|
|
429
|
+
totalSupplyUsd: 0,
|
|
430
|
+
underlyingPrice: 0,
|
|
431
|
+
underlyingToken,
|
|
432
|
+
baseBorrowApy,
|
|
433
|
+
baseSupplyApy,
|
|
434
|
+
totalBorrowApr: baseBorrowApy,
|
|
435
|
+
totalSupplyApr: baseSupplyApy,
|
|
436
|
+
rewards: [],
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
markets.push(market);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return markets;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Fetch markets data from Lunar Indexer (hybrid approach)
|
|
447
|
+
*
|
|
448
|
+
* Uses Lunar for core market data and conditionally:
|
|
449
|
+
* - If Lunar provides priceUsd/supplyApr/borrowApr in incentives: use those (NO RPC calls)
|
|
450
|
+
* - If Lunar fields are null: fetch governance/native token prices for reward APR calculations (RPC calls)
|
|
451
|
+
* - Always fetch liquid staking APRs from external APIs
|
|
452
|
+
*/
|
|
453
|
+
async function fetchMarketsFromLunar(
|
|
454
|
+
environment: Environment,
|
|
455
|
+
): Promise<Market[]> {
|
|
456
|
+
if (!environment.lunarIndexerUrl) {
|
|
457
|
+
throw new Error("Lunar Indexer URL not configured");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Import client dynamically to avoid circular dependencies
|
|
461
|
+
const { createLunarIndexerClient, DEFAULT_LUNAR_TIMEOUT_MS } = await import(
|
|
462
|
+
"../../lunar-indexer-client.js"
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const client = createLunarIndexerClient({
|
|
466
|
+
baseUrl: environment.lunarIndexerUrl,
|
|
467
|
+
timeout: DEFAULT_LUNAR_TIMEOUT_MS,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const lunarMarketsResponse = await client.listMarkets(environment.chainId);
|
|
471
|
+
const lunarMarkets = lunarMarketsResponse.results;
|
|
472
|
+
|
|
473
|
+
const needsRpcPrices = lunarMarkets.some((market) =>
|
|
474
|
+
market.incentives.some(
|
|
475
|
+
(incentive) =>
|
|
476
|
+
incentive.priceUsd === null ||
|
|
477
|
+
incentive.supplyApr === null ||
|
|
478
|
+
incentive.borrowApr === null,
|
|
479
|
+
),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
let governanceTokenPrice: Amount | undefined;
|
|
483
|
+
let nativeTokenPrice: Amount | undefined;
|
|
484
|
+
|
|
485
|
+
if (needsRpcPrices) {
|
|
486
|
+
const homeEnvironment =
|
|
487
|
+
(Object.values(publicEnvironments) as Environment[]).find((e) =>
|
|
488
|
+
e.custom?.governance?.chainIds?.includes(environment.chainId),
|
|
489
|
+
) || environment;
|
|
490
|
+
|
|
491
|
+
const [nativeTokenPriceRaw, governanceTokenPriceRaw] = await Promise.all([
|
|
492
|
+
homeEnvironment.contracts.views?.read.getNativeTokenPrice(),
|
|
493
|
+
homeEnvironment.contracts.views?.read.getGovernanceTokenPrice(),
|
|
494
|
+
]);
|
|
495
|
+
|
|
496
|
+
if (!nativeTokenPriceRaw || !governanceTokenPriceRaw) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
"Failed to fetch native or governance token prices from home chain",
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
governanceTokenPrice = new Amount(governanceTokenPriceRaw, 18);
|
|
503
|
+
nativeTokenPrice = new Amount(nativeTokenPriceRaw, 18);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const markets: Market[] = [];
|
|
507
|
+
|
|
508
|
+
for (const lunarMarket of lunarMarkets) {
|
|
509
|
+
const marketFound = findMarketByAddress(
|
|
510
|
+
environment,
|
|
511
|
+
lunarMarket.address as `0x${string}`,
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
if (!marketFound) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const { marketConfig, marketToken, underlyingToken, marketKey } =
|
|
519
|
+
marketFound;
|
|
520
|
+
|
|
521
|
+
// Transform Lunar decimal numbers to SDK Amount types
|
|
522
|
+
// Note: Number() wrapping is defensive — the Lunar API may return numeric
|
|
523
|
+
// fields as strings, which would break BigInt conversion via Math.floor.
|
|
524
|
+
const totalSupply = new Amount(
|
|
525
|
+
BigInt(
|
|
526
|
+
Math.floor(
|
|
527
|
+
Number(lunarMarket.totalSupply) * 10 ** underlyingToken.decimals,
|
|
528
|
+
),
|
|
529
|
+
),
|
|
530
|
+
underlyingToken.decimals,
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const totalBorrows = new Amount(
|
|
534
|
+
BigInt(
|
|
535
|
+
Math.floor(
|
|
536
|
+
Number(lunarMarket.totalBorrows) * 10 ** underlyingToken.decimals,
|
|
537
|
+
),
|
|
538
|
+
),
|
|
539
|
+
underlyingToken.decimals,
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const totalReserves = new Amount(
|
|
543
|
+
BigInt(
|
|
544
|
+
Math.floor(
|
|
545
|
+
Number(lunarMarket.totalReserves) * 10 ** underlyingToken.decimals,
|
|
546
|
+
),
|
|
547
|
+
),
|
|
548
|
+
underlyingToken.decimals,
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const cash = new Amount(
|
|
552
|
+
BigInt(
|
|
553
|
+
Math.floor(Number(lunarMarket.cash) * 10 ** underlyingToken.decimals),
|
|
554
|
+
),
|
|
555
|
+
underlyingToken.decimals,
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
const badDebt = new Amount(
|
|
559
|
+
BigInt(
|
|
560
|
+
Math.floor(
|
|
561
|
+
Number(lunarMarket.badDebt) * 10 ** underlyingToken.decimals,
|
|
562
|
+
),
|
|
563
|
+
),
|
|
564
|
+
underlyingToken.decimals,
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const supplyCaps = new Amount(
|
|
568
|
+
BigInt(
|
|
569
|
+
Math.floor(
|
|
570
|
+
Number(lunarMarket.supplyCap) * 10 ** underlyingToken.decimals,
|
|
571
|
+
),
|
|
572
|
+
),
|
|
573
|
+
underlyingToken.decimals,
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const borrowCaps = new Amount(
|
|
577
|
+
BigInt(
|
|
578
|
+
Math.floor(
|
|
579
|
+
Number(lunarMarket.borrowCap) * 10 ** underlyingToken.decimals,
|
|
580
|
+
),
|
|
581
|
+
),
|
|
582
|
+
underlyingToken.decimals,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// Lunar provides reserveFactor as wei string, convert to decimal
|
|
586
|
+
const reserveFactor = new Amount(BigInt(lunarMarket.reserveFactor), 18)
|
|
587
|
+
.value;
|
|
588
|
+
|
|
589
|
+
const market: Market = {
|
|
590
|
+
marketKey,
|
|
591
|
+
chainId: environment.chainId,
|
|
592
|
+
seizePaused: lunarMarket.seizePaused,
|
|
593
|
+
transferPaused: lunarMarket.transferPaused,
|
|
594
|
+
mintPaused: lunarMarket.mintPaused,
|
|
595
|
+
borrowPaused: lunarMarket.borrowPaused,
|
|
596
|
+
deprecated: marketConfig.deprecated === true,
|
|
597
|
+
borrowCaps,
|
|
598
|
+
borrowCapsUsd:
|
|
599
|
+
Number(lunarMarket.borrowCap) * Number(lunarMarket.priceUsd),
|
|
600
|
+
cash,
|
|
601
|
+
collateralFactor: Number(lunarMarket.collateralFactor),
|
|
602
|
+
exchangeRate: Number(lunarMarket.exchangeRate),
|
|
603
|
+
marketToken,
|
|
604
|
+
reserveFactor,
|
|
605
|
+
supplyCaps,
|
|
606
|
+
supplyCapsUsd:
|
|
607
|
+
Number(lunarMarket.supplyCap) * Number(lunarMarket.priceUsd),
|
|
608
|
+
badDebt,
|
|
609
|
+
badDebtUsd: Number(lunarMarket.badDebtUsd),
|
|
610
|
+
totalBorrows,
|
|
611
|
+
totalBorrowsUsd: Number(lunarMarket.totalBorrowsUsd),
|
|
612
|
+
totalReserves,
|
|
613
|
+
totalReservesUsd: Number(lunarMarket.totalReservesUsd),
|
|
614
|
+
totalSupply,
|
|
615
|
+
totalSupplyUsd: Number(lunarMarket.totalSupplyUsd),
|
|
616
|
+
underlyingPrice: Number(lunarMarket.priceUsd),
|
|
617
|
+
underlyingToken,
|
|
618
|
+
baseBorrowApy: Number(lunarMarket.baseBorrowApy),
|
|
619
|
+
baseSupplyApy: Number(lunarMarket.baseSupplyApy),
|
|
620
|
+
totalBorrowApr: 0,
|
|
621
|
+
totalSupplyApr: 0,
|
|
622
|
+
rewards: [],
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
for (const incentive of lunarMarket.incentives) {
|
|
626
|
+
const token = findTokenByAddress(
|
|
627
|
+
environment,
|
|
628
|
+
incentive.token as `0x${string}`,
|
|
629
|
+
);
|
|
630
|
+
if (!token) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let supplyApr: number;
|
|
635
|
+
let borrowApr: number;
|
|
636
|
+
|
|
637
|
+
if (
|
|
638
|
+
incentive.priceUsd !== null &&
|
|
639
|
+
incentive.supplyApr !== null &&
|
|
640
|
+
incentive.borrowApr !== null
|
|
641
|
+
) {
|
|
642
|
+
supplyApr = Number(incentive.supplyApr);
|
|
643
|
+
borrowApr = Number(incentive.borrowApr);
|
|
644
|
+
} else {
|
|
645
|
+
const isGovernanceToken =
|
|
646
|
+
token.symbol === environment.custom?.governance?.token;
|
|
647
|
+
const isNativeToken = token.address === zeroAddress;
|
|
648
|
+
|
|
649
|
+
const price = isNativeToken
|
|
650
|
+
? nativeTokenPrice?.value
|
|
651
|
+
: isGovernanceToken
|
|
652
|
+
? governanceTokenPrice?.value
|
|
653
|
+
: undefined;
|
|
654
|
+
|
|
655
|
+
if (!price) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let borrowIncentivesPerSec = BigInt(incentive.borrowIncentivesPerSec);
|
|
660
|
+
const supplyIncentivesPerSec = BigInt(incentive.supplyIncentivesPerSec);
|
|
661
|
+
|
|
662
|
+
// USDC on-chain returns borrowIncentivesPerSec=1 (1 wei) as a
|
|
663
|
+
// placeholder when there are no active borrow incentives. Treat as zero.
|
|
664
|
+
if (token.symbol === "USDC" && borrowIncentivesPerSec === 1n) {
|
|
665
|
+
borrowIncentivesPerSec = 0n;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const supplyRewardsPerDayUsd =
|
|
669
|
+
perDay(new Amount(supplyIncentivesPerSec, token.decimals).value) *
|
|
670
|
+
price;
|
|
671
|
+
const borrowRewardsPerDayUsd =
|
|
672
|
+
perDay(new Amount(borrowIncentivesPerSec, token.decimals).value) *
|
|
673
|
+
price;
|
|
674
|
+
|
|
675
|
+
supplyApr =
|
|
676
|
+
Number(lunarMarket.totalSupplyUsd) === 0
|
|
677
|
+
? 0
|
|
678
|
+
: (supplyRewardsPerDayUsd / Number(lunarMarket.totalSupplyUsd)) *
|
|
679
|
+
DAYS_PER_YEAR *
|
|
680
|
+
100;
|
|
681
|
+
// Negative: borrow reward APR reduces the effective borrowing cost
|
|
682
|
+
borrowApr =
|
|
683
|
+
Number(lunarMarket.totalBorrowsUsd) === 0
|
|
684
|
+
? 0
|
|
685
|
+
: (borrowRewardsPerDayUsd / Number(lunarMarket.totalBorrowsUsd)) *
|
|
686
|
+
DAYS_PER_YEAR *
|
|
687
|
+
100 *
|
|
688
|
+
-1;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
market.rewards.push({
|
|
692
|
+
liquidStakingApr: 0,
|
|
693
|
+
borrowApr,
|
|
694
|
+
supplyApr,
|
|
695
|
+
token,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
market.totalSupplyApr = market.rewards.reduce(
|
|
700
|
+
(prev, curr) => prev + curr.supplyApr,
|
|
701
|
+
market.baseSupplyApy,
|
|
702
|
+
);
|
|
703
|
+
market.totalBorrowApr = market.rewards.reduce(
|
|
704
|
+
(prev, curr) => prev + curr.borrowApr,
|
|
705
|
+
market.baseBorrowApy,
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
markets.push(market);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Still fetch liquid staking rewards from external APIs
|
|
712
|
+
const liquidStakingRewards = await fetchLiquidStakingRewards();
|
|
713
|
+
|
|
714
|
+
for (const market of markets) {
|
|
715
|
+
const symbol = market.underlyingToken.symbol;
|
|
716
|
+
if (symbol in liquidStakingRewards) {
|
|
717
|
+
const liquidStakingApr =
|
|
718
|
+
liquidStakingRewards[symbol as keyof typeof liquidStakingRewards];
|
|
719
|
+
|
|
720
|
+
market.rewards.push({
|
|
721
|
+
token: market.underlyingToken,
|
|
722
|
+
supplyApr: liquidStakingApr,
|
|
723
|
+
borrowApr: 0,
|
|
724
|
+
liquidStakingApr,
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
market.totalSupplyApr += liquidStakingApr;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return markets;
|
|
732
|
+
}
|
|
733
|
+
|
|
239
734
|
const fetchFromGenericCacheApi = async <T>(uri: string): Promise<T> => {
|
|
240
735
|
const response = await fetch(
|
|
241
736
|
"https://generic-api-cache.moonwell.workers.dev/",
|