@moonwell-fi/moonwell-sdk 0.24.0 → 0.25.1
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 +37 -0
- package/_cjs/actions/beam/getBeamQuote.js +35 -2
- package/_cjs/actions/beam/getBeamQuote.js.map +1 -1
- package/_cjs/actions/core/getUserBalances.js +63 -32
- package/_cjs/actions/core/getUserBalances.js.map +1 -1
- package/_cjs/actions/core/markets/common.js +14 -12
- package/_cjs/actions/core/markets/common.js.map +1 -1
- package/_cjs/actions/governance/getStakingInfo.js +1 -7
- package/_cjs/actions/governance/getStakingInfo.js.map +1 -1
- package/_cjs/actions/governance/getUserStakingInfo.js +1 -7
- package/_cjs/actions/governance/getUserStakingInfo.js.map +1 -1
- package/_cjs/actions/governance/getWellPrice.js +51 -1
- package/_cjs/actions/governance/getWellPrice.js.map +1 -1
- package/_cjs/actions/governance/proposals/common.js +34 -10
- package/_cjs/actions/governance/proposals/common.js.map +1 -1
- package/_cjs/actions/governance/proposals/getProposals.js +5 -4
- package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
- package/_cjs/actions/morpho/markets/common.js +32 -2
- package/_cjs/actions/morpho/markets/common.js.map +1 -1
- package/_cjs/actions/morpho/markets/lunarIndexerTransform.js +3 -0
- package/_cjs/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
- package/_cjs/actions/morpho/user-rewards/common.js +1 -7
- package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
- package/_cjs/actions/morpho/vaults/common.js +104 -87
- package/_cjs/actions/morpho/vaults/common.js.map +1 -1
- package/_cjs/actions/morpho/vaults/lunarIndexerTransform.js +69 -21
- package/_cjs/actions/morpho/vaults/lunarIndexerTransform.js.map +1 -1
- package/_cjs/environments/types/config.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_esm/actions/beam/getBeamQuote.js +5 -1
- package/_esm/actions/beam/getBeamQuote.js.map +1 -1
- package/_esm/actions/core/getUserBalances.js +72 -35
- package/_esm/actions/core/getUserBalances.js.map +1 -1
- package/_esm/actions/core/markets/common.js +27 -13
- package/_esm/actions/core/markets/common.js.map +1 -1
- package/_esm/actions/governance/getStakingInfo.js +2 -8
- package/_esm/actions/governance/getStakingInfo.js.map +1 -1
- package/_esm/actions/governance/getUserStakingInfo.js +2 -8
- package/_esm/actions/governance/getUserStakingInfo.js.map +1 -1
- package/_esm/actions/governance/getWellPrice.js +67 -7
- package/_esm/actions/governance/getWellPrice.js.map +1 -1
- package/_esm/actions/governance/proposals/common.js +37 -10
- package/_esm/actions/governance/proposals/common.js.map +1 -1
- package/_esm/actions/governance/proposals/getProposals.js +7 -5
- package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
- package/_esm/actions/morpho/markets/common.js +40 -2
- package/_esm/actions/morpho/markets/common.js.map +1 -1
- package/_esm/actions/morpho/markets/lunarIndexerTransform.js +5 -0
- package/_esm/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
- package/_esm/actions/morpho/user-rewards/common.js +2 -8
- package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
- package/_esm/actions/morpho/vaults/common.js +110 -91
- package/_esm/actions/morpho/vaults/common.js.map +1 -1
- package/_esm/actions/morpho/vaults/lunarIndexerTransform.js +97 -22
- package/_esm/actions/morpho/vaults/lunarIndexerTransform.js.map +1 -1
- package/_esm/environments/types/config.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_types/actions/beam/getBeamQuote.d.ts.map +1 -1
- package/_types/actions/core/getUserBalances.d.ts.map +1 -1
- package/_types/actions/core/markets/common.d.ts.map +1 -1
- package/_types/actions/governance/getStakingInfo.d.ts.map +1 -1
- package/_types/actions/governance/getUserStakingInfo.d.ts.map +1 -1
- package/_types/actions/governance/getWellPrice.d.ts +9 -1
- package/_types/actions/governance/getWellPrice.d.ts.map +1 -1
- package/_types/actions/governance/proposals/common.d.ts.map +1 -1
- package/_types/actions/governance/proposals/getProposals.d.ts +10 -1
- package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
- package/_types/actions/morpho/markets/common.d.ts.map +1 -1
- package/_types/actions/morpho/markets/lunarIndexerTransform.d.ts.map +1 -1
- package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
- package/_types/actions/morpho/vaults/common.d.ts.map +1 -1
- package/_types/actions/morpho/vaults/lunarIndexerTransform.d.ts.map +1 -1
- package/_types/environments/types/config.d.ts +17 -1
- package/_types/environments/types/config.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/actions/beam/getBeamQuote.ts +5 -1
- package/actions/core/getUserBalances.ts +98 -51
- package/actions/core/markets/common.ts +44 -18
- package/actions/governance/getStakingInfo.ts +2 -9
- package/actions/governance/getUserStakingInfo.ts +2 -8
- package/actions/governance/getWellPrice.ts +99 -7
- package/actions/governance/proposals/common.ts +44 -16
- package/actions/governance/proposals/getProposals.ts +22 -5
- package/actions/morpho/markets/common.ts +45 -2
- package/actions/morpho/markets/lunarIndexerTransform.ts +7 -0
- package/actions/morpho/user-rewards/common.ts +2 -8
- package/actions/morpho/vaults/common.ts +161 -127
- package/actions/morpho/vaults/lunarIndexerTransform.ts +117 -36
- package/environments/types/config.ts +17 -1
- package/errors/version.ts +1 -1
- package/package.json +2 -2
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
MorphoVault,
|
|
20
20
|
MorphoVaultMarket,
|
|
21
21
|
} from "../../../types/morphoVault.js";
|
|
22
|
-
import {
|
|
22
|
+
import { getGovernanceTokenPriceOrZero } from "../../governance/getWellPrice.js";
|
|
23
23
|
import { getGraphQL, getVaultV2Apy } from "../utils/graphql.js";
|
|
24
24
|
import {
|
|
25
25
|
SECONDS_PER_YEAR,
|
|
@@ -300,144 +300,184 @@ async function getMorphoVaultsDataFromIndexer(params: {
|
|
|
300
300
|
|
|
301
301
|
// Add staking rewards if includeRewards is true
|
|
302
302
|
if (params.includeRewards) {
|
|
303
|
+
const vaultsByEnvironment = new Map<
|
|
304
|
+
Environment,
|
|
305
|
+
{ vault: MorphoVault; multiReward: Address }[]
|
|
306
|
+
>();
|
|
303
307
|
for (const vault of allVaults) {
|
|
304
308
|
const environment = environments.find(
|
|
305
309
|
(env) => env.chainId === vault.chainId,
|
|
306
310
|
);
|
|
307
311
|
if (!environment) continue;
|
|
308
312
|
|
|
309
|
-
const
|
|
310
|
-
|
|
313
|
+
const multiReward =
|
|
314
|
+
environment.config.vaults[vault.vaultKey]?.multiReward;
|
|
315
|
+
if (!multiReward) continue;
|
|
311
316
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
e.custom?.governance?.chainIds?.includes(environment.chainId),
|
|
317
|
-
) || environment;
|
|
318
|
-
|
|
319
|
-
const viewsContract = environment.contracts.views;
|
|
320
|
-
const homeViewsContract = homeEnvironment.contracts.views;
|
|
321
|
-
|
|
322
|
-
const data = await Promise.all([
|
|
323
|
-
viewsContract?.read.getAllMarketsInfo(),
|
|
324
|
-
homeViewsContract?.read.getNativeTokenPrice(),
|
|
325
|
-
getGovernanceTokenPriceFor(environment).catch((err) => {
|
|
326
|
-
environment.onError?.(err, {
|
|
327
|
-
source: "governance-token-price",
|
|
328
|
-
chainId: environment.chainId,
|
|
329
|
-
});
|
|
330
|
-
return 0n;
|
|
331
|
-
}),
|
|
332
|
-
]);
|
|
317
|
+
const group = vaultsByEnvironment.get(environment) ?? [];
|
|
318
|
+
group.push({ vault, multiReward });
|
|
319
|
+
vaultsByEnvironment.set(environment, group);
|
|
320
|
+
}
|
|
333
321
|
|
|
334
|
-
|
|
322
|
+
// Price reads are per environment, not per vault: read them once per
|
|
323
|
+
// environment (in parallel across environments) and report failures once.
|
|
324
|
+
await Promise.all(
|
|
325
|
+
[...vaultsByEnvironment].map(([environment, group]) =>
|
|
326
|
+
addStakingRewardsForEnvironment(environment, group),
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
335
330
|
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
return [...allVaults, ...fallbackVaults];
|
|
332
|
+
}
|
|
338
333
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
},
|
|
369
|
-
];
|
|
370
|
-
}
|
|
334
|
+
async function getStakingRewardTokenPrices(
|
|
335
|
+
environment: Environment,
|
|
336
|
+
): Promise<{ token: TokenConfig; tokenPrice: Amount }[]> {
|
|
337
|
+
// Fetch market prices from the views contract to calculate rewards
|
|
338
|
+
const homeEnvironment =
|
|
339
|
+
(Object.values(publicEnvironments) as Environment[]).find((e) =>
|
|
340
|
+
e.custom?.governance?.chainIds?.includes(environment.chainId),
|
|
341
|
+
) || environment;
|
|
342
|
+
|
|
343
|
+
const [allMarkets, nativeTokenPriceRaw, governanceTokenPriceRaw] =
|
|
344
|
+
await Promise.all([
|
|
345
|
+
environment.contracts.views?.read.getAllMarketsInfo(),
|
|
346
|
+
homeEnvironment.contracts.views?.read.getNativeTokenPrice(),
|
|
347
|
+
getGovernanceTokenPriceOrZero(environment),
|
|
348
|
+
]);
|
|
349
|
+
|
|
350
|
+
const tokenPrices: { token: TokenConfig; tokenPrice: Amount }[] = [];
|
|
351
|
+
for (const marketInfo of allMarkets ?? []) {
|
|
352
|
+
const marketFound = findMarketByAddress(environment, marketInfo.market);
|
|
353
|
+
if (marketFound) {
|
|
354
|
+
tokenPrices.push({
|
|
355
|
+
token: marketFound.underlyingToken,
|
|
356
|
+
tokenPrice: new Amount(
|
|
357
|
+
marketInfo.underlyingPrice,
|
|
358
|
+
36 - marketFound.underlyingToken.decimals,
|
|
359
|
+
),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
371
363
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
364
|
+
// Add governance token to token prices
|
|
365
|
+
const governanceToken = environment.custom?.governance?.token
|
|
366
|
+
? environment.config.tokens[environment.custom.governance.token]
|
|
367
|
+
: undefined;
|
|
368
|
+
if (governanceToken) {
|
|
369
|
+
tokenPrices.push({
|
|
370
|
+
token: governanceToken,
|
|
371
|
+
tokenPrice: new Amount(governanceTokenPriceRaw, 18),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
380
374
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
375
|
+
// Add native token to token prices
|
|
376
|
+
const nativeToken = findTokenByAddress(environment, zeroAddress);
|
|
377
|
+
if (nativeToken) {
|
|
378
|
+
tokenPrices.push({
|
|
379
|
+
token: nativeToken,
|
|
380
|
+
tokenPrice: new Amount(nativeTokenPriceRaw ?? 0n, 18),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
385
383
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
vaultConfig.multiReward,
|
|
389
|
-
);
|
|
384
|
+
return tokenPrices;
|
|
385
|
+
}
|
|
390
386
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
)
|
|
397
|
-
.forEach((reward) => {
|
|
398
|
-
const token = Object.values(environment.config.tokens).find(
|
|
399
|
-
(token) => token.address === reward?.token,
|
|
400
|
-
);
|
|
401
|
-
if (!token || !reward?.rewardRate) return;
|
|
387
|
+
async function addStakingRewardsForEnvironment(
|
|
388
|
+
environment: Environment,
|
|
389
|
+
group: { vault: MorphoVault; multiReward: Address }[],
|
|
390
|
+
): Promise<void> {
|
|
391
|
+
const vaultKeys = group.map(({ vault }) => vault.vaultKey);
|
|
402
392
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
393
|
+
let tokenPrices: { token: TokenConfig; tokenPrice: Amount }[];
|
|
394
|
+
try {
|
|
395
|
+
tokenPrices = await getStakingRewardTokenPrices(environment);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.warn(
|
|
398
|
+
`Failed to fetch staking reward prices for chain ${environment.chainId}:`,
|
|
399
|
+
error,
|
|
400
|
+
);
|
|
401
|
+
environment.onError?.(error, {
|
|
402
|
+
source: "morpho-vault-staking-rewards",
|
|
403
|
+
chainId: environment.chainId,
|
|
404
|
+
operation: "staking-reward-prices",
|
|
405
|
+
failedCount: group.length,
|
|
406
|
+
totalCount: group.length,
|
|
407
|
+
items: vaultKeys,
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
406
411
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
412
|
+
const settlements = await Promise.allSettled(
|
|
413
|
+
group.map(async ({ vault, multiReward }) => {
|
|
414
|
+
const [rewards, distributorTotalSupply] = await Promise.all([
|
|
415
|
+
getRewardsData(environment, multiReward),
|
|
416
|
+
getTotalSupplyData(environment, multiReward),
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
rewards
|
|
420
|
+
.filter(
|
|
421
|
+
(reward) =>
|
|
422
|
+
reward?.periodFinish &&
|
|
423
|
+
dayjs.utc().isBefore(dayjs.unix(Number(reward.periodFinish))),
|
|
424
|
+
)
|
|
425
|
+
.forEach((reward) => {
|
|
426
|
+
const token = Object.values(environment.config.tokens).find(
|
|
427
|
+
(token) => token.address === reward?.token,
|
|
428
|
+
);
|
|
429
|
+
if (!token || !reward?.rewardRate) return;
|
|
430
|
+
|
|
431
|
+
const market = tokenPrices.find(
|
|
432
|
+
(m) => m?.token.address === reward.token,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const rewardPriceUsd = market?.tokenPrice.value ?? 0;
|
|
436
|
+
|
|
437
|
+
const rewardsPerYear =
|
|
438
|
+
new Amount(reward.rewardRate, market?.token.decimals ?? 18).value *
|
|
439
|
+
SECONDS_PER_YEAR *
|
|
440
|
+
rewardPriceUsd;
|
|
441
|
+
|
|
442
|
+
vault.stakingRewards.push({
|
|
443
|
+
apr:
|
|
444
|
+
(rewardsPerYear /
|
|
445
|
+
(new Amount(distributorTotalSupply, vault.vaultToken.decimals)
|
|
446
|
+
.value *
|
|
447
|
+
vault.underlyingPrice)) *
|
|
448
|
+
100,
|
|
449
|
+
token: token,
|
|
424
450
|
});
|
|
451
|
+
});
|
|
425
452
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
`Failed to fetch staking rewards for vault ${vault.vaultKey}:`,
|
|
434
|
-
error,
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
453
|
+
vault.stakingRewardsApr = vault.stakingRewards.reduce(
|
|
454
|
+
(acc, curr) => acc + curr.apr,
|
|
455
|
+
0,
|
|
456
|
+
);
|
|
457
|
+
vault.totalStakingApr = vault.stakingRewardsApr + vault.baseApy;
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
439
460
|
|
|
440
|
-
|
|
461
|
+
const failedVaultKeys = vaultKeys.filter(
|
|
462
|
+
(_, i) => settlements[i]?.status === "rejected",
|
|
463
|
+
);
|
|
464
|
+
const firstFailure = settlements.find(
|
|
465
|
+
(s): s is PromiseRejectedResult => s.status === "rejected",
|
|
466
|
+
);
|
|
467
|
+
if (firstFailure) {
|
|
468
|
+
console.warn(
|
|
469
|
+
`Failed to fetch staking rewards for vault(s) ${failedVaultKeys.join(", ")} (chain ${environment.chainId}):`,
|
|
470
|
+
firstFailure.reason,
|
|
471
|
+
);
|
|
472
|
+
environment.onError?.(firstFailure.reason, {
|
|
473
|
+
source: "morpho-vault-staking-rewards",
|
|
474
|
+
chainId: environment.chainId,
|
|
475
|
+
operation: "staking-rewards",
|
|
476
|
+
failedCount: failedVaultKeys.length,
|
|
477
|
+
totalCount: group.length,
|
|
478
|
+
items: failedVaultKeys,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
441
481
|
}
|
|
442
482
|
|
|
443
483
|
export async function getMorphoVaultsData(params: {
|
|
@@ -1060,13 +1100,7 @@ async function getMorphoVaultsDataFromOnChain(params: {
|
|
|
1060
1100
|
const data = await Promise.all([
|
|
1061
1101
|
viewsContract?.read.getAllMarketsInfo(),
|
|
1062
1102
|
homeViewsContract?.read.getNativeTokenPrice(),
|
|
1063
|
-
|
|
1064
|
-
environment.onError?.(err, {
|
|
1065
|
-
source: "governance-token-price",
|
|
1066
|
-
chainId: environment.chainId,
|
|
1067
|
-
});
|
|
1068
|
-
return 0n;
|
|
1069
|
-
}),
|
|
1103
|
+
getGovernanceTokenPriceOrZero(environment),
|
|
1070
1104
|
]);
|
|
1071
1105
|
|
|
1072
1106
|
const [allMarkets, nativeTokenPriceRaw, governanceTokenPriceRaw] = data;
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
MorphoVaultMarket,
|
|
16
16
|
MorphoVaultSnapshot,
|
|
17
17
|
} from "../../../types/morphoVault.js";
|
|
18
|
+
import { LunarIndexerError } from "../../lunar-indexer-client.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Lunar Indexer API Response Types
|
|
@@ -248,13 +249,14 @@ export function transformVaultFromIndexer(
|
|
|
248
249
|
// where totalLiquidity briefly exceeds totalAssets.
|
|
249
250
|
const vaultSupplyValue = Math.max(0, totalAssetsValue - totalLiquidityValue);
|
|
250
251
|
|
|
251
|
-
// Transform markets
|
|
252
|
-
|
|
252
|
+
// Transform markets. The indexer has served vaults without `markets` /
|
|
253
|
+
// `rewards` (Sentry MOONWELL-FRONTEND-GX); keep the vault with empty lists.
|
|
254
|
+
const markets = (indexerVault.markets ?? []).map((market) =>
|
|
253
255
|
transformMarket(market, underlyingDecimals, totalAssetsValue, tokenMap),
|
|
254
256
|
);
|
|
255
257
|
|
|
256
258
|
// Transform rewards
|
|
257
|
-
const rewards = transformRewards(indexerVault.rewards, tokenMap);
|
|
259
|
+
const rewards = transformRewards(indexerVault.rewards ?? [], tokenMap);
|
|
258
260
|
|
|
259
261
|
// Build vault object
|
|
260
262
|
const vault: MorphoVault = {
|
|
@@ -313,9 +315,99 @@ export function transformVaultsFromIndexer(
|
|
|
313
315
|
environment: Environment,
|
|
314
316
|
tokenMap: Map<string, LunarIndexerToken>,
|
|
315
317
|
): MorphoVault[] {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
// A malformed record costs only that record, never the chain's whole list.
|
|
319
|
+
// Missing `markets` / `rewards` keep the vault (as empty lists); a vault
|
|
320
|
+
// that cannot be built at all is dropped. Both are reported once per call,
|
|
321
|
+
// with `droppedCount` telling them apart: only the first error is reported,
|
|
322
|
+
// so a consumer demoting the kept case (the frontend logs it at warning,
|
|
323
|
+
// since the list still renders in full) would otherwise misread a batch
|
|
324
|
+
// where a kept record precedes a dropped one.
|
|
325
|
+
const malformed: string[] = [];
|
|
326
|
+
let droppedCount = 0;
|
|
327
|
+
let firstError: unknown;
|
|
328
|
+
const vaults = indexerVaults.flatMap((vault) => {
|
|
329
|
+
// A `null` entry would throw on `vault.markets` below, before the try
|
|
330
|
+
// (PR #339 review). Drop it and count it with the other malformed records.
|
|
331
|
+
if (vault === null || typeof vault !== "object") {
|
|
332
|
+
malformed.push(String(vault));
|
|
333
|
+
droppedCount += 1;
|
|
334
|
+
firstError ??= new Error(
|
|
335
|
+
`Lunar Indexer vault record is ${String(vault)}, not an object`,
|
|
336
|
+
);
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
if (!Array.isArray(vault.markets) || !Array.isArray(vault.rewards)) {
|
|
340
|
+
malformed.push(vault.address);
|
|
341
|
+
firstError ??= new Error(
|
|
342
|
+
`Lunar Indexer vault ${vault.address} is missing ${!Array.isArray(vault.markets) ? "markets" : "rewards"}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
return [transformVaultFromIndexer(vault, environment, tokenMap)];
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (!malformed.includes(vault.address)) malformed.push(vault.address);
|
|
349
|
+
droppedCount += 1;
|
|
350
|
+
firstError ??= error;
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (malformed.length > 0) {
|
|
356
|
+
console.warn(
|
|
357
|
+
`Malformed Lunar Indexer vault record(s) on chain ${environment.chainId}: ${malformed.join(", ")}`,
|
|
358
|
+
firstError,
|
|
359
|
+
);
|
|
360
|
+
environment.onError?.(firstError, {
|
|
361
|
+
source: "vaults",
|
|
362
|
+
chainId: environment.chainId,
|
|
363
|
+
operation: "malformed-vault-records",
|
|
364
|
+
failedCount: malformed.length,
|
|
365
|
+
totalCount: indexerVaults.length,
|
|
366
|
+
items: malformed,
|
|
367
|
+
droppedCount,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return vaults;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Fetch JSON from the Lunar Indexer and reject on a non-OK or missing response.
|
|
376
|
+
*
|
|
377
|
+
* `response` can arrive `undefined` when a wallet extension monkeypatches
|
|
378
|
+
* `window.fetch` (Sentry MOONWELL-FRONTEND-H0 / -133, MOO-535); reading `.ok`
|
|
379
|
+
* on it used to throw a bare TypeError instead of a diagnosable error.
|
|
380
|
+
*
|
|
381
|
+
* The rejection is a `LunarIndexerError` whose message must not start with
|
|
382
|
+
* "Failed to fetch": the frontend's Sentry noise filter substring-matches that
|
|
383
|
+
* browser transport text and dropped every failure raised here (PR #339
|
|
384
|
+
* review). Consumers should match on `error.name`, not on the message.
|
|
385
|
+
*
|
|
386
|
+
* `AbortSignal.timeout` is missing in Safari < 16 and older WebViews, where it
|
|
387
|
+
* throws a bare TypeError before `fetch` runs; use AbortController instead and
|
|
388
|
+
* keep the timer alive until the body has been read.
|
|
389
|
+
*/
|
|
390
|
+
async function fetchJsonFromIndexer<T>(url: string, what: string): Promise<T> {
|
|
391
|
+
const controller = new AbortController();
|
|
392
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
393
|
+
try {
|
|
394
|
+
const response: Response | undefined = await fetch(url, {
|
|
395
|
+
signal: controller.signal,
|
|
396
|
+
});
|
|
397
|
+
if (!response?.ok) {
|
|
398
|
+
const status = response
|
|
399
|
+
? `${response.status} ${response.statusText}`
|
|
400
|
+
: "no response";
|
|
401
|
+
throw new LunarIndexerError(
|
|
402
|
+
`Lunar Indexer ${what} request failed: ${status}`,
|
|
403
|
+
response?.status,
|
|
404
|
+
url,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return await response.json();
|
|
408
|
+
} finally {
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
}
|
|
319
411
|
}
|
|
320
412
|
|
|
321
413
|
/**
|
|
@@ -331,16 +423,16 @@ export async function fetchTokenMap(
|
|
|
331
423
|
): Promise<Map<string, LunarIndexerToken>> {
|
|
332
424
|
const url = `${lunarIndexerUrl}/api/v1/vaults/tokens/${chainId}`;
|
|
333
425
|
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
426
|
+
const data = await fetchJsonFromIndexer<LunarIndexerTokensResponse>(
|
|
427
|
+
url,
|
|
428
|
+
"tokens",
|
|
429
|
+
);
|
|
430
|
+
if (!Array.isArray(data?.results)) {
|
|
337
431
|
throw new Error(
|
|
338
|
-
`
|
|
432
|
+
`Lunar Indexer tokens response for chain ${chainId} is missing the results array`,
|
|
339
433
|
);
|
|
340
434
|
}
|
|
341
435
|
|
|
342
|
-
const data: LunarIndexerTokensResponse = await response.json();
|
|
343
|
-
|
|
344
436
|
const tokenMap = new Map<string, LunarIndexerToken>();
|
|
345
437
|
for (const token of data.results) {
|
|
346
438
|
tokenMap.set(token.address.toLowerCase(), token);
|
|
@@ -373,15 +465,17 @@ export async function fetchVaultsFromIndexer(
|
|
|
373
465
|
|
|
374
466
|
const url = `${lunarIndexerUrl}/api/v1/vaults/vaults/${chainId}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
375
467
|
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
468
|
+
const data = await fetchJsonFromIndexer<LunarIndexerVaultsResponse>(
|
|
469
|
+
url,
|
|
470
|
+
"vaults",
|
|
471
|
+
);
|
|
472
|
+
// transformVaultsFromIndexer calls `.flatMap` on this array (PR #339 review).
|
|
473
|
+
if (!Array.isArray(data?.results)) {
|
|
379
474
|
throw new Error(
|
|
380
|
-
`
|
|
475
|
+
`Lunar Indexer vaults response for chain ${chainId} is missing the results array`,
|
|
381
476
|
);
|
|
382
477
|
}
|
|
383
|
-
|
|
384
|
-
return response.json();
|
|
478
|
+
return data;
|
|
385
479
|
}
|
|
386
480
|
|
|
387
481
|
/**
|
|
@@ -397,15 +491,7 @@ export async function fetchVaultFromIndexer(
|
|
|
397
491
|
): Promise<LunarIndexerVault> {
|
|
398
492
|
const url = `${lunarIndexerUrl}/api/v1/vaults/vault/${vaultId}`;
|
|
399
493
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (!response.ok) {
|
|
403
|
-
throw new Error(
|
|
404
|
-
`Failed to fetch vault from Lunar Indexer: ${response.status} ${response.statusText}`,
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return response.json();
|
|
494
|
+
return fetchJsonFromIndexer<LunarIndexerVault>(url, "vault");
|
|
409
495
|
}
|
|
410
496
|
|
|
411
497
|
/**
|
|
@@ -464,15 +550,10 @@ export async function fetchVaultSnapshotsFromIndexer(
|
|
|
464
550
|
const queryString = params.toString();
|
|
465
551
|
const url = `${lunarIndexerUrl}/api/v1/vaults/vault/${vaultId}/snapshots${queryString ? `?${queryString}` : ""}`;
|
|
466
552
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
`Failed to fetch vault snapshots from Lunar Indexer: ${response.status} ${response.statusText}`,
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return response.json();
|
|
553
|
+
return fetchJsonFromIndexer<LunarIndexerVaultSnapshotsResponse>(
|
|
554
|
+
url,
|
|
555
|
+
"vault snapshots",
|
|
556
|
+
);
|
|
476
557
|
}
|
|
477
558
|
|
|
478
559
|
/**
|
|
@@ -228,11 +228,27 @@ export type ContractsConfig<contracts, tokens> = {} extends contracts
|
|
|
228
228
|
? {}
|
|
229
229
|
: { [name in keyof ContractConfig<tokens>]?: ContractConfig<tokens>[name] };
|
|
230
230
|
|
|
231
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* Context passed to `onError` callbacks. `token` is set for per-token read failures.
|
|
233
|
+
*
|
|
234
|
+
* Batched reads report once per operation rather than once per item: `error`
|
|
235
|
+
* is the first failure, `failedCount`/`totalCount` size the batch and `items`
|
|
236
|
+
* identifies what failed (proposalIds, token/vault addresses, chainIds...).
|
|
237
|
+
*
|
|
238
|
+
* `droppedCount` is how many of the failed items were left out of the result
|
|
239
|
+
* rather than kept in a degraded form. Only `error` (the first failure) is
|
|
240
|
+
* reported, so without it a consumer cannot tell a batch whose failed records
|
|
241
|
+
* were all kept from one where a later record was dropped.
|
|
242
|
+
*/
|
|
232
243
|
export type OnErrorContext = {
|
|
233
244
|
source: string;
|
|
234
245
|
chainId: number;
|
|
235
246
|
token?: Address;
|
|
247
|
+
operation?: string;
|
|
248
|
+
failedCount?: number;
|
|
249
|
+
totalCount?: number;
|
|
250
|
+
items?: string[];
|
|
251
|
+
droppedCount?: number;
|
|
236
252
|
};
|
|
237
253
|
|
|
238
254
|
export type CustomConfig<custom> = {} extends custom
|
package/errors/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '0.25.1'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonwell-fi/moonwell-sdk",
|
|
3
3
|
"description": "TypeScript Interface for Moonwell",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.25.1",
|
|
5
5
|
"main": "./_cjs/index.js",
|
|
6
6
|
"module": "./_esm/index.js",
|
|
7
7
|
"types": "./_types/index.d.ts",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"@rhinestone/module-sdk": "0.2.7",
|
|
77
77
|
"@types/lodash": "^4.17.9",
|
|
78
78
|
"@types/node": "^22.13.10",
|
|
79
|
-
"axios": "^1.
|
|
79
|
+
"axios": "^1.20.0",
|
|
80
80
|
"dayjs": "^1.11.20",
|
|
81
81
|
"lodash": "^4.17.21",
|
|
82
82
|
"viem": "^2.26.2"
|